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






4.50/5 (10投票s)
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
的任何操作中找到错误时,将触发 BaseController
的 OnException
。
对于整个应用程序
在 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);
}
}
限制
- 我还在处理它。也可能有一些错误。如果您发现任何错误,请告诉我。
- 如果您使用自定义的会话模型,请确保覆盖
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); } }
- 尽量避免对特定异常重复使用此日志记录过程。(在操作/在控制器/基控制器/global.asax)一次。
- 看看评论部分 "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 安装所需的软件包。