Регистрация активности пользователя, телеметрия (и переменные в глобальных обработчиках исключений)

Задний план:

Я имею дело с очень старым приложением, которое генерирует Исключения довольно редко и очень прерывисто.

Текущая практика:

Обычно мы, программисты, имеем дело с редкими неизвестными, использующими обработчики Global Exception, проводя что-то вроде этого:

[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) { //... } 

Проблемная область:

Его трудно получить шаги воспроизведения от пользователей и из-за разнообразного количества сообщений, о которых сообщается. Я еще не хочу спускать пути устранения неполадок (исключение из второго шанса) WinDBG или CDB. Я хочу некоторые показатели и, надеюсь, недавнюю любовь System.Diagnostic.

Исследования / Понимание:

Давным-давно я прочитал книгу « Отладка приложений Microsoft .NET 2.0», и в ней обсуждается отличный инструмент, который Джон Роббинс (он же The BugSlayer) написал SuperAssert.Net

Единственным недостатком этого инструмента является (для решения проблем) размер дампов памяти огромен, и, конечно, отладка их почти такая же, как искусство, как наука.

Вопрос:

Я надеюсь, кто-то скажет мне, как я могу выгрузить переменные в этой программе, ну, по крайней мере, на последнем этапе приложений Exception.StackTrace .

Возможно ли это в наши дни? Для меня достаточно легко отобразить StackTrace для действий пользователя, чтобы выработать этапы. Мне просто нужны переменные!

Обновить

Оказалось, что это ошибочный маршрутизатор.

Проект с открытым исходным кодом теперь находится на GitHub: https://github.com/MeaningOfLights/UserActionLog

Я сделал ОГРОМНОЕ количество исследований в этом *. В конце я только что создал журнал того, что делает пользователь, его доля от размера дампа памяти и надежно получает мне шаги для воспроизведения проблем. Это также служит еще одной выгоде, понимая, как пользователи используют приложение.

* Я серьезно не смог найти что-либо в Интернете, которое выполняет эту основную регистрацию активности пользователей. Все, что я нашел, это AOP, Auto UI Testing Frameworks или дампы памяти 1/2 Gig.

Для вашего удобства это доброта!


Класс ActionLogger:

 public class ActionLogger { private Type _frmType; private Form _frm; ///  /// Ctor Lazy way of hooking up all form control events to listen for user actions. ///  /// /// The WinForm, WPF, Xamarin, etc Form. public ActionLogger(Control frm) { _frmType = ((Form)frm).GetType(); _frm = (Form)frm; ActionLoggerSetUp(frm); } ///  /// Ctor Optimal way of hooking up control events to listen for user actions. ///  public ActionLogger(Control[] ctrls) { ActionLoggerSetUp(ctrls); } ///  /// Lazy way of hooking up all form control events to listen for user actions. ///  /// /// The WinForm, WPF, Xamarin, etc Form. 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 } } ///  /// Optimal way of hooking up control events to listen for user actions. ///  /// The controls on the WinForm, WPF, Xamarin, etc Form. public void ActionLoggerSetUp(Control[] ctrls) { foreach (var ctrl in ctrls) { HookUpEvents(ctrl); } } ///  /// Releases the hooked up events (avoiding memory leaks). ///  public void ActionLoggerTierDown(Control frm) { ReleaseEvents(frm); } ///  /// Hooks up the event(s) needed to debug problems. Feel free to add more Controls like ListView for example subscribe LogAction() to more events. ///  /// The control whose events we're suspicious of causing problems. 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; } } ///  /// Releases the hooked up events (avoiding memory leaks). ///  ///  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; } } ///  /// Log the Control that made the call and its value ///  ///  ///  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:

 public static class ActionLog { const string ACTIONLOGFILEIDENTIFIER = "ActionLog_"; private static int _numberOfDaily = 0; private static int _maxNumerOfLogsInMemory = 512; private static List _TheUserActions = new List(); 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(); } } 

Примечание. Метод LogAction, скорее всего, сработает 2-й (например, для нажатия кнопки, он будет вызываться после вызова события Button_Click). Поэтому, хотя вы можете подумать, что вам нужно вставить эти события LogAction, чтобы запустить их, например, путем изменения порядка вызова события, который не является хорошей практикой и не требуется. Фокус в стеке, последний вызов (ы) в стеке скажет вам последнее действие пользователя . Журнал действий сообщает вам, как получить программу в состоянии до возникновения необработанного исключения. Как только вы доберетесь до этого момента, вам нужно будет следовать за StackTrace, чтобы вызвать вину приложения.

Ввод его в действие – например, событие загрузки формы MDI:

 UserActionLog.ActionLog.LogActionSetUp(); 

В событии MDI Forms Close:

 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; List _emailAttachments = new List(); 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(""); _errMsg.Append(""); _errMsg.Append(""); if (exception != null) { _errMsg.Append(""); if (exception.InnerException != null) _errMsg.Append(""); _errMsg.Append("
User:" & _userName & "
Time:" & _errorDateTime.ToShortTimeString & "
Exception Type:" & _errorType.ToString & "
Message:" & exception.Message.Replace(" at ", " at
") & "
Inner Exception:" & exception.InnerException.Message & "
Stacktrace:" & exception.StackTrace & "
"); } .... //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());

После того, как отправлено сообщение электронной почты, вы увидите окно с описанием проблемы, с красивым рисунком … У сайтов StackExchange отличный пример, это мой любимый: https://serverfault.com/error

  • Отладка Android с помощью Logcat и эмулятора. Является ли это возможным?
  • Перечислите или перечислите все переменные в программе
  • Как исправить «Точка останова в настоящее время не будет удалена. Для этого документа не были загружены символы. "Предупреждение?
  • Есть ли разница в производительности между Javac debug on и off?
  • Android Studio IDE: перерыв на исключении
  • Как отлаживать веб-сайты на мобильных устройствах?
  • addr2line на модуле ядра
  • Как отлаживать реальное устройство (используя Eclipse / ADT)
  • Как увидеть, какие плагины делают Vim медленным?
  • Различия в производительности между assemblyми отладки и выпуска
  • Как установить условную точку останова в Xcode на основе свойства объекта?
  • Interesting Posts

    Как заблокировать сеанс Windows 7 с помощью простых нажатий клавиш?

    Зачем включать охранников?

    Пользовательский макет для DialogFragment OnCreateView и OnCreateDialog

    Как использовать Android ProgressBar в детерминированном режиме?

    Как узнать, сколько строк кода есть в проекте Xcode?

    Как я могу получить количество строк в файле эффективным способом?

    Android N требует, чтобы IDE работала с Java 1.8 или новее?

    Почему сохранение изменений в базе данных не удается?

    Разница между светодиодной подсветкой и светодиодным дисплеем HD на мониторе ноутбука

    Получение ScriptControl для работы с Excel 2010 x64

    Каковы все функции-члены, созданные компилятором для classа? Это происходит постоянно?

    Объект classа универсального classа (java)

    PuTTY: очистка прокрутки от командной строки

    ПК: Quick Freeze, затем BSOD, затем принудительная перезагрузка, затем снова замерзает

    Как легко использовать расширенные параметры поиска Windows 7?

    Давайте будем гением компьютера.