关于日志记录:如何让ELMAH使用ASP.NET MVC [HandleError]属性?

How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?

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

因为Elmah只记录未处理的错误,而[handleError]属性正在处理错误,所以不需要记录。

如何修改或如何修改属性,以便Elmah知道发生了错误并记录下来。

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


您可以子类HandleErrorAttribute并重写其OnException成员(不需要复制),以便它用elmah记录异常,并且仅当基本实现处理它时。您需要的最少代码量如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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;
    }
}

第二个版本将首先尝试使用来自elmah的错误信号,这涉及到完全配置的管道,如日志记录、邮件、过滤和您拥有的东西。如果失败,它将尝试查看是否应该过滤错误。如果没有,则只记录错误。此实现不处理邮件通知。如果可以发出异常信号,那么如果配置为发送邮件,则会发送邮件。

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


对不起,但我认为被接受的答案太过分了。你需要做的就是:

1
2
3
4
5
6
7
8
9
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);
    }
}

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

1
2
3
4
5
public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}


现在,Nuget中有一个elmah.mvc包,其中包括由atif改进的解决方案,以及在mvc路由中处理elmah接口的控制器(不再需要使用该axd)。这个解决方案(以及这里所有的解决方案)的问题在于,不管怎样,elmah错误处理程序实际上正在处理错误,忽略您可能希望设置为customError标记的内容,或者通过error handler或您自己的错误处理程序来设置的内容。IMHO的最佳解决方案是创建一个过滤器,该过滤器将在所有其他过滤器的末尾起作用,并记录已处理的事件。Elmah模块应负责将应用程序未处理的其他错误登录。这还允许您使用运行状况监视器和所有其他可以添加到ASP.NET的模块来查看错误事件。

我在elmah.mvc内部的错误处理程序中用reflector编写了这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
   }
}

现在,在您的过滤器配置中,您希望执行如下操作:

1
2
3
4
5
    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());
    }

请注意,我在这里留下了一条注释,提醒人们,如果他们想添加一个实际处理异常的全局筛选器,它应该放在最后一个筛选器之前,否则您会遇到这样一种情况:未处理的异常将被elmahmvserrorfilter忽略,因为它没有被处理,并且应由elmah模块记录下来。但是,下一个过滤器将异常标记为已处理,模块将忽略它,导致异常永远不会进入Elmah。

现在,确保webconfig中elmah的应用程序设置如下所示:

1
2
3
4
5
 <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
 <!-- This uses the default filter for elmah, set to disabled to use our own -->
 <!-- Manages authentication for elmah pages -->
 <!-- Manages authentication for elmah pages -->
 <!-- Base route for elmah pages -->

这里重要的是"elmah.mvc.disablehandleerfrilter",如果这是错误的,它将使用elmah.mvc中的处理程序,该处理程序将使用默认的handleerfrandler处理异常,该处理程序将忽略您的客户错误设置。

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


您可以使用上面的代码,通过引入一个自定义控制器工厂,将handleErrorWithelmah属性注入到每个控制器中,从而更进一步。

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

文章结尾有一个可下载代码的链接。希望有帮助。

网址:http://dotnedarren.wordpress.com/


另一种完全不同的解决方案是不使用MVC HandleErrorAttribute,而是依赖于Elmah设计用于处理的ASP.NET错误处理。

您需要从app ou startfilterconfig(或global.asax)中删除默认的global HandleErrorAttribute,然后在web.config中设置一个错误页:

1
<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />

注意,这可能是一个MVC路由的URL,因此当发生错误时,上面的内容将重定向到ErrorController.Index操作。


我是ASP.NET MVC的新手。我也面临同样的问题,下面是我在erorr.vbhtml中的可操作性(如果只需要使用elmah日志记录错误,它就可以工作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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


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

    @item.ActionName<br />
    @item.ControllerName<br />
    @item.Exception.Message

很简单!


对我来说,让电子邮件记录工作非常重要。过了一段时间,我发现在atif示例中,这只需要两行代码。

1
2
3
4
5
6
7
8
9
10
11
public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
    static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();

    public override void OnException(ExceptionContext context)
    {
        error_mail_log.Init(HttpContext.Current.ApplicationInstance);
        [...]
    }
    [...]
}

我希望这能帮助别人:)


这正是我的MVC站点配置所需要的!

我对OnException方法进行了一些修改,以处理多个HandleErrorAttribute实例,正如atif aziz所建议的那样:

bear in mind that you may have to take care that if multiple HandleErrorAttribute instances are in effect then duplicate logging does not occur.

我只是在调用基类之前检查context.ExceptionHandled,只是为了知道是否有人在当前处理程序之前处理了异常。它对我有用,我发布代码以防其他人需要它,并询问是否有人知道我忽略了什么。

希望它有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public override void OnException(ExceptionContext context)
{
    bool exceptionHandledByPreviousHandler = context.ExceptionHandled;

    base.OnException(context);

    Exception e = context.Exception;
    if (exceptionHandledByPreviousHandler
        || !context.ExceptionHandled  // if unhandled, will be logged anyhow
        || RaiseErrorSignal(e)        // prefer signaling, if possible
        || IsFiltered(context))       // filtered?
        return;

    LogException(e);
}