65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET MVC 中的带有请求详细信息的异常日志

2014年7月17日

CPOL

3分钟阅读

viewsIcon

30281

downloadIcon

436

ASP.NET MVC 中带有请求详细信息的异常日志

引言

异常是 Web 项目中常见的问题。异常日志记录器对于跟踪此类异常以提高解决方案的质量非常重要。但在我的情况下,我希望跟踪比仅仅堆栈跟踪更多的额外信息。因此,在这里,我们将看到如何在 ASP.NET Web 服务中跟踪整个 Web 请求对象。

背景

当我第一次撰写关于记录传递给 C# 中某个方法的所有参数时,这个想法就开始了。但我希望避免使用大量 try/catch 块,并使用更集中的代码。所以,让我们看看如何在 ASP.NET MVC 中做到这一点。

假设我们想收集错误数据,如下所示

public class ErrorModel
{
    /*RouteDetail*/
    public string ControllerName { get; set; }
    public string ActionName { get; set; }

    /*SessionDetail*/
    public bool? HasSession { get; set; }
    public string Session { get; set; }

    /*RequestDetail*/
    public Dictionary<string, string> RequestHeader { get; set; }
    public string RequestData { get; set; }

    /*ExceptionDetail*/
    public Exception Exception { get; set; }
    public DateTime CreatedDateTime { get; set; }
}

我们可以在哪里找到错误?

  • 实际应用
  • 在所有操作的控制器中
  • 对于所有控制器操作,使用基控制器
  • 对于整个应用程序

实际应用

这是一个简单的 try/catch 块。异常将在 catch 范围内找到。

public ActionResult Create(string name)
{
    try
    {
        throw new Exception("Error to Reset.");
    }
    catch (Exception exception)
    {
        //Here is the exception
        var aException = exception;
        throw new Exception("Error.", exception);
    }
}

在控制器中,针对其所有操作

在控制器上使用 OnException 意味着每当控制器的任何操作抛出异常时,都会每次触发。

protected override void OnException(ExceptionContext filterContext)
{
    //Here is the exception
    var aException = filterContext.Exception;
    base.OnException(filterContext);
}

对于所有控制器操作,使用基控制器

像我在ASP.NET MVC 中的会话中所做的那样,为项目的每个控制器使用一个基控制器,并将 OnException 放置在 BaseController 中,这意味着当在 SubControllers 的任何操作中找到错误时,将触发 BaseControllerOnException

对于整个应用程序

Global.asax 中添加 Application_Error 方法

protected void Application_Error(Object sender, EventArgs e)
{
    //Here is the exception
    Exception aException = HttpContext.Current.Server.GetLastError();
    //Or
    var httpContext = ((MvcApplication)sender).Context;
    aException = httpContext.Server.GetLastError();
}

我们还需要什么?

  • Session 对象
  • HttpRequest 对象

Session 对象

拥有一个 session 对象并不重要。日志实用程序也可以在没有会话的情况下创建日志。但重要的是要知道在抛出异常时用户是否已登录。

让我们在控制器中添加一个内联 session 对象,如下所示

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);
    Session["logOnUserId"] = "User-101";
}

HttpRequest 对象

可能来自任何浏览器或通过任何请求。在这里,我们可以使用其中任何一个来我们的错误记录器实用程序。

HttpRequest request = System.Web.HttpContext.Current.Request;   
HttpRequestBase requestBase = new HttpRequestWrapper(request);     //this one is important

让我们开始构建错误日志实用程序

这是 Extensions 方法,它将处理 HttpRequest 对象,以跟踪请求头和参数。

public static class HttpRequestExtensions
{
    public static string ParamsToString(this HttpRequestBase request)
    {
        request.InputStream.Seek(0, SeekOrigin.Begin);
        return new StreamReader(request.InputStream).ReadToEnd();
    }

    public static Dictionary<string, string> ToRaw(this HttpRequest request)
    {
        return new HttpRequestWrapper(request).ToRaw();
    }

    public static Dictionary<string, string> ToRaw(this HttpRequestBase requestBase)
    {
        Dictionary<string, string> writer = new Dictionary<string, string>();
        WriteStartLine(requestBase, ref writer);
        WriteHeaders(requestBase, ref writer);
        WriteBody(requestBase, ref writer);
        return writer;
    }

    private static void WriteStartLine(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        writer.Add("HttpMethod", request.HttpMethod);
        writer.Add("Url", request.Url.ToString());
        writer.Add("ServerVariables", request.ServerVariables["SERVER_PROTOCOL"]);
    }

    private static void WriteHeaders(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        foreach (string key in request.Headers.AllKeys)
        {
            writer.Add(key, request.Headers[key]);
        }
    }

    private static void WriteBody(HttpRequestBase request, ref Dictionary<string, string> writer)
    {
        StreamReader reader = new StreamReader(request.InputStream);
        try
        {
            string body = reader.ReadToEnd();
            writer.Add("Body", body);
        }
        finally
        {
            reader.BaseStream.Position = 0;
        }
    }
} 

这是 Utility 类,它为我们提供了 ErrorModel

public class ErrorLogUtility
{
    public readonly HttpRequestBase RequestBase;

    public ErrorLogUtility(HttpRequestBase requestBase)
    {
        if (requestBase == null)
        {
            throw new NullReferenceException("requestBase is null at ErrorLogUtility.");
        }
        RequestBase = requestBase;
    }

    public ErrorLogUtility(HttpRequest request)
        : this(new HttpRequestWrapper(request))
    {
    }

    public ErrorModel GetErrorModel(Exception exception)
    {
        var errorModel = new ErrorModel();
        SetRouteDetail(ref errorModel);
        SetRequestDetail(ref errorModel);
        SetExceptionDetail(ref errorModel, exception);
        return errorModel;
    }

    public ErrorModel GetErrorModel(Exception exception, object session)
    {
        var errorModel = GetErrorModel(exception);
        SetSessionDetail(ref errorModel, session);
        return errorModel;
    }

    private void SetRequestDetail(ref ErrorModel errorModel)
    {
        errorModel.RequestHeader = RequestBase.ToRaw();
        errorModel.RequestData = RequestBase.ParamsToString();
    }

    private void SetRouteDetail(ref ErrorModel errorModel)
    {
        Func<string, string> routeDataValue = delegate(string indexName)
        {
            bool hasValue = RequestBase.RequestContext.RouteData.Values[indexName] != null;
            return hasValue ? RequestBase.RequestContext.RouteData.Values[indexName].ToString() : "";
        };
        errorModel.ControllerName = routeDataValue("controller");
        errorModel.ActionName = routeDataValue("action");
    }

    private void SetExceptionDetail(ref ErrorModel errorModel, Exception exception)
    {
        errorModel.Exception = exception;
        errorModel.CreatedDateTime = DateTime.Now;
    }

    private void SetSessionDetail(ref ErrorModel errorModel, object session)
    {
        errorModel.HasSession = session != null;
        errorModel.Session = (session != null)? session.ToString() : "";
    }
}

Using the Code

在控制器中,针对其所有操作/对于所有控制器操作,使用基控制器

/*take log*/
protected override void OnException(ExceptionContext filterContext)
{
    //HttpRequest or HttpRequestBase is required in the constructor
    var errorModelUsingRequestBase = new ErrorLogUtility
    (filterContext.RequestContext.HttpContext.Request).GetErrorModel(filterContext.Exception);
    var errorModelUsingRequest = new ErrorLogUtility
    (System.Web.HttpContext.Current.Request).GetErrorModel(filterContext.Exception);
    
    //include session in error model
    var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                    .GetErrorModel(filterContext.Exception, Session["logOnUserId"]);
    base.OnException(filterContext);
}

对于整个应用程序

protected void Application_Error(Object sender, EventArgs e)
{
    /*take log*/
    var httpContext = ((MvcApplication)sender).Context;
    Exception exception = HttpContext.Current.Server.GetLastError();            
    //or
    //exception = httpContext.Server.GetLastError();

    //HttpRequest or HttpRequestBase is required at contructor
    var errorModelUsingRequestBase = new ErrorLogUtility(httpContext.Request).GetErrorModel(exception);
    var errorModelUsingRequest = new ErrorLogUtility
    (System.Web.HttpContext.Current.Request).GetErrorModel(exception);

    //include session in error model
    var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                    .GetErrorModel(exception, Session["logOnUserId"]);
}

实际应用

/*take log*/
public ActionResult Create(string name)
{
    try
    {
        throw new Exception("Error to Reset.");
    }
    catch (Exception exception)
    {
        //HttpRequest is required at contructor
        var errorModelUsingRequest = new ErrorLogUtility
        (System.Web.HttpContext.Current.Request).GetErrorModel(exception);
        //include session in error model
        var errorModelWithSession = new ErrorLogUtility(System.Web.HttpContext.Current.Request)
                                        .GetErrorModel(exception, Session["logOnUserId"]);
        throw new Exception("Error.", exception);
    }
}

限制

  1. 我还在处理它。也可能有一些错误。如果您发现任何错误,请告诉我。
  2. 如果您使用自定义的会话模型,请确保覆盖 ToString 方法。
    class Person
    {
        public long Id { get; set; }
        public string Name { get; set; }
    
        public override string ToString()
        {
            /*your value*/
            return string.Format("Id: {0}, Name: {1}", Id, Name);
        }
    }
  3. 尽量避免对特定异常重复使用此日志记录过程。(在操作/在控制器/基控制器/global.asax)一次。
  4. 看看评论部分 "一个好主意,关于实现的几点评论",其中John Brett指出了一些好东西。

示例 JSONs

在这里,我已将 ErrorModel 转换为 json。

正如您所见

  • RequestData 指向找到此类异常的数据。
  • RequestHeader 也有一些额外的信息。
{
  "ControllerName": "InControllerLog",
  "ActionName": "Edit",
  "HasSession": true,
  "Session": "User-101",
  "RequestHeader": {
    "HttpMethod": "POST",
    "Url": "https://:1999/InControllerLog/Edit",
    "ServerVariables": "HTTP/1.1",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Pragma": "no-cache",
    "Content-Length": "143",
    "Content-Type": "application/json; charset=utf-8",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en-US,en;q=0.5",
    "Cookie": "ASP.NET_SessionId=xs32x1xeelllzuycaz01lpn3",
    "Host": "localhost:1999",
    "Referer": "https://:1999/InControllerLog",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
    "X-Requested-With": "XMLHttpRequest",
    "Body": ""
  },
  "RequestData": "{\"id\":1,
  \"student\":{\"id\":1,\"name\":\"Student_1\"},
  \"subjects\":[{\"id\":1,\"name\":\"Subject_1\"},
  {\"id\":2,\"name\":\"Subject_2\"},{\"id\":3,
  \"name\":\"Subject_3\"}]}",
  "Exception": {
    "ClassName": "System.Exception",
    "Message": "Error to Edit.",
    "Data": {},
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "   at ErrorRequestLog.Controllers.InControllerLogController.Edit
    (UInt64 id, Student student, List`1 subjects) in 
    c:\\Users\\Dipon Roy\\Desktop\\ErrorRequestLog\\ErrorRequestLog\\ErrorRequestLog\\Controllers\\
    InControllerLogController.cs:line 35\r\n   at lambda_method(Closure , ControllerBase , 
    Object[] )\r\n   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, 
    Object[] parameters)\r\n   at System.Web.Mvc.ReflectedActionDescriptor.Execute
    (ControllerContext controllerContext, IDictionary`2 parameters)\r\n   at 
    System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, 
    ActionDescriptor actionDescriptor, IDictionary`2 parameters)\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.
    <BeginInvokeSynchronousActionMethod>b__41()\r\n   at System.Web.Mvc.Async.AsyncResultWrapper.
    <>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)\r\n   
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()\r\n   at 
    System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod
                          (IAsyncResult asyncResult)\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.
    <BeginInvokeActionMethodWithFilters>b__33()\r\n   
                   at System.Web.Mvc.Async.AsyncControllerActionInvoker.
    <>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.
    <BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult)\r\n   
    at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters
    (IAsyncResult asyncResult)\r\n   at System.Web.Mvc.Async.AsyncControllerActionInvoker.
    <>c__DisplayClass25.<>c__DisplayClass2a.<BeginInvokeAction>b__20()\r\n   
    at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.
    <BeginInvokeAction>b__22(IAsyncResult asyncResult)",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": "8\nEdit\nErrorRequestLog, Version=1.0.0.0, 
    Culture=neutral, PublicKeyToken=null\nErrorRequestLog.Controllers.InControllerLogController
    \nSystem.Web.Mvc.JsonResult Edit(UInt64, ErrorRequestLog.Model.Student, 
    System.Collections.Generic.List`1[ErrorRequestLog.Model.Subject])",
    "HResult": -2146233088,
    "Source": "ErrorRequestLog",
    "WatsonBuckets": null
  },
  "CreatedDateTime": "2014-07-16T13:15:56.5202454-08:00"
}

http://www.jsoneditoronline.org/中使用此 json 来查看它会是什么样子。或者根据需要使用模型对象。

在附件中找到 VS2012 MVC4 项目解决方案。重建,让 nugget 安装所需的软件包。

© . All rights reserved.