如何让ELMAH使用ASP.NET MVC [HandleError]属性?

dsw*_*tik 562 asp.net-mvc logging elmah

我正在尝试使用ELMAH来记录我的ASP.NET MVC应用程序中的错误,但是当我在控制器上使用[HandleError]属性时,ELMAH在发生错误时不会记录任何错误.

正如我猜测它,因为ELMAH只记录未处理的错误,[HandleError]属性正在处理错误,因此无需记录它.

我如何修改或如何修改属性,以便ELMAH可以知道有错误并记录它..

编辑:让我确保每个人都理解,我知道我可以修改那个不是我问的问题的属性... ELMAH在使用handleerror属性时会被绕过,这意味着它不会看到有错误因为它被处理了已经由属性...我要问的是有一种方法让ELMAH看到错误并记录它即使属性处理它...我搜索周围,没有看到任何方法调用强制它来记录错误....

Ati*_*ziz 501

您可以子类化HandleErrorAttribute并覆盖其OnException成员(无需复制),以便它使用ELMAH记录异常,并且仅在基本实现处理它时.您需要的最少代码量如下:

using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled) 
            return;
        var httpContext = context.HttpContext.ApplicationInstance.Context;
        var signal = ErrorSignal.FromContext(httpContext);
        signal.Raise(context.Exception, httpContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

首先调用基本实现,使其有机会将异常标记为正在处理.只有这样才会发出异常信号.上面的代码很简单,如果在HttpContext可能不可用的环境中使用,例如测试,可能会导致问题.因此,您将需要更具防御性的代码(代价是稍长一些):

using System.Web;
using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled       // if unhandled, will be logged anyhow
            || TryRaiseErrorSignal(context) // prefer signaling, if possible
            || IsFiltered(context))         // filtered?
            return;

        LogException(context);
    }

    private static bool TryRaiseErrorSignal(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        if (httpContext == null)
            return false;
        var signal = ErrorSignal.FromContext(httpContext);
        if (signal == null)
            return false;
        signal.Raise(context.Exception, httpContext);
        return true;
    }

    private static bool IsFiltered(ExceptionContext context)
    {
        var config = context.HttpContext.GetSection("elmah/errorFilter")
                        as ErrorFilterConfiguration;

        if (config == null)
            return false;

        var testContext = new ErrorFilterModule.AssertionHelperContext(
                              context.Exception, 
                              GetHttpContextImpl(context.HttpContext));
        return config.Assertion.Test(testContext);
    }

    private static void LogException(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        var error = new Error(context.Exception, httpContext);
        ErrorLog.GetDefault(httpContext).Log(error);
    }

    private static HttpContext GetHttpContextImpl(HttpContextBase context)
    {
        return context.ApplicationInstance.Context;
    }
}
Run Code Online (Sandbox Code Playgroud)

第二个版本将尝试首先使用ELMAH的错误信号,这涉及完整配置的管道,如日志记录,邮件,过滤和你有什么.如果不这样做,它会尝试查看是否应该过滤错误.如果没有,则只记录错误.此实现不处理邮件通知.如果可以发出异常信号,那么如果配置了邮件,则会发送邮件.

您可能还需要注意,如果多个HandleErrorAttribute实例生效,则不会发生重复日志记录,但上述两个示例应该启动.

  • 您不需要继承HandleErrorAttribute.您可以简单地使用IExceptionFilter实现并将其与HandleErrorAttribute一起注册.此外,我不明白为什么你需要有一个后备,以防ErrorSignal.Raise(..)失败.如果管道配置错误,则应修复.对于5班轮IExceptionFilter检查点4.这里 - http://ivanz.com/2011/05/08/asp-net-mvc-magical-error-logging-with-elmah/ (17认同)
  • 这仍然是相关的还是ELMAH.MVC处理这个? (7认同)
  • 请您对@IvanZlatev关于适用性,缺点等的答案发表评论.人们评论说它更容易/更短/更简单并且与答案相同,因此应该标记为正确答案.最好对此有所了解并通过这些答案获得一些清晰度. (5认同)
  • 即使我想知道它是否仍然适用于今天的版本 (2认同)

Iva*_*tev 297

对不起,但我认为接受的答案是矫枉过正.你需要做的就是:

public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
    public void OnException (ExceptionContext context)
    {
        // Log only handled exceptions, because all other will be caught by ELMAH anyway.
        if (context.ExceptionHandled)
            ErrorSignal.FromCurrentContext().Raise(context.Exception);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在Global.asax.cs中注册它(顺序很重要):

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Run Code Online (Sandbox Code Playgroud)

  • @jamiebarrow我没有意识到这一点,但他的答案是〜2岁,可能已经简化了API,以更短的更自包含的方式支持问题的用例. (48认同)
  • Atif Aziz创造了ELMAH,我会回答他的问题 (23认同)
  • @Ivan Zlatev真的无法工作`ElmahHandledErrorLoggerFilter()`elmah只记录未处理的错误,但没有处理.我按照你提到的那样,按照正确的顺序注册过滤器,有什么想法 (6认同)
  • +1非常好,不需要扩展`HandleErrorAttribute`,不需要在`BaseController`上覆盖`OnException`.这是接受的答案. (3认同)

Rau*_*jar 14

现在在NuGet中有一个ELMAH.MVC包,其中包括Atif的改进解决方案以及处理MVC路由中的elmah接口的控制器(不再需要使用该axd)
该解决方案的问题(以及所有这些解决方案) )是这样或那样的elmah错误处理程序实际上处理错误,忽略您可能想要设置为customError标记或通过ErrorHandler或您自己的错误处理程序
最好的解决方案恕我直言创建一个过滤器,将在所有其他过滤器的结束并记录已经处理过的事件.elmah模块应该注意记录应用程序未处理的其他错误.这还允许您使用运行状况监视器和可添加到asp.net的所有其他模块来查看错误事件

我在elmah.mvc里面的ErrorHandler中用反射器写了这个

public class ElmahMVCErrorFilter : IExceptionFilter
{
   private static ErrorFilterConfiguration _config;

   public void OnException(ExceptionContext context)
   {
       if (context.ExceptionHandled) //The unhandled ones will be picked by the elmah module
       {
           var e = context.Exception;
           var context2 = context.HttpContext.ApplicationInstance.Context;
           //TODO: Add additional variables to context.HttpContext.Request.ServerVariables for both handled and unhandled exceptions
           if ((context2 == null) || (!_RaiseErrorSignal(e, context2) && !_IsFiltered(e, context2)))
           {
            _LogException(e, context2);
           }
       }
   }

   private static bool _IsFiltered(System.Exception e, System.Web.HttpContext context)
   {
       if (_config == null)
       {
           _config = (context.GetSection("elmah/errorFilter") as ErrorFilterConfiguration) ?? new ErrorFilterConfiguration();
       }
       var context2 = new ErrorFilterModule.AssertionHelperContext((System.Exception)e, context);
       return _config.Assertion.Test(context2);
   }

   private static void _LogException(System.Exception e, System.Web.HttpContext context)
   {
       ErrorLog.GetDefault((System.Web.HttpContext)context).Log(new Elmah.Error((System.Exception)e, (System.Web.HttpContext)context));
   }


   private static bool _RaiseErrorSignal(System.Exception e, System.Web.HttpContext context)
   {
       var signal = ErrorSignal.FromContext((System.Web.HttpContext)context);
       if (signal == null)
       {
           return false;
       }
       signal.Raise((System.Exception)e, (System.Web.HttpContext)context);
       return true;
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,在您的过滤器配置中,您想要执行以下操作:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //These filters should go at the end of the pipeline, add all error handlers before
        filters.Add(new ElmahMVCErrorFilter());
    }
Run Code Online (Sandbox Code Playgroud)

请注意,我在那里留下了一个注释,提醒人们如果他们想要添加一个实际处理异常的全局过滤器,它应该在最后一个过滤器之前进行,否则你会遇到ElmahMVCErrorFilter忽略未处理异常的情况,因为它没有被处理,它应该由Elmah模块提供,但是接下来的过滤器将异常标记为已处理,模块忽略它,导致异常从未使它成为elmah.

现在,确保webconfig中的elmah appsettings看起来像这样:

<add key="elmah.mvc.disableHandler" value="false" /> <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" /> <!-- This uses the default filter for elmah, set to disabled to use our own -->
<add key="elmah.mvc.requiresAuthentication" value="false" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.allowedRoles" value="*" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.route" value="errortracking" /> <!-- Base route for elmah pages -->
Run Code Online (Sandbox Code Playgroud)

这里重要的是"elmah.mvc.disableHandleErrorFilter",如果这是假的,它将使用elmah.mvc中的处理程序,该处理程序将使用默认的HandleErrorHandler实际处理异常,该默认HandleErrorHandler将忽略您的customError设置

此设置允许您在类和视图中设置自己的ErrorHandler标记,同时仍通过ElmahMVCErrorFilter记录这些错误,通过elmah模块向web.config添加customError配置,甚至编写自己的错误处理程序.您唯一需要做的就是记住在我们编写的elmah过滤器之前不添加任何实际处理错误的过滤器.我忘了提到:elmah没有重复.


小智 7

您可以采用上面的代码,并通过引入一个自定义控制器工厂更进一步,该工厂将HandleErrorWithElmah属性注入每个控制器.

有关更多信息,请查看我关于登录MVC的博客系列.第一篇文章介绍了如何让Elmah为MVC设置和运行.

本文末尾有一个可下载代码的链接.希望有所帮助.

http://dotnetdarren.wordpress.com/

  • 在我看来,将它粘贴在基础控制器类上会更容易! (6认同)
  • Darren上面关于日志记录和异常处理的系列非常值得一读!!! 非常彻底! (2认同)

小智 6

我是ASP.NET MVC的新手.我遇到了同样的问题,以下是我的Erorr.vbhtml可行(如果您只需要使用Elmah日志记录错误,它就可以工作)

@ModelType System.Web.Mvc.HandleErrorInfo

    @Code
        ViewData("Title") = "Error"
        Dim item As HandleErrorInfo = CType(Model, HandleErrorInfo)
        //To log error with Elmah
        Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(New Elmah.Error(Model.Exception, HttpContext.Current))
    End Code

<h2>
    Sorry, an error occurred while processing your request.<br />

    @item.ActionName<br />
    @item.ControllerName<br />
    @item.Exception.Message
</h2> 
Run Code Online (Sandbox Code Playgroud)

简直就是这样!

  • 这也是在视图中执行服务级别功能.不属于这里. (6认同)
  • 对于任何JSON /非HTML响应都将被忽略. (3认同)

Ros*_*Nab 6

一个完全替代的解决方案是不使用MVC HandleErrorAttribute,而是依赖于Elmah旨在使用的ASP.Net错误处理.

您需要HandleErrorAttribute从App_Start\FilterConfig(或Global.asax)中删除默认全局,然后在Web.config中设置错误页面:

<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />
Run Code Online (Sandbox Code Playgroud)

请注意,这可以是MVC路由URL,因此上述ErrorController.Index操作会在发生错误时重定向到操作.

  • 这将重定向其他类型的请求,如JSON等 - 不好. (3认同)

小智 5

对我来说,让电子邮件日志记录工作非常重要.经过一段时间后,我发现在Atif示例中只需要2行代码.

public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
    static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();

    public override void OnException(ExceptionContext context)
    {
        error_mail_log.Init(HttpContext.Current.ApplicationInstance);
        [...]
    }
    [...]
}
Run Code Online (Sandbox Code Playgroud)

我希望这会帮助别人:)