使用全局日志记录时,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 建议的)您可以显式设置ExceptionContextExceptionHandled 属性

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 调用了两次的主要内容,如果未能解决你的问题,请参考以下文章

Elmah.MVC 与 Elmah.contrib.Mvc

如何为 C# MVC4 WebAPI 应用程序全局记录所有异常?

使用Spring AOP 记录Spring MVC请求日志

使用Spring AOP 记录Spring MVC请求日志

使用Spring AOP 记录Spring MVC请求日志

使用Spring AOP 记录Spring MVC请求日志