用户活动日志 - 看到变量的全局异常处理 [英] User Activity Logging - see Variables in a Global Exception Handler

查看:207
本文介绍了用户活动日志 - 看到变量的全局异常处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:

我处理的一个非常旧的应用程序产生异常非常罕见,非常间歇性。

现行做法:

通常情况下,我们的程序员应对罕见的未知使用全局异常处理程序,连接最多是这样的:

  [STAThread]
[的SecurityPermission(SecurityAction.Demand,旗帜= SecurityPermissionFlag.ControlAppDomain)
私有静态无效的主要()
{
    Application.ThreadException + =新ThreadExceptionEventHandler(UIThreadException);
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
    AppDomain.CurrentDomain.UnhandledException + =
        新UnhandledExceptionEventHandler(UnhandledException);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(假);
    Application.Run(新OldAppWithLotsOfWierdExceptionsThatUsersAlwaysIgnore());
}

私有静态无效UIThreadException(对象发件人,ThreadExceptionEventArgs T)
{
    // -------------------------------
    ReportToDevelopers(所有的步骤和放大器;你需要瑞普问题变量是:+
    ShowMeStepsToReproduceAndDiagnoseProblem(吨));
    // -------------------------------

    MessageToUser.Show(这不是你,这是我们的。这是我们的错。\ r \ñ有关此错误的详细信息已经被自动记录下来,我们一直在notified.Yes,我们就来看看每一个错误。我们甚至尝试修复他们中有一些。)
}

私有静态无效UnhandledException(对象发件人,UnhandledExceptionEventArgs E)
{
    // ...
}
 

问题域:

它很难获得的,因为不同的量的问题被报道,我不想下去(第二次机会异常)的用户和再现步骤的WinDBG或国开行故障排除的路径,只是还没有。我想要一些指标,并希望最近的一些System.Diagnostic初恋。

研究/谅解:

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

的唯一缺点这个工具是(为了制定出的问题)的内存转储的大小是巨大的,当然和调试他们几乎是作为一门艺术,因为它是一门科学。

问:

我希望有人能告诉我一个方法可以让我倾倒出来的变量此计划,以及至少一个在应用程序的最后一步 Exception.StackTrace

这是在所有可能的这些日子?它很容易让我的堆栈跟踪映射回用户的行为制定出的步骤..的我只需要变量!

解决方案

我做研制的巨额这个*。最后,我刚刚创建了一个记录用户做什么,内存转储的大小,它的一小部分,可靠地让我的步骤来重现问题。它还有另一个好处,了解用户如何使用该应用程序。

  

*我认真找不到任何网上做这个基本的用户活动日志。一切我发现大约AOP,自动UI测试框架或1/2千兆内存转储。

为了您的方便这里是善良!


ActionLogger类:

 公共类ActionLogger
{
    私有类型_frmType;
    私人形式_frm;
    ///<总结>
    ///挂钩所有的表单控件事件侦听用户操作的构造函数偷懒的方法。
    ///< /总结>
    ///< PARAM NAME =FRM>在WinForm的,WPF,Xamarin等形式< /参数>
    公共ActionLogger(控制FRM)
    {
        _frmType =((表格)FRM).GetType();
        _frm =(表)FRM;
        ActionLoggerSetUp(FRM);
    }

    ///<总结>
    ///挂钩控件事件侦听用户操作的构造函数的优化方式。
    ///< /总结>
    公共ActionLogger(控制[] ctrls监视)
    {
        ActionLoggerSetUp(ctrls监视);
    }

    ///<总结>
    挂钩所有的表单控件事件侦听用户行为///偷懒的方法。
    ///< /总结>
    ///< PARAM NAME =FRM>在WinForm的,WPF,Xamarin等形式< /参数>
    公共无效ActionLoggerSetUp(控制FRM)
    {
        HookUpEvents(FRM); //第一钩了这个控件的事件,然后traversely挂钩的儿童
        的foreach(在frm.Controls控制CTRL){
            ActionLoggerSetUp(CTRL);儿童期>通过*表格的*儿童为&GT //递归挂钩控件事件等控制
        }
    }

    ///<总结>
    ///挂钩控件事件侦听用户操作的最佳方式。
    ///< /总结>
    ///< PARAM NAME =ctrls监视>参数>在在WinForm,WPF,Xamarin等形式与所述的控制;
    公共无效ActionLoggerSetUp(控制[] ctrls监视)
    {
        的foreach(在ctrls监视VAR CTRL){
            HookUpEvents(CTRL);
        }
    }

    ///<总结>
    ///发布的迷上了事件(避免内存泄漏)。
    ///< /总结>
    公共无效ActionLoggerTierDown(控制FRM)
    {
        ReleaseEvents(FRM);
    }

    ///<总结>
    ///挂接需要调试问题的情况下(S)。随意添加更多的控件,如ListView控件,例如订阅LogAction(),以更多的事件。
    ///< /总结>
    ///< PARAM NAME =CTRL>在控制的事件我们怀疑导致问题和LT; /参数>
    私人无效HookUpEvents(控制CTRL)
    {
        如果(CTRL为表){
            表FRM =((表格)CTRL);
            frm.Load + = LogAction;
            frm.FormClosed + = LogAction;
            frm.ResizeBegin + = LogAction;
            frm.ResizeEnd + = LogAction;
        }
        否则,如果(CTRL是TextBoxBase){
            TextBoxBase TXT =((TextBoxBase)CTRL);
            txt.Enter + = LogAction;
        }
        否则,如果(CTRL为列表控件){//列表控件代表组合框和列表框。
            列表控件LST =((列表控件)CTRL);
            lst.SelectedValueChanged + = LogAction;
        }
        否则,如果(CTRL是ButtonBase){// ButtonBase代表按钮,复选框和单选按钮。
            ButtonBase BTN =((ButtonBase)CTRL);
            btn.Click + = LogAction;
        }
        否则,如果(CTRL是的DateTimePicker){
            的DateTimePicker DTP =((的DateTimePicker)CTRL);
            dtp.Enter + = LogAction;
            dtp.ValueChanged + = LogAction;
        }
        否则,如果(CTRL为DataGridView中){
            DataGridView的DGV =((DataGridView中)CTRL);
            dgv.RowEnter + = LogAction;
            dgv.CellBeginEdit + = LogAction;
            dgv.CellEndEdit + = LogAction;
        }
    }

    ///<总结>
    ///发布的迷上了事件(避免内存泄漏)。
    ///< /总结>
    ///< PARAM NAME =CTRL与GT;< /参数>
    私人无效ReleaseEvents(控制CTRL)
    {
        如果(CTRL为表){
            表FRM =((表格)CTRL);
            frm.Load  -  = LogAction;
            frm.FormClosed  -  = LogAction;
            frm.ResizeBegin  -  = LogAction;
            frm.ResizeEnd  -  = LogAction;
        }
        否则,如果(CTRL是TextBoxBase){
            TextBoxBase TXT =((TextBoxBase)CTRL);
            txt.Enter  -  = LogAction;
        }
        否则,如果(CTRL为列表控件){
            列表控件LST =((列表控件)CTRL);
            lst.SelectedValueChanged  -  = LogAction;
        }
        否则,如果(CTRL是的DateTimePicker){
            的DateTimePicker DTP =((的DateTimePicker)CTRL);
            dtp.Enter  -  = LogAction;
            dtp.ValueChanged  -  = LogAction;
        }
        否则,如果(CTRL是ButtonBase){
            ButtonBase BTN =((ButtonBase)CTRL);
            btn.Click  -  = LogAction;
        }
        否则,如果(CTRL为DataGridView中){
            DataGridView的DGV =((DataGridView中)CTRL);
            dgv.RowEnter  -  = LogAction;
            dgv.CellBeginEdit  -  = LogAction;
            dgv.CellEndEdit  -  = LogAction;
        }
    }

    ///<总结>
    ///日志发出的呼吁,并将其值控制
    ///< /总结>
    ///< PARAM NAME =发件人>< /参数>
    ///< PARAM NAME =E>< /参数>
    公共无效LogAction(对象发件人,EventArgs的)
    {
        如果(!(发件人为表||发件人是ButtonBase ||发件人的DataGridView))//裁缝这一行,以满足您的需求
        {//不记录控制事件,如果它是一个维护形式和它不是在编辑模式
            如果(_frmType.BaseType.ToString()包含(frmMaint)){//这是严格具体到我的项目 - 你将需要重写这一行,并尽可能线之上了。这一切虽然...
                的PropertyInfo PI = _frmType.GetProperty(IsEditing);
                布尔isEditing =(布尔)pi.GetValue(_frm,NULL);
                如果(isEditing!)回报;
            }
        }
        堆栈跟踪堆栈跟踪=新的堆栈跟踪();
        的StackFrame [] stackFrames = stackTrace.GetFrames();
        VAR EVENTTYPE = stackFrames [2] .GetMethod()名称; //这通常取决于它的第一帧,但在这个特殊的架构(CSLA)的2
        ActionLog.LogAction(_frm.Name,((控制)发送方).name和事件类型,GetSendingCtrlValue(((控制)发送者),EVENTTYPE));
    }

    私人字符串GetSendingCtrlValue(控制CTRL,串EVENTTYPE)
    {
        如果(CTRL是TextBoxBase){
            返回((TextBoxBase)CTRL)。文;
        }
        //否则,如果(CTRL是复选框|| CTRL是单选){
        //返回((ButtonBase)CTRL)。文;
        //}
        否则,如果(CTRL为列表控件){
            返回((列表控件)CTRL).Text.ToString();
        }
        否则,如果(CTRL是的DateTimePicker){
            返回((的DateTimePicker)CTRL)。文;
        }
        否则,如果(CTRL是DataGridView的&放大器;&安培; EVENTTYPE ==OnRowEnter)
        {
            如果(((DataGridView中)CTRL).SelectedRows.Count大于0){
                返程((DataGridView中)CTRL).SelectedRows [0] .Cells [0] .Value.ToString();
            }
            其他 {
                返回的String.Empty;
            }
        }
        否则,如果(CTRL为DataGridView中){
            DataGridViewCell的细胞=(((DataGridView中)CTRL).CurrentCell);
            如果(细胞== NULL)返回的String.Empty;
            如果(cell.Value == NULL)返回的String.Empty;
            返回cell.Value.ToString();
        }
        返回的String.Empty;
    }
}
 

ActionLog类:

 公共静态类ActionLog
{
    常量字符串ACTIONLOGFILEIDENTIFIER =ActionLog_;
    私有静态诠释_numberOfDaily = 0;
    私有静态诠释_maxNumerOfLogsInMemory = 512;
    私有静态列表<字符串> _TheUserActions =新的名单,其中,串>();
    私人静态字符串_actionLoggerDirectory =的String.Empty;

    公共静态无效LogActionSetUp(INT maxNumerOfLogsInMemory = 512,串actionLoggerDirectory =)
    {
        如果(string.IsNullOrEmpty(actionLoggerDirectory))actionLoggerDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)+\\文档\\ ProjectNameMgtFolder \\;
        如果(!Directory.Exists(actionLoggerDirectory))Directory.CreateDirectory(actionLoggerDirectory);

        _actionLoggerDirectory = actionLoggerDirectory;

        LogAction(MDI_Form,应用,启动,的String.Empty);
    }

    公共静态无效LogAction(字符串frmName,串ctrlName,串eventName的,字符串值)
    {
        如果(value.Length→10)值= value.Substring(0,10);
        LogAction(DateTime.Now,frmName,ctrlName,eventName的,价值);
    }

    公共静态无效LogAction(DateTime时间戳,串frmName,串ctrlName,串eventName的,字符串值)
    {
        _TheUserActions.Add(的String.Format({0} \ t {1} \ t {2} \ t {3} \ t {4},timeStamp.ToShortTimeString(),frmName,ctrlName,eventName的,值));
        如果(_TheUserActions.Count> _maxNumerOfLogsInMemory)WriteLogActionsToFile();
    }

    公共静态字符串GetLogFileName()
    {
        //检查当前文件> 1 MB并创建另一个
        字符串[] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory,ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(年月日)+* .LOG);

        字符串的文件路径= _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(年月日)+-0.log;
        如果(existingFileList.Count()大于0)
        {
            文件路径= _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(年月日)+ - +(existingFileList.Count() -  1)的ToString()+.LOG;
            FileInfo的F1 =新的FileInfo(文件路径);
            如果(fi.Length / 1024> 1000)//在一个MB(即> 1000 KB的)
            {
                文件路径= _actionLoggerDirectory + ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(年月日)+ - + existingFileList.Count()的ToString()+.LOG。
            }
        }
        返回文件路径;
    }

    公共静态字符串[] GetTodaysLogFileNames()
    {
        字符串[] existingFileList = System.IO.Directory.GetFiles(_actionLoggerDirectory,ACTIONLOGFILEIDENTIFIER + DateTime.Now.ToString(年月日)+* .LOG);
        返回existingFileList;
    }

    公共静态无效WriteLogActionsToFile()
    {
        字符串LOGFILEPATH = GetLogFileName();
        如果(File.Exists(LOGFILEPATH)){
            File.AppendAllLines(LOGFILEPATH,_TheUserActions);
        }
        其他 {
            File.WriteAllLines(LOGFILEPATH,_TheUserActions);
        }
        _TheUserActions =新的名单,其中,串>();
    }
}
 

  

注:LogAction方法,将最有可能起火2日(如点击一个按钮,它会调用Button_Click事件被调用后)。因此,尽管你可能会认为你需要将这些LogAction事件由先触发,例如倒车时调用顺序的<一个HREF =htt​​p://stackoverflow.com/a/374549/495455>不是好的做法并不是必需的。 的诀窍是在堆栈跟踪,最后调用堆栈中的(S)会告诉你最后一次用户操作。操作的日志告诉你如何让程序在该州未处理的异常发生之前。一旦你得到它的时候,你需要遵循的堆栈跟踪故障的应用程序。

把它在行动 - 例如MDI窗体Load事件:

  UserActionLog.ActionLog.LogActionSetUp();
 

在MDI窗体关闭事件:

  UserActionLog.ActionLog.WriteLogActionsToFile();
 

在子窗体构造器:

  _logger =新UserActionLog.ActionLogger(本);
 

在子窗体关闭事件:

  _logger.ActionLoggerTierDown(本);
 

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


下面是关于如何获取日志文件一个简单的例子通过电子邮件发送给支持:

 字符串_errMsg =新System.Text.StringBuilder();
串_caseNumber = IO.Path.GetRandomFileName.Substring(0,5).ToUpper();
串_errorType;
串_screenshotPath;
名单&LT;字符串&GT; _emailAttachments =新的名单,其中,串&GT;();
串_userName;

私有静态无效UIThreadException(对象发件人,ThreadExceptionEventArgs T)
{
 _errorType =UI线程异常

 ....

// HTML表格包含了异常详细的支持电子邮件的正文
_errMsg.Append(&LT;表&gt;&LT; TR&GT;&LT; TD COLSPAN = 1&GT;&LT; B&gt;用户:&LT; / B&GT;&LT; / TD&GT;&LT; TD COLSPAN = 2&gt;中和放大器; _userName&安培; &所述; / TD&GT;&所述; / TR&gt;中);
_errMsg.Append(&其中; TR&GT;&其中; TD&GT;&其中b取代;时间:&l​​t; / B个;&所述; / TD&GT;&其中; TD&gt;中&安培; _errorDateTime.ToShortTimeString&安培;&所述; / TD&GT;&其中; / TR&GT;&其中; TR&GT;&所述; / TR&gt;中);
_errMsg.Append(&其中; TR&GT;&其中; TD&GT;&其中b取代;异常类型:其中; / B个;&所述; / TD&GT;&其中; TD&gt;中&安培; _errorType.ToString&安培;&所述; / TD&GT;&其中; / TR&GT;&其中; TR&GT;&所述; / TR&gt;中);

如果(例外!= NULL){
    _errMsg.Append(&其中; TR&GT;&其中; TD&GT;&其中b取代;消息:其中; / B个;&所述; / TD&GT;&其中; TD&gt;中&安培; exception.Message.Replace(在,在与所述; BR&gt;中)及&所述; / TD&GT;&所述; / TR&GT;&其中; TR&GT;&所述; / TR&gt;中);
    如果(exception.InnerException!= NULL)_errMsg.Append(&LT; TR&GT;&LT; TD&GT;&LT; B&GT;内部异常:LT; / B&GT;&LT; / TD&GT;&LT; TD&gt;中和放大器; exception.InnerException。消息和;&所述; / TD&GT;&所述; / TR&gt;中);
    _errMsg.Append(&其中; TR&GT;&其中; TD&GT;&其中b取代;堆栈跟踪:其中; / B个;&所述; / TD&GT;&其中; TD&gt;中&安培; exception.StackTrace&安培;&所述; / TD&GT;&其中; / TR&GT;&LT; /表&gt;);
}

....

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

//获取今天的日志文件列表
_emailAttachments.AddRange(UserActionLog.ActionLog.GetTodaysLogFileNames());

//添加破窗支持的截图是一个很好的触摸
//http://stackoverflow.com/a/1163770/495455
_emailAttachments.Add(_screenshotPath);

....


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

在电子邮件发送给用户演示一个窗口说明一个问题发生了,用一个漂亮的图片... StackExchange网站都有很好的例子,这是我最喜欢的:的 http://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!

解决方案

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
//http://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: http://serverfault.com/error

这篇关于用户活动日志 - 看到变量的全局异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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