用户活动记录,遥测(和全局异常处理程序中的变量) [英] User Activity Logging, Telemetry (and Variables in Global Exception Handlers)

查看:255
本文介绍了用户活动记录,遥测(和全局异常处理程序中的变量)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:



我正在处理一个非常古老的应用程序,非常间歇性地产生异常。



当前实践



通常我们的程序员使用全局异常处理程序处理罕见的未知数,这个:

  [STAThread] 
[SecurityPermission(SecurityAction.Demand,Flags = SecurityPermissionFlag.ControlAppDomain)]
private static void Main()
{
Application.ThreadException + = new ThreadExceptionEventHandler(UIThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException + =
new UnhandledExceptionEventHandler(UnhandledException);

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new OldAppWithLotsOfWierdExceptionsThatUsersAlwaysIgnore());
}

private static void UIThreadException(object sender,ThreadExceptionEventArgs t)
{
// --------------- ----------------
ReportToDevelopers(您需要重新解决问题的所有步骤和变量是:+
ShowMeStepsToReproduceAndDiagnoseProblem(t));
// -------------------------------

MessageToUser.Show(这不是你,这是我们的,这是我们的错误。\\\
这个错误的详细信息已经被自动记录下来,我们已经收到通知。是的,我们来看看每一个错误,甚至尝试修复其中的一些。)
}

private static void UnhandledException(object sender,UnhandledExceptionEventArgs e)
{
// ...
}

问题域



很难从用户那里获得重复的步骤,并且由于报告的不同数量的问题,我不想下降(第二次机会异常)WinDBG或CDB故障排除路径。我想要一些指标,希望有一些最近的System.Diagnostic爱情。



研究/了解:



很久以前,我读了一本书调试Microsoft .NET 2.0应用程序它讨论了一个很酷的工具,约翰·罗宾斯(又名The BugSlayer)撰写了 SuperAssert。 Net



此工具的唯一缺点是(为了解决问题)内存转储的大小很大,当然调试它们几乎是



问题:



我希望有人可以告诉我一个方法,我可以在这个程序中转储变量,至少在应用程序的最后一步中 Exception.StackTrace



这几天可能是这样吗?它很容易让我将StackTrace映射回用户操作来解决步骤.. 我只需要变量!



更新



原来是一个有故障的路由器。

解决方案

项目现在在GitHub上: https://github.com/MeaningOfLights/UserActionLog



...



我做了大量的研究。最后,我刚刚创建了用户所做的日志,它是内存转储大小的一小部分,可靠地让我重现问题的步骤。它还提供了另一个好处,了解用户如何使用该应用程序。


*我认真地找不到任何在线的基本用户活动记录。我发现的一切都是关于AOP,Auto UI测试框架或1/2 Gig内存转储。


为了您的方便,这里是好的!






ActionLogger类:



< p $ p> public class ActionLogger
{
private Type _frmType;
私人表单_frm;
///< summary>
/// Ctor Lazy挂钩所有表单控件事件以侦听用户操作的方式。
///< / summary>
/// ///< param name =frm> WinForm,WPF,Xamarin等。Form。< / param>
public ActionLogger(control frm)
{
_frmType =((Form)frm).GetType();
_frm =(Form)frm;
ActionLoggerSetUp(frm);
}

///< summary>
/// Ctor挂起控制事件以监听用户操作的最佳方式。
///< / summary>
public ActionLogger(Control [] ctrls)
{
ActionLoggerSetUp(ctrls);
}

///< summary>
///挂起所有表单控件事件的Lazy方式来监听用户操作。
///< / summary>
/// ///< param name =frm> WinForm,WPF,Xamarin等。Form。< / param>
public void ActionLoggerSetUp(Control frm)
{
HookUpEvents(frm); //首先连接这个控件的事件,然后遍历钩子的
foreach(控制ctrl在frm.Controls){
ActionLoggerSetUp(ctrl); //通过* Form的* child-> child->等递归地连接控件事件控制
}
}

///< summary>
///挂起控制事件以监听用户操作的最佳方式。
///< / summary>
///< param name =ctrls> WinForm,WPF,Xamarin等的控件。< param>
public void ActionLoggerSetUp(Control [] ctrls)
{
foreach(var ctrl in ctrls){
HookUpEvents(ctrl);
}
}

///< summary>
///释放挂起事件(避免内存泄漏)。
///< / summary>
public void ActionLoggerTierDown(控制frm)
{
ReleaseEvents(frm);
}

///< summary>
///挂起调试问题所需的事件。随意添加更多控件,如ListView,例如将LogAction()订阅到更多的事件。
///< / summary>
///< param name =ctrl>控件的事件我们怀疑导致问题。< / param>
private void HookUpEvents(Control ctrl)
{
if(ctrl为Form){
Form frm =((Form)ctrl);
frm.Load + = LogAction;
frm.FormClosed + = LogAction;
frm.ResizeBegin + = LogAction;
frm.ResizeEnd + = LogAction;
}
else if(ctrl是TextBoxBase){
TextBoxBase txt =((TextBoxBase)ctrl);
txt.Enter + = LogAction;
}
else if(ctrl是ListControl){// ListControl代表ComboBoxes和ListBoxes。
ListControl lst =((ListControl)ctrl);
lst.SelectedValueChanged + = LogAction;
}
else if(ctrl是ButtonBase){// ButtonBase表示Buttons,CheckBoxes和RadioButtons。
ButtonBase btn =((ButtonBase)ctrl);
btn.Click + = LogAction;
}
else if(ctrl是DateTimePicker){
DateTimePicker dtp =((DateTimePicker)ctrl);
dtp.Enter + = LogAction;
dtp.ValueChanged + = LogAction;
}
else if(ctrl是DataGridView){
DataGridView dgv =((DataGridView)ctrl);
dgv.RowEnter + = LogAction;
dgv.CellBeginEdit + = LogAction;
dgv.CellEndEdit + = LogAction;
}
}

///< summary>
///释放挂起事件(避免内存泄漏)。
///< / summary>
///< param name =ctrl>< / param>
private void ReleaseEvents(Control ctrl)
{
if(ctrl是Form){
Form frm =((Form)ctrl);
frm.Load - = LogAction;
frm.FormClosed - = LogAction;
frm.ResizeBegin - = LogAction;
frm.ResizeEnd - = LogAction;
}
else if(ctrl是TextBoxBase){
TextBoxBase txt =((TextBoxBase)ctrl);
txt.Enter - = LogAction;
}
else if(ctrl为ListControl){
ListControl lst =((ListControl)ctrl);
lst.SelectedValueChanged - = LogAction;
}
else if(ctrl是DateTimePicker){
DateTimePicker dtp =((DateTimePicker)ctrl);
dtp.Enter - = LogAction;
dtp.ValueChanged - = LogAction;
}
else if(ctrl是ButtonBase){
ButtonBase btn =((ButtonBase)ctrl);
btn.Click - = LogAction;
}
else if(ctrl是DataGridView){
DataGridView dgv =((DataGridView)ctrl);
dgv.RowEnter - = LogAction;
dgv.CellBeginEdit - = LogAction;
dgv.CellEndEdit - = LogAction;
}
}

///< summary>
///记录调用的控件,其值为
///< / summary>
///< param name =sender>< / param>
///< param name =e>< / param>
public void LogAction(object sender,EventArgs e)
{
如果(!(发件人是Form || sender是ButtonBase || sender是DataGridView))//定制此行以适合您的需要
{//不记录控制事件,如果它的维护表单,而不是编辑模式
if(_frmType.BaseType.ToString()。包含(frmMaint)){//这是严格的具体到我的项目 - 你将需要重写这一行,也可能上面的行。这就是...
PropertyInfo pi = _frmType.GetProperty(IsEditing);
bool isEditing =(bool)pi.GetValue(_frm,null);
if(!isEditing)return;
}
}
StackTrace stackTrace = new StackTrace();
StackFrame [] stackFrames = stackTrace.GetFrames();
var eventType = stackFrames [2] .GetMethod()。Name; //这通常取决于第一帧,但在这个特定的框架(CSLA)中它的2
ActionLog.LogAction(_frm.Name,( (Control)sender).Name,eventType,GetSendingCtrlValue(((Control)sender),eventType));


私有字符串GetSendingCtrlValue(控制ctrl,字符串eventType)
{
如果(ctrl是TextBoxBase){
return((TextBoxBase)ctrl) 。文本;
}
// else if(ctrl是CheckBox || ctrl是RadioButton){
// return((ButtonBase)ctrl).Text;
//}
else if(ctrl是ListControl){
return((ListControl)ctrl).Text.ToString();
}
else if(ctrl是DateTimePicker){
return((DateTimePicker)ctrl).Text;
}
如果(ctrl是DataGridView&& eventType ==OnRowEnter)
{
if(((DataGridView)ctrl).SelectedRows.Count> 0 ){
return((DataGridView)ctrl).SelectedRows [0] .Cells [0] .Value.ToString();
}
else {
return string.Empty;
}
}
else if(ctrl是DataGridView){
DataGridViewCell cell =(((DataGridView)ctrl).CurrentCell);
if(cell == null)return string.Empty;
if(cell.Value == null)return string.Empty;
return cell.Value.ToString();
}
return string.Empty;
}
}

ActionLog类: / p>

  public static class ActionLog 
{
const string ACTIONLOGFILEIDENTIFIER =ActionLog_;
private static int _numberOfDaily = 0;
private static int _maxNumerOfLogsInMemory = 512;
private static List< string> _TheUserActions = new List< string>();
private static string _actionLoggerDirectory = string.Empty;

public static void LogActionSetUp(int maxNumerOfLogsInMemory = 512,string actionLoggerDirectory =)
{
if(string.IsNullOrEmpty(actionLoggerDirectory))actionLoggerDirectory = Environment.GetFolderPath(Environment。 SpecialFolder.UserProfile)+\\Documents\\ProjectNameMgtFolder\\;
if(!Directory.Exists(actionLoggerDirectory))Directory.CreateDirectory(actionLoggerDirectory);

_actionLoggerDirectory = actionLoggerDirectory;

LogAction(MDI_Form,APPLICATION,STARTUP,string.Empty);
}

public static void LogAction(string frmName,string ctrlName,string eventName,string value)
{
if(value.Length> 10)value = value.Substring(0,10);
LogAction(DateTime.Now,frmName,ctrlName,eventName,value);
}

public static void LogAction(DateTime timeStamp,string frmName,string ctrlName,string eventName,string value)
{
_TheUserActions.Add(string.Format {0} \t {1} \t {2} \t {3} \t {4},timeStamp.ToShortTimeString(),frmName,ctrlName,eventName,value));
if(_TheUserActions.Count> _maxNumerOfLogsInMemory)WriteLogActionsToFile();
}

public static string GetLogFileName()
{
//检查当前文件是否为> 1 MB并创建另一个
string [] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory,ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(yyyyMMdd)+* .log);

string filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(yyyyMMdd)+-0.log;
if(existingFileList.Count()> 0)
{
filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(yyyyMMdd)+ - +(existingFileList.Count ) - 1).ToString()+.log;
FileInfo fi = new FileInfo(filePath);
if(fi.Length / 1024> 1000)//超过MB(即> 1000 KBs)
{
filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(yyyyMMdd )+ - + existingFileList.Count()。ToString()+.log;
}
}
return filePath;
}

public static string [] GetTodaysLogFileNames()
{
string [] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory,ACTIONLOGFILEIDENTIFIER + DateTime.Now .ToString(yyyyMMdd)+* .log);
return existingFileList;
}

public static void WriteLogActionsToFile()
{
string logFilePath = GetLogFileName();
if(File.Exists(logFilePath)){
File.AppendAllLines(logFilePath,_TheUserActions);
}
else {
File.WriteAllLines(logFilePath,_TheUserActions);
}
_TheUserActions = new List< string>();
}
}




注意:LogAction方法最有可能是第二次(例如,对于Button按钮,它将在Button_Click事件被调用后被调用)。因此,您可能认为您需要先插入这些LogAction事件,例如通过撤销事件调用顺序来< href =https://stackoverflow.com/a/374549/495455>不是很好的做法,而不是必需的。 诀窍在堆栈跟踪中,堆栈中的最后一个调用将告诉您最后一个用户操作。操作日志告诉您如何在未处理的异常发生之前将程序置于状态。一旦你得到了这一点,你需要跟随StackTrace来错误的应用程序。


例如MDI窗体加载事件:

  UserActionLog.ActionLog.LogActionSetUp(); 

在MDI窗体关闭事件中:

  UserActionLog.ActionLog.WriteLogActionsToFile(); 

在子表单构造函数中:

  _logger = New UserActionLog.ActionLogger(this); 

在儿童关闭活动中

  _logger.ActionLoggerTierDown(this); 

UIThreadException CurrentDomain_UnhandledException 事件调用 WriteLogActionsToFile(); 然后将日志附加到发送到支持的电子邮件的屏幕截图...






以下是通过电子邮件发送支持日志文件的快速示例:

  string _errMsg = new System.Text.StringBuilder(); 
string _caseNumber = IO.Path.GetRandomFileName.Substring(0,5).ToUpper();
string _errorType;
string _screenshotPath;
列表< string> _emailAttachments = new List< string>();
string _userName;

private static void UIThreadException(object sender,ThreadExceptionEventArgs t)
{
_errorType =UI线程异常

....

//包含支持电子邮件正文的异常详细信息的HTML表
_errMsg.Append(< table>< tr>< td colSpan = 1>< b>用户: < / b>< / td>< td colSpan = 2>& _userName&< / td>< / tr>);
_errMsg.Append(< tr>< td>< b>时间:< / b>< / td>< td>& _errorDateTime.ToShortTimeString&< / td> ;< / TR>< TR>< / TR>中);
_errMsg.Append(< tr>< td>< b>异常类型:< / b>< / td>< td>& _errorType.ToString&& TD>< / TR>< TR>< / TR>中);

if(exception!= null){
_errMsg.Append(< tr>< td>< b>消息:< / b>< / td> ; td>& exception.Message.Replace(at,at< br>)&< / td>< / tr>< tr>< / tr>);
if(exception.InnerException!= null)_errMsg.Append(< tr>< td>< b>内部异常:< / b>< / td>< td>& exception.InnerException.Message&< / td>< / tr>);
_errMsg.Append(< tr>< td>< b> StackTrace:< / b>< / td>< td>& exception.StackTrace&< / td> ;< / TR>< /表>);
}

....

//将内存中的日志写入文件
UserActionLog.ActionLog.WriteLogActionsToFile();

//获取当前日志文件列表
_emailAttachments.AddRange(UserActionLog.ActionLog.GetTodaysLogFileNames());

//添加破碎的窗口的屏幕截图是一个很好的触摸
//https://stackoverflow.com/a/1163770/495455
_emailAttachments.Add( _screenshotPath);

....


电子邮件电子邮件系统=新的电子邮件(); //(使用Microsoft.Exchange.WebServices.Data)
emailSystem.SendEmail(ConfigMgr.AppSettings.GetSetting(EmailSupport),PROJECT_NAME-PROBLEM CASE ID:& _caseNumber,_errMsg.ToString(),_emailAttachments .ToArray());

发送电子邮件后,用户发现一个解释问题的窗口,一张漂亮的图片... StackExchange网站有很好的例子,这是我最喜欢的: https://serverfault.com/error


Background:

I am dealing with a very old app that generates Exceptions quite rarely and very intermittently.

Current Practices:

Typically we programmers deal with rare unknowns using Global Exception handlers, wiring up something like this:

[STAThread]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
private static void Main()
{
    Application.ThreadException += new ThreadExceptionEventHandler(UIThreadException);
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 
    AppDomain.CurrentDomain.UnhandledException +=
        new UnhandledExceptionEventHandler(UnhandledException);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new OldAppWithLotsOfWierdExceptionsThatUsersAlwaysIgnore());
}

private static void UIThreadException(object sender, ThreadExceptionEventArgs t)
{
    //-------------------------------
    ReportToDevelopers("All the steps & variables you need to repro the problem are: " + 
    ShowMeStepsToReproduceAndDiagnoseProblem(t));
    //-------------------------------

    MessageToUser.Show("It’s not you, it’s us. This is our fault.\r\n Detailed information about this error has automatically been recorded and we have been notified.Yes, we do look at every error. We even try to fix some of them.")
}

private static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    //...
}

Problem Domain:

Its hard to get the repro steps from users and because of the varied amount of issues being reported I dont want to go down (the Second Chance Exception) WinDBG or CDB troubleshooting paths just yet. I want some metrics and hopefully some recent System.Diagnostic love first.

Research/Understanding:

A long time ago I read a book Debugging Microsoft .NET 2.0 Applications and it discusses a cool tool that John Robbins (a.k.a The BugSlayer) wrote SuperAssert.Net

The only downside to this tool is (in order to work out problems) the size of the memory dumps are huge and of course debugging them is almost as much of an art, as it is a science.

Question:

I am hoping someone can tell me a way I can dump out variables in this program, well at least the one's in the last step of the applications Exception.StackTrace.

Is this at all possible these days? Its easy enough for me to map the StackTrace back to user actions to work out the steps.. I just need the variables!

Update

Turned out to be a faulty router.

解决方案

The Open Source Project is now on GitHub: https://github.com/MeaningOfLights/UserActionLog

...

I did a HUGE amount of reseach into this*. In the end I just created a log of what the user does, its a fraction of the size of a memory dump and reliably gets me the steps to reproduce problems. It also serves another benefit, understanding how users use the application.

*I seriously could not find anything online that does this basic User Activity Logging. Everything I found was about AOP, Auto UI Testing Frameworks or 1/2 Gig memory dumps.

For your convenience here is the goodness!


ActionLogger Class:

public class ActionLogger
{
    private Type _frmType;
    private Form _frm;
    /// <summary>
    /// Ctor Lazy way of hooking up all form control events to listen for user actions.
    /// </summary>
    /// /// <param name="frm">The WinForm, WPF, Xamarin, etc Form.</param>
    public ActionLogger(Control frm)
    {
        _frmType = ((Form)frm).GetType();
        _frm = (Form)frm;
        ActionLoggerSetUp(frm);
    }

    /// <summary>
    /// Ctor Optimal way of hooking up control events to listen for user actions.
    /// </summary>
    public ActionLogger(Control[] ctrls)
    {
        ActionLoggerSetUp(ctrls);
    }

    /// <summary>
    /// Lazy way of hooking up all form control events to listen for user actions.
    /// </summary>
    /// /// <param name="frm">The WinForm, WPF, Xamarin, etc Form.</param>
    public void ActionLoggerSetUp(Control frm)
    {
        HookUpEvents(frm);  //First hook up this controls' events, then traversely Hook Up its children's
        foreach (Control ctrl in frm.Controls) {
            ActionLoggerSetUp(ctrl); //Recursively hook up control events via the *Form's* child->child->etc controls
        }
    }

    /// <summary>
    /// Optimal way of hooking up control events to listen for user actions.
    /// </summary>
    /// <param name="ctrls">The controls on the WinForm, WPF, Xamarin, etc Form.<param>
    public void ActionLoggerSetUp(Control[] ctrls)
    { 
        foreach (var ctrl in ctrls) {
            HookUpEvents(ctrl);
        }
    }

    /// <summary>
    /// Releases the hooked up events (avoiding memory leaks).
    /// </summary>      
    public void ActionLoggerTierDown(Control frm)
    {
        ReleaseEvents(frm);
    }

    /// <summary>
    /// Hooks up the event(s) needed to debug problems. Feel free to add more Controls like ListView for example subscribe LogAction() to more events.
    /// </summary>
    /// <param name="ctrl">The control whose events we're suspicious of causing problems.</param>
    private void HookUpEvents(Control ctrl)
    {
        if (ctrl is Form) {
            Form frm = ((Form)ctrl);
            frm.Load += LogAction;
            frm.FormClosed += LogAction;
            frm.ResizeBegin += LogAction;
            frm.ResizeEnd += LogAction;
        }
        else if (ctrl is TextBoxBase) {
            TextBoxBase txt = ((TextBoxBase)ctrl);
            txt.Enter += LogAction;
        }
        else if (ctrl is ListControl) { //ListControl stands for ComboBoxes and ListBoxes.
            ListControl lst = ((ListControl)ctrl);
            lst.SelectedValueChanged += LogAction;
        }
        else if (ctrl is ButtonBase) { //ButtonBase stands for Buttons, CheckBoxes and RadioButtons.
            ButtonBase btn = ((ButtonBase)ctrl);
            btn.Click += LogAction;
        }
        else if (ctrl is DateTimePicker) {
            DateTimePicker dtp = ((DateTimePicker)ctrl);
            dtp.Enter += LogAction;
            dtp.ValueChanged += LogAction;
        }
        else if (ctrl is DataGridView) {
            DataGridView dgv = ((DataGridView)ctrl);
            dgv.RowEnter += LogAction;
            dgv.CellBeginEdit += LogAction; 
            dgv.CellEndEdit += LogAction;
        }
    }

    /// <summary>
    /// Releases the hooked up events (avoiding memory leaks).
    /// </summary>
    /// <param name="ctrl"></param>
    private void ReleaseEvents(Control ctrl)
    {
        if (ctrl is Form) {
            Form frm = ((Form)ctrl);
            frm.Load -= LogAction;
            frm.FormClosed -= LogAction;
            frm.ResizeBegin -= LogAction;
            frm.ResizeEnd -= LogAction;
        }
        else if (ctrl is TextBoxBase) {
            TextBoxBase txt = ((TextBoxBase)ctrl);
            txt.Enter -= LogAction;
        }
        else if (ctrl is ListControl) {
            ListControl lst = ((ListControl)ctrl);
            lst.SelectedValueChanged -= LogAction;
        }
        else if (ctrl is DateTimePicker) {
            DateTimePicker dtp = ((DateTimePicker)ctrl); 
            dtp.Enter -= LogAction;
            dtp.ValueChanged -= LogAction;
        }
        else if (ctrl is ButtonBase) {
            ButtonBase btn = ((ButtonBase)ctrl);
            btn.Click -= LogAction; 
        }
        else if (ctrl is DataGridView) {
            DataGridView dgv = ((DataGridView)ctrl);
            dgv.RowEnter -= LogAction;
            dgv.CellBeginEdit -= LogAction; 
            dgv.CellEndEdit -= LogAction;
        }
    }

    /// <summary>
    /// Log the Control that made the call and its value
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void LogAction(object sender, EventArgs e)
    {
        if (!(sender is Form || sender is ButtonBase || sender is DataGridView)) //Tailor this line to suit your needs
        {   //dont log control events if its a Maintenance Form and its not in Edit mode
            if (_frmType.BaseType.ToString().Contains("frmMaint")) {//This is strictly specific to my project - you will need to rewrite this line and possible the line above too. That's all though...
                PropertyInfo pi = _frmType.GetProperty("IsEditing");
                bool isEditing = (bool)pi.GetValue(_frm, null);
                if (!isEditing) return;
            }
        }
        StackTrace stackTrace = new StackTrace();      
        StackFrame[] stackFrames = stackTrace.GetFrames();
        var eventType = stackFrames[2].GetMethod().Name;//This depends usually its the 1st Frame but in this particular framework (CSLA) its 2
        ActionLog.LogAction(_frm.Name, ((Control)sender).Name, eventType, GetSendingCtrlValue(((Control)sender), eventType));
    }

    private string GetSendingCtrlValue(Control ctrl, string eventType)
    {
        if (ctrl is TextBoxBase) {
            return ((TextBoxBase)ctrl).Text;
        }
        //else if (ctrl is CheckBox || ctrl is RadioButton) {
        //  return  ((ButtonBase)ctrl).Text;
        //}
        else if (ctrl is ListControl) {
            return ((ListControl)ctrl).Text.ToString();
        }
        else if (ctrl is DateTimePicker) {
            return ((DateTimePicker)ctrl).Text;
        }
        else if (ctrl is DataGridView && eventType == "OnRowEnter")
        {
            if (((DataGridView)ctrl).SelectedRows.Count > 0) {
                return ((DataGridView)ctrl).SelectedRows[0].Cells[0].Value.ToString();
            }
            else {
                return string.Empty;
            }
        }
        else if (ctrl is DataGridView) {
            DataGridViewCell cell = (((DataGridView)ctrl).CurrentCell);
            if (cell == null) return string.Empty;
            if (cell.Value == null) return string.Empty;
            return cell.Value.ToString();
        }
        return string.Empty;
    }
}

ActionLog Class:

public static class ActionLog
{
    const string ACTIONLOGFILEIDENTIFIER = "ActionLog_"; 
    private static int _numberOfDaily = 0;
    private static int _maxNumerOfLogsInMemory = 512;
    private static List<string> _TheUserActions = new List<string>();
    private static string _actionLoggerDirectory = string.Empty;

    public static void LogActionSetUp(int maxNumerOfLogsInMemory = 512,string actionLoggerDirectory = "")
    {  
        if (string.IsNullOrEmpty(actionLoggerDirectory)) actionLoggerDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\Documents\\ProjectNameMgtFolder\\";
        if (!Directory.Exists(actionLoggerDirectory)) Directory.CreateDirectory(actionLoggerDirectory);

        _actionLoggerDirectory = actionLoggerDirectory;

        LogAction("MDI_Form", "APPLICATION", "STARTUP", string.Empty);
    }

    public static void LogAction(string frmName, string ctrlName, string eventName, string value)
    {
        if (value.Length > 10) value = value.Substring(0, 10);
        LogAction(DateTime.Now, frmName,ctrlName, eventName, value);
    }

    public static void LogAction(DateTime timeStamp, string frmName, string ctrlName, string eventName, string value)
    {
        _TheUserActions.Add(string.Format("{0}\t{1}\t{2}\t{3}\t{4}", timeStamp.ToShortTimeString(), frmName, ctrlName, eventName, value));
        if (_TheUserActions.Count > _maxNumerOfLogsInMemory) WriteLogActionsToFile();
    }

    public static string GetLogFileName()
    {
        //Check if the current file is > 1 MB and create another
        string[] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory, ACTIONLOGFILEIDENTIFIER +  DateTime.Now.ToString("yyyyMMdd") + "*.log");

        string filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString("yyyyMMdd") + "-0.log";
        if (existingFileList.Count() > 0)
        {
            filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString("yyyyMMdd") + "-" + (existingFileList.Count() - 1).ToString() + ".log";
            FileInfo fi = new FileInfo(filePath);
            if (fi.Length / 1024 > 1000) //Over a MB (ie > 1000 KBs)
            {
                filePath = _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString("yyyyMMdd") + "-" + existingFileList.Count().ToString() + ".log";
            }
        }
        return filePath;
    }

    public static string[] GetTodaysLogFileNames()
    {
        string[] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory, ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString("yyyyMMdd") + "*.log");
        return existingFileList;
    }

    public static void WriteLogActionsToFile()
    {
        string logFilePath = GetLogFileName();
        if (File.Exists(logFilePath)) {
            File.AppendAllLines(logFilePath,_TheUserActions);
        }
        else {
            File.WriteAllLines(logFilePath,_TheUserActions);
        }
        _TheUserActions = new List<string>();
    }
}

Note: The LogAction method will most likely fire 2nd (eg for a Button click, it will be invoked after the Button_Click event has been called). So while you may think you need to insert these LogAction events to fire first for example by reversing the event invocation order that is not good practice and is not required. The trick is in the stacktrace, the last call(s) in the stack will tell you the last user action. The Log of Actions tells you how to get the program in the state before the unhandled exception occurred. Once you get it to that point you need to follow the StackTrace to fault the application.

Putting it in action - for example a MDI Form Load event:

UserActionLog.ActionLog.LogActionSetUp();

In the MDI Forms Close event:

UserActionLog.ActionLog.WriteLogActionsToFile();

In a Child Form Constructor:

_logger = New UserActionLog.ActionLogger(this);

In a Child Form Closed Event:

_logger.ActionLoggerTierDown(this);

In the UIThreadException and CurrentDomain_UnhandledException events call WriteLogActionsToFile(); then attach the logs to the email sent to support with a screenshot...


Here's a quick example on how to get log files emailed to support:

string _errMsg = new System.Text.StringBuilder();
string _caseNumber = IO.Path.GetRandomFileName.Substring(0, 5).ToUpper();
string _errorType;
string _screenshotPath;
List<string> _emailAttachments = new List<string>();
string _userName;

private static void UIThreadException(object sender, ThreadExceptionEventArgs t)
{
 _errorType = "UI Thread Exception"

 ....

//HTML table containing the Exception details for the body of the support email
_errMsg.Append("<table><tr><td colSpan=1><b>User:</b></td><td colSpan=2>" & _userName & "</td></tr>");
_errMsg.Append("<tr><td><b>Time:</b></td><td>" & _errorDateTime.ToShortTimeString & "</td></tr><tr></tr>");
_errMsg.Append("<tr><td><b>Exception Type:</b></td><td>" & _errorType.ToString & "</td></tr><tr></tr>");

if (exception != null) {
    _errMsg.Append("<tr><td><b>Message:</b></td><td>" & exception.Message.Replace(" at ", " at <br>") & "</td></tr><tr></tr>");
    if (exception.InnerException != null) _errMsg.Append("<tr><td><b>Inner Exception:</b></td><td>" & exception.InnerException.Message & "</td></tr>");
    _errMsg.Append("<tr><td><b>Stacktrace:</b></td><td>" & exception.StackTrace & "</td></tr></table>");
}

....

//Write out the logs in memory to file
UserActionLog.ActionLog.WriteLogActionsToFile();

//Get list of today's log files
_emailAttachments.AddRange(UserActionLog.ActionLog.GetTodaysLogFileNames());

//Adding a screenshot of the broken window for support is a good touch
//https://stackoverflow.com/a/1163770/495455
_emailAttachments.Add(_screenshotPath);

....


Email emailSystem = New Email(); //(using Microsoft.Exchange.WebServices.Data)
emailSystem.SendEmail(ConfigMgr.AppSettings.GetSetting("EmailSupport"),  "PROJECT_NAME - PROBLEM CASE ID: " & _caseNumber, _errMsg.ToString(), _emailAttachments.ToArray());

After the email is sent show users a window explaining a problem happened, with a nice picture... StackExchange websites have great example, this is my favorite: https://serverfault.com/error

这篇关于用户活动记录,遥测(和全局异常处理程序中的变量)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆