使用全局日志记录时,MVC [HandleError] HandleErrorAttribute 调用了两次
Posted
技术标签:
【中文标题】使用全局日志记录时,MVC [HandleError] HandleErrorAttribute 调用了两次【英文标题】:MVC [HandleError] HandleErrorAttribute called twice when using global logging 【发布时间】:2012-07-12 02:07:52 【问题描述】:在我使用的 MVC3 Web 应用程序中
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
filters.Add(new HandleErrorAttribute());
应用全局错误处理,如果发生未处理的异常,则向用户显示“错误”视图。
对于一个特定的视图,我还希望通过使用[HandleError(View = "SpecialError")]
装饰方法来在发生未处理的异常时显示不同的错误视图。这很好用。
然后我想添加未处理异常的全局日志记录。我使用日志记录代码创建了一个自定义 HandleError 属性:
public class MyHandleErrorAttribute : HandleErrorAttribute
public override void OnException(ExceptionContext context)
// Write to log code
base.OnException(context);
并更新了 RegisterGlobalFilters 和方法修饰以改用此属性名称。这通常有效,但是当在用MyHandleError(View = "SpecialError")]
修饰的方法中发生异常时,OnException 方法被调用两次。我最初假设用这个属性装饰方法取代了全局处理程序,但似乎它只是简单地添加到(这更有意义,但这不是我想要的)。通过调用 OnException 两次,相同的异常会被记录两次,这是绝对不能发生的。我不认为 OnException 被调用了两次因为它是一个自定义属性 - 我相信标准的 HandleError 属性也会发生这种情况,它现在只是可见的,因为我正在创建它的记录。
最终,我想记录所有未处理的异常(一次),同时保留 [HandleError] 提供的功能,特别是为特定方法异常设置不同的视图。有没有一种干净的方法来做到这一点?
【问题讨论】:
【参考方案1】:我相信我自己找到了一个干净的解决方案。扩展 HandleError 似乎是个好主意,但现在我认为这是朝着错误方向迈出的一步。我不想以不同的方式处理任何错误,只需在 HandleError 拾取异常之前将异常写入日志一次。因此,默认的 HandleError 可以保持原样。虽然 OnException 可以被多次调用,但在 HandleErrorAttribute 的标准实现中似乎是完全良性的。
相反,我创建了一个异常日志过滤器:
public class LoggedExceptionFilter : IExceptionFilter
public void OnException(ExceptionContext filterContext)
// logging code
它不需要从FilterAttribute
继承太多,因为它只是在 RegisterGlobalFilters 中与 HandleErrorAttribute 一起注册一次。
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
filters.Add(new LoggedExceptionFilter());
filters.Add(new HandleErrorAttribute());
这允许在不更改标准 [HandleError]
功能的情况下整齐地记录异常
【讨论】:
您好,很好的解决方案,但是您如何从异常源获取更多信息,例如类/方法名称?【参考方案2】:试试这个,
public class MyHandleErrorAttribute : HandleErrorAttribute
public override void OnException(ExceptionContext context)
var exceptionHandled = context.ExceptionHandled;
base.OnException(context);
if(!exceptionHandled && context.ExceptionHandled)
// log the error.
【讨论】:
【参考方案3】:您可以创建一个自定义IFilterProvider
,它将检查过滤器是否已应用于该操作:
public class MyFilterProvider : IFilterProvider
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
if (!actionDescriptor.GetFilterAttributes(true).Any(a => a.GetType() == typeof(MyHandleErrorAttribute)))
yield return new Filter(new MyHandleErrorAttribute(), FilterScope.Global, null);
然后,您无需在GlobalFilterCollection
中注册您的过滤器,而是在Application_Start()
中注册您的过滤器提供程序
FilterProviders.Providers.Add(new MyFilterProvider());
或者(类似于@Mark 建议的)您可以显式设置ExceptionContext
的ExceptionHandled
属性
public class MyHandleErrorAttribute : HandleErrorAttribute
public override void OnException(ExceptionContext context)
if(context.ExceptionHandled) return;
// Write to log code
base.OnException(context);
context.ExceptionHandled = true;
【讨论】:
【参考方案4】:我实际上找到了防止 OnException 方法触发两次的解决方案。如果你使用FilterConfig.RegisterGlobalFilters()方法中,注释掉HandleErrorAttribute的注册:
public class FilterConfig
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
//filters.Add(new HandleErrorAttribute());
其实我也使用了自带的HandleErrorAttribute,没有注册,效果很好。我只需要打开自定义错误:
<system.web>
<customErrors mode="On" />
</system.web>
【讨论】:
也许你应该重新阅读这个问题。他想同时使用两者。以上是关于使用全局日志记录时,MVC [HandleError] HandleErrorAttribute 调用了两次的主要内容,如果未能解决你的问题,请参考以下文章