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

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

问题描述

背景:

我正在处理一个非常旧的应用程序,它很少且非常间歇性地生成异常.

目前的做法:

通常,我们程序员使用全局异常处理程序处理罕见的未知数,连接如下:

[STAThread][SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]私有静态无效 Main(){Application.ThreadException += new ThreadExceptionEventHandler(UIThreadException);Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);AppDomain.CurrentDomain.UnhandledException +=新的 UnhandledExceptionEventHandler(UnhandledException);Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new OldAppWithLotsOfWierdExceptionsThatUsersAlwaysIgnore());}私有静态无效 UIThreadException(对象发送者,ThreadExceptionEventArgs t){//------------------------------ReportToDevelopers("重现问题所需的所有步骤和变量是:" +ShowMeStepsToReproduceAndDiagnoseProblem(t));//------------------------------MessageToUser.Show("不是你,是我们.这是我们的错.
关于这个错误的详细信息已自动记录并通知我们.是的,我们确实会查看每个错误.我们甚至尝试修复他们中的一些.")}私有静态无效 UnhandledException(对象发送者,UnhandledExceptionEventArgs e){//...}

问题域:

很难从用户那里获得重现步骤,而且由于报告的问题数量众多,我还不想继续(第二次机会异常)WinDBG 或 CDB 故障排除路径.我想要一些指标,希望先了解一些最近的 System.Diagnostic 爱.

研究/理解:

很久以前我读过一本书 调试 Microsoft .NET 2.0 应用程序,并讨论了 John Robbins(又名 BugSlayer)编写的一个很酷的工具 SuperAssert.Net

这个工具唯一的缺点是(为了解决问题)内存转储的大小很大,当然调试它们几乎是一门艺术,也是一门科学.

问题:

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

这些天这可能吗?我很容易将 StackTrace 映射回用户操作以制定步骤..我只需要变量!

更新

结果是路由器有问题.

解决方案

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

...

我对此进行了大量研究*.最后,我只是创建了用户所做操作的日志,它只是内存转储大小的一小部分,并且可靠地让我掌握了重现问题的步骤.它还提供了另一个好处,即了解用户如何使用应用程序.

<块引用>

*我真的在网上找不到任何进行这种基本用户活动记录的东西.我发现的一切都是关于 AOP、自动 UI 测试框架或 1/2 Gig 内存转储.

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

<小时>

ActionLogger 类:

公共类 ActionLogger{私有类型_frmType;私人表格_frm;///<总结>///Ctor 连接所有表单控件事件以侦听用户操作的懒惰方式.///</总结>//////<param name="frm">WinForm、WPF、Xamarin 等表单.</param>公共 ActionLogger(控制 frm){_frmType = ((Form)frm).GetType();_frm = (形式)frm;ActionLoggerSetUp(frm);}///<总结>///Ctor 连接控制事件以侦听用户操作的最佳方式.///</总结>公共 ActionLogger(Control[] ctrls){ActionLoggerSetUp(ctrls);}///<总结>///连接所有表单控件事件以侦听用户操作的懒惰方式.///</总结>//////<param name="frm">WinForm、WPF、Xamarin 等表单.</param>public void ActionLoggerSetUp(Control frm){挂钩事件(FRM);//先hook这个控件的事件,然后遍历hook Up它的子控件foreach(frm.Controls 中的 Control ctrl){ActionLoggerSetUp(ctrl);//通过*Form's* child->child->etc控件递归挂接控件事件}}///<总结>///连接控制事件以侦听用户操作的最佳方式.///</总结>///<param name="ctrls">WinForm、WPF、Xamarin等Form上的控件.<param>public void ActionLoggerSetUp(Control[] ctrls){foreach(ctrl 中的 var ctrl){挂钩事件(ctrl);}}///<总结>///释放连接的事件(避免内存泄漏).///</总结>public void ActionLoggerTierDown(Control frm){发布事件(frm);}///<总结>///连接调试问题所需的事件.随意添加更多像 ListView 这样的控件,例如订阅 LogAction() 到更多事件.///</总结>///<param name="ctrl">我们怀疑其事件导致问题的控件.</param>私有无效 HookUpEvents(控制 ctrl){如果(ctrl 是表格){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 代表 ComboBoxes 和 ListBoxes.ListControl lst = ((ListControl)ctrl);lst.SelectedValueChanged += LogAction;}else if (ctrl is ButtonBase) {//ButtonBase 代表按钮、复选框和单选按钮.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;}}///<总结>///释放连接的事件(避免内存泄漏).///</总结>///<param name="ctrl"></param>private void ReleaseEvents(Control ctrl){如果(ctrl 是表格){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 是 DateTimePicker) {DateTimePicker dtp = ((DateTimePicker)ctrl);dtp.Enter -= LogAction;dtp.ValueChanged -= LogAction;}否则如果(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;}}///<总结>///记录进行调用的控件及其值///</总结>///<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))//调整这一行以满足您的需要{//如果它是一个维护表单并且它不在编辑模式下,则不要记录控制事件if (_frmType.BaseType.ToString().Contains("frmMaint")) {//这完全特定于我的项目 - 您需要重写这一行,也可能需要重写上面的行.仅此而已……PropertyInfo pi = _frmType.GetProperty("IsEditing");bool isEditing = (bool)pi.GetValue(_frm, null);如果 (!isEditing) 返回;}}StackTrace stackTrace = new StackTrace();StackFrame[] stackFrames = stackTrace.GetFrames();var eventType = stackFrames[2].GetMethod().Name;//这通常取决于它的第 1 帧,但在这个特定框架 (CSLA) 中它的第 2ActionLog.LogAction(_frm.Name, ((Control)sender).Name, eventType, GetSendingCtrlValue(((Control)sender), eventType));}私有字符串 GetSendingCtrlValue(Control ctrl, string eventType){如果(ctrl 是 TextBoxBase){返回 ((TextBoxBase)ctrl).Text;}//else if (ctrl is CheckBox || ctrl is RadioButton) {//返回 ((ButtonBase)ctrl).Text;//}else if (ctrl is ListControl) {返回 ((ListControl)ctrl).Text.ToString();}else if (ctrl 是 DateTimePicker) {返回 ((DateTimePicker)ctrl).Text;}else if (ctrl 是 DataGridView && eventType == "OnRowEnter"){如果 (((DataGridView)ctrl).SelectedRows.Count > 0) {return ((DataGridView)ctrl).SelectedRows[0].Cells[0].Value.ToString();}别的 {返回字符串.空;}}else if (ctrl 是 DataGridView) {DataGridViewCell 单元格 = (((DataGridView)ctrl).CurrentCell);if (cell == null) return string.Empty;if (cell.Value == null) 返回 string.Empty;返回 cell.Value.ToString();}返回字符串.空;}}

ActionLog 类:

公共静态类 ActionLog{const string ACTIONLOGFILEIDENTIFIER = "ActionLog_";私有静态 int _numberOfDaily = 0;私有静态 int _maxNumerOfLogsInMemory = 512;私有静态列表_TheUserActions = new List();私有静态字符串_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}	{1}	{2}	{3}	{4}", timeStamp.ToShortTimeString(), frmName, ctrlName, eventName, value));if (_TheUserActions.Count > _maxNumerOfLogsInMemory) WriteLogActionsToFile();}公共静态字符串 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";}}返回文件路径;}公共静态字符串[] GetTodaysLogFileNames(){string[] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory, ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString("yyyyMMdd") + "*.log");返回现有文件列表;}public static void WriteLogActionsToFile(){字符串 logFilePath = GetLogFileName();如果(文件.存在(日志文件路径)){File.AppendAllLines(logFilePath,_TheUserActions);}别的 {File.WriteAllLines(logFilePath,_TheUserActions);}_TheUserActions = new List();}}

<块引用>

注意:LogAction 方法很可能会触发第二次(例如,对于 Button 单击,它将在调用 Button_Click 事件后调用).因此,虽然您可能认为您需要插入这些 LogAction 事件以首先触发,例如通过反转事件调用顺序不是好的做法,也不是必需的.诀窍在于堆栈跟踪,堆栈中的最后一个调用将告诉您最后的用户操作.操作日志告诉您如何使程序处于未处理异常发生之前的状态.一旦达到该点,您就需要按照 StackTrace 对应用程序进行故障处理.

付诸行动 - 例如 MDI 表单加载事件:

UserActionLog.ActionLog.LogActionSetUp();

在 MDI 表单关闭事件中:

UserActionLog.ActionLog.WriteLogActionsToFile();

在子表单构造函数中:

_logger = New UserActionLog.ActionLogger(this);

在子窗体关闭事件中:

_logger.ActionLoggerTierDown(this);

UIThreadExceptionCurrentDomain_UnhandledException 事件中调用 WriteLogActionsToFile(); 然后将日志附加到发送给支持的电子邮件中,并附上屏幕截图..

<小时>

以下是关于如何通过电子邮件将日志文件发送给支持人员的快速示例:

string _errMsg = new System.Text.StringBuilder();string _caseNumber = IO.Path.GetRandomFileName.Substring(0, 5).ToUpper();字符串_错误类型;字符串_screenshotPath;列表<字符串>_emailAttachments = new List();字符串_用户名;私有静态无效 UIThreadException(对象发送者,ThreadExceptionEventArgs t){_errorType = "UI 线程异常"....//包含支持电子邮件正文的异常详细信息的 HTML 表_errMsg.Append("<table><tr><td colSpan=1><b>User:</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>");如果(异常!= 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>内部异常:</b></td><td>" & exception.InnerException.消息 & "</td></tr>");_errMsg.Append("<tr><td><b>Stacktrace:</b></td><td>" & exception.StackTrace & "</td></tr></table>");}....//将内存中的日志写到文件中UserActionLog.ActionLog.WriteLogActionsToFile();//获取今天的日志文件列表_emailAttachments.AddRange(UserActionLog.ActionLog.GetTodaysLogFileNames());//添加破窗截图支持是个好办法//https://stackoverflow.com/a/1163770/495455_emailAttachments.Add(_screenshotPath);....电子邮件 emailSystem = 新电子邮件();//(使用 Microsoft.Exchange.WebServices.Data)emailSystem.SendEmail(ConfigMgr.AppSettings.GetSetting("EmailSupport"), "PROJECT_NAME - 问题案例 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.
 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}	{1}	{2}	{3}	{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天全站免登陆