gpt4 book ai didi

c# - 用户事件记录、遥测(和全局异常处理程序中的变量)

转载 作者:太空宇宙 更新时间:2023-11-03 21:21:36 35 4
gpt4 key购买 nike

背景:

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

当前的做法:

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

[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 爱。

研究/理解:

很久以前看过一本书Debugging Microsoft .NET 2.0 Applications它讨论了 John Robbins(又名 The BugSlayer)编写的一个很酷的工具 SuperAssert.Net

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

问题:

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

这几天有可能吗?我很容易将 StackTrace 映射回用户操作以计算出步骤。我只需要变量!

更新

原来是路由器故障。

最佳答案

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

...

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

*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.

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


ActionLogger 类:

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;
}
}

Action 日志类:

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.

将其付诸实践 - 例如 MDI 表单加载事件:

UserActionLog.ActionLog.LogActionSetUp();

在 MDI Forms Close 事件中:

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();
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());

电子邮件发送后,向用户显示一个窗口,解释发生的问题,并附有漂亮的图片...StackExchange 网站有很好的示例,这是我的最爱:https://serverfault.com/error

关于c# - 用户事件记录、遥测(和全局异常处理程序中的变量),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30326673/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com