Làm cách nào để ELMAH hoạt động với thuộc tính ASP.NET MVC [HandleError]?


564

Tôi đang cố gắng sử dụng ELMAH để ghi nhật ký lỗi trong ứng dụng ASP.NET MVC của mình, tuy nhiên khi tôi sử dụng thuộc tính [HandleError] trên bộ điều khiển của mình, ELMAH không ghi lại bất kỳ lỗi nào khi chúng xảy ra.

Như tôi đoán là vì ELMAH chỉ ghi lại các lỗi chưa được xử lý và thuộc tính [Xử lýError] đang xử lý lỗi do đó không cần phải ghi nhật ký.

Làm cách nào để sửa đổi hoặc làm cách nào để sửa đổi thuộc tính để ELMAH có thể biết rằng đã xảy ra lỗi và ghi lại nó ..

Chỉnh sửa: Hãy để tôi đảm bảo mọi người hiểu, tôi biết tôi có thể sửa đổi thuộc tính không phải là câu hỏi tôi đang hỏi ... ELMAH bị bỏ qua khi sử dụng thuộc tính xử lý có nghĩa là nó sẽ không thấy có lỗi vì đã xử lý đã có thuộc tính ... Điều tôi đang hỏi là có cách nào để ELMAH thấy lỗi và ghi lại nó mặc dù thuộc tính đã xử lý nó ... Tôi đã tìm kiếm xung quanh và không thấy bất kỳ phương thức nào để gọi nó để đăng nhập lỗi....


12
Wow, tôi hy vọng Jeff hoặc Jared sẽ trả lời câu hỏi này. Họ đang sử dụng ELMAH cho Stackoverflow;)
Jon Limjap

11
Hmm, lạ - chúng tôi không sử dụng HandErrorAttribution - Elmah được thiết lập trong phần <mô-đun> của web.config. Có lợi ích gì khi sử dụng HandErrorAttribution không?
Jarrod Dixon

9
@Jarrod - thật tuyệt khi thấy những gì "tùy chỉnh" về ngã ba ELMAH của bạn.
Scott Hanselman

3
@dswatik Bạn cũng có thể ngăn chuyển hướng bằng cách đặt redirectMode thành FeedbackRewrite trong web.config. Xem blog.turlov.com/2009/01/ từ
Pavel

6
Tôi tiếp tục chạy vào tài liệu web và các bài đăng nói về thuộc tính [HandleError] và Elmah, nhưng tôi không thấy hành vi này giải quyết (ví dụ Elmah không ghi lại lỗi "đã xử lý") khi tôi thiết lập trường hợp giả. Điều này là do đối với Elmah.MVC 2.0.x, Xử lý tùy chỉnhErrorAttribution này không còn cần thiết nữa; Nó được bao gồm trong gói nuget.
plyawn

Câu trả lời:


503

Bạn có thể phân lớp HandleErrorAttributevà ghi đè OnExceptionthành viên của nó (không cần sao chép) để nó ghi lại ngoại lệ với ELMAH và chỉ khi việc triển khai cơ sở xử lý nó. Số lượng mã tối thiểu bạn cần là như sau:

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);
    }
}

Việc thực hiện cơ sở được gọi trước tiên, tạo cơ hội đánh dấu ngoại lệ là được xử lý. Chỉ sau đó là ngoại lệ báo hiệu. Đoạn mã trên rất đơn giản và có thể gây ra sự cố nếu được sử dụng trong môi trường HttpContextcó thể không có sẵn, chẳng hạn như thử nghiệm. Kết quả là, bạn sẽ muốn mã có tính phòng thủ cao hơn (với chi phí dài hơn một chút):

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;
    }
}

Phiên bản thứ hai này sẽ cố gắng sử dụng báo hiệu lỗi từ ELMAH trước, bao gồm các đường ống được cấu hình đầy đủ như ghi nhật ký, gửi thư, lọc và những gì có bạn. Không, nó cố gắng xem liệu có nên lọc lỗi hay không. Nếu không, lỗi chỉ đơn giản là đăng nhập. Việc thực hiện này không xử lý thông báo thư. Nếu ngoại lệ có thể được báo hiệu thì một thư sẽ được gửi nếu được cấu hình để làm như vậy.

Bạn cũng có thể phải lưu ý rằng nếu nhiều HandleErrorAttributetrường hợp có hiệu lực thì việc ghi nhật ký trùng lặp không xảy ra, nhưng hai ví dụ trên sẽ giúp bạn bắt đầu.


1
Thông minh. Tôi đã không cố gắng để thực hiện Elmah cả. Tôi chỉ cố gắng đưa ra báo cáo lỗi của riêng mình mà tôi đã sử dụng trong nhiều năm theo cách hoạt động tốt với MVC. Mã của bạn đã cho tôi một điểm khởi đầu. +1
Steve Wortham

18
Bạn không cần phải phân lớp Xử lýErrorAttribution. Bạn chỉ có thể có một triển khai IExceptionFilter và đăng ký nó cùng với HandleErrorAttribution. Ngoài ra tôi không hiểu tại sao bạn cần phải có dự phòng trong trường hợp ErrorSignal.Raise (..) không thành công. Nếu đường ống được cấu hình xấu, nó sẽ được sửa chữa. Đối với một điểm kiểm tra IExceptionFilter 5 điểm 4. tại đây - ivanz.com/2011/05/08/ Giấy
Ivan Zlatev

5
Xin vui lòng bạn có thể nhận xét về câu trả lời dưới đây của @IvanZlatev liên quan đến khả năng áp dụng, thiếu sót, v.v. Mọi người đang nhận xét rằng nó dễ hơn / ngắn hơn / đơn giản hơn và đạt được giống như câu trả lời của bạn và như vậy nên được đánh dấu là câu trả lời đúng. Sẽ là tốt để có quan điểm của bạn về điều này và đạt được một số rõ ràng với những câu trả lời.
Andrew

7
Điều này vẫn còn có liên quan hay ELMAH.MVC xử lý việc này?
Romias

2
Ngay cả tôi cũng muốn biết liệu nó có còn phù hợp trong phiên bản ngày hôm nay hay không
tái cấu trúc

299

Xin lỗi, nhưng tôi nghĩ rằng câu trả lời được chấp nhận là quá mức cần thiết. Tất cả bạn cần làm là đây:

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);
    }
}

và sau đó đăng ký nó (thứ tự là quan trọng) trong Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}

3
1 Rất thoải mái, không cần phải kéo dài HandleErrorAttribute, không cần phải ghi đè lên OnExceptiontrên BaseController. Đây là giả sử cho câu trả lời được chấp nhận.
CallMeLaNN

1
@bigb Tôi nghĩ rằng bạn sẽ phải bọc ngoại lệ trong loại ngoại lệ của riêng mình để nối các thứ vào thông báo ngoại lệ, v.v. (ví dụ: new UnhandledLoggedException(Exception thrown)nối thêm thứ gì đó vào Messagetrước khi trả lại.
Ivan Zlatev

23
Atif Aziz đã tạo ELMAH, tôi sẽ đi với câu trả lời của anh ấy
jamiebarrow

48
@jamiebarrow Tôi không nhận ra điều đó, nhưng câu trả lời của anh ấy là ~ 2 tuổi và có lẽ API đã được đơn giản hóa để hỗ trợ các trường hợp sử dụng câu hỏi theo cách ngắn gọn hơn.
Ivan Zlatev

6
@Ivan Zlatev thực sự không thể làm việc ElmahHandledErrorLoggerFilter()elmah chỉ ghi nhật ký các lỗi chưa được xử lý, nhưng không được xử lý. Tôi đã đăng ký các bộ lọc theo đúng thứ tự như bạn đã đề cập, bạn có suy nghĩ gì không?
kuncevic.dev

14

Hiện tại đã có gói ELMAH.MVC trong NuGet bao gồm giải pháp cải tiến của Atif và cũng là bộ điều khiển xử lý giao diện elmah trong định tuyến MVC (không cần sử dụng axd đó nữa)
Vấn đề với giải pháp đó (và với tất cả các giải pháp ở đây ) là bằng cách này hay cách khác, trình xử lý lỗi elmah thực sự đang xử lý lỗi, bỏ qua những gì bạn có thể muốn thiết lập như một thẻ customError hoặc thông qua ErrorHandler hoặc trình xử lý lỗi của riêng bạn
Giải pháp tốt nhất IMHO là tạo một bộ lọc sẽ hoạt động ở cuối tất cả các bộ lọc khác và ghi nhật ký các sự kiện đã được xử lý. Mô-đun elmah nên chăm sóc để ghi lại các lỗi khác mà ứng dụng chưa xử lý. Điều này cũng sẽ cho phép bạn sử dụng máy theo dõi sức khỏe và tất cả các mô-đun khác có thể được thêm vào asp.net để xem xét các sự kiện lỗi

Tôi đã viết cái nhìn này với gương phản chiếu tại ErrorHandler bên trong elmah.mvc

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;
   }
}

Bây giờ, trong cấu hình bộ lọc của bạn, bạn muốn làm một cái gì đó như thế này:

    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());
    }

Lưu ý rằng tôi đã để lại một bình luận ở đó để nhắc nhở mọi người rằng nếu họ muốn thêm bộ lọc toàn cầu thực sự sẽ xử lý ngoại lệ thì nên đi TRƯỚC bộ lọc cuối cùng này, nếu không, bạn sẽ gặp trường hợp ngoại lệ chưa được xử lý sẽ bị ElmahMVCErrorFilter bỏ qua nó chưa được xử lý và nó phải được ghi lại bởi mô-đun Elmah nhưng sau đó bộ lọc tiếp theo đánh dấu ngoại lệ là đã xử lý và mô-đun bỏ qua nó, dẫn đến ngoại lệ không bao giờ biến nó thành elmah.

Bây giờ, hãy đảm bảo các cài đặt ứng dụng cho elmah trong cấu hình web của bạn trông giống như thế này:

<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 -->

Điều quan trọng ở đây là "elmah.mvc.disableHandleErrorFilter", nếu điều này là sai, nó sẽ sử dụng trình xử lý bên trong elmah.mvc thực sự sẽ xử lý ngoại lệ bằng cách sử dụng Trình xử lý mặc định sẽ bỏ qua các cài đặt tùy chỉnhError của bạn.

Thiết lập này cho phép bạn đặt các thẻ ErrorHandler của riêng mình trong các lớp và chế độ xem, trong khi vẫn ghi nhật ký các lỗi đó thông qua ElmahMVCErrorFilter, thêm cấu hình customError vào web.config thông qua mô đun elmah, thậm chí viết Trình xử lý lỗi của riêng bạn. Điều duy nhất bạn cần làm là nhớ không thêm bất kỳ bộ lọc nào thực sự sẽ xử lý lỗi trước bộ lọc elmah mà chúng tôi đã viết. Và tôi quên đề cập: không có bản sao trong elmah.


7

Bạn có thể lấy mã ở trên và tiến thêm một bước bằng cách giới thiệu một nhà máy điều khiển tùy chỉnh đưa thuộc tính HandleErrorWithElmah vào mọi bộ điều khiển.

Để biết thêm thông tin, hãy xem loạt blog của tôi về việc đăng nhập vào MVC. Bài viết đầu tiên bao gồm việc Elmah thiết lập và chạy cho MVC.

Có một liên kết đến mã có thể tải xuống ở cuối bài viết. Mong rằng sẽ giúp.

http://dotnetdarren.wordpress.com/


6
Có vẻ như tôi sẽ dễ dàng hơn rất nhiều khi chỉ cần dán nó vào một lớp trình điều khiển cơ sở!
Nathan Taylor

2
Loạt bài của Darren ở trên về ghi nhật ký và xử lý ngoại lệ rất đáng để đọc !!! Rất kỹ lưỡng!
Ryan Anderson

6

Tôi là người mới trong ASP.NET MVC. Tôi đã đối mặt với cùng một vấn đề, sau đây là khả năng của tôi trong Erorr.vbhtml của tôi (nó hoạt động nếu bạn chỉ cần ghi nhật ký lỗi bằng nhật ký 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> 

Nó chỉ đơn giản là!


Đây là giải pháp đơn giản nhất. Không cần phải viết hoặc đăng ký xử lý tùy chỉnh và công cụ. Hoạt động tốt với tôi
ThiagoAlves

3
Sẽ bị bỏ qua cho mọi phản hồi JSON / không phải HTML.
Craig Stuntz

6
Ngoài ra, đây là chức năng cấp dịch vụ trong một khung nhìn. Không thuộc về nơi này.
Trevor de Koekkoek

6

Một giải pháp hoàn toàn thay thế là không sử dụng MVC HandleErrorAttributevà thay vào đó dựa vào xử lý lỗi của ASP.Net, Elmah được thiết kế để hoạt động.

Bạn cần xóa toàn cầu mặc định HandleErrorAttributekhỏi App_Start \ FilterConfig (hoặc Global.asax), sau đó thiết lập trang lỗi trong Web.config:

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

Lưu ý, đây có thể là một URL định tuyến MVC, do đó, ở trên sẽ chuyển hướng đến ErrorController.Indexhành động khi xảy ra lỗi.


Đây là giải pháp đơn giản nhất cho đến nay và chuyển hướng mặc định có thể là một hành động MVC :)
Jeremy Cook

3
Điều đó sẽ chuyển hướng cho các loại yêu cầu khác, như JSON, v.v. - không tốt.
zvolkov

5

Đối với tôi, việc đăng nhập email hoạt động là rất quan trọng. Sau một thời gian tôi phát hiện ra rằng điều này chỉ cần thêm 2 dòng mã trong ví dụ Atif.

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

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

Tôi hy vọng điều này sẽ giúp được ai đó :)


2

Đây chính xác là những gì tôi cần cho cấu hình trang web MVC của tôi!

Tôi đã thêm một chút sửa đổi cho OnExceptionphương thức để xử lý nhiều HandleErrorAttributetrường hợp, như được đề xuất bởi Atif Aziz:

lưu ý rằng bạn có thể phải lưu ý rằng nếu nhiều HandleErrorAttributetrường hợp có hiệu lực thì việc đăng nhập trùng lặp không xảy ra.

Tôi chỉ cần kiểm tra context.ExceptionHandledtrước khi gọi lớp cơ sở, chỉ để biết liệu có ai khác xử lý ngoại lệ trước trình xử lý hiện tại không.
Nó hoạt động cho tôi và tôi đăng mã trong trường hợp người khác cần nó và hỏi xem có ai biết tôi có bỏ qua điều gì không.

Hy vọng nó hữu ích:

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);
}

Bạn dường như không có câu lệnh "nếu" xung quanh việc gọi cơ sở.OnException () .... Và (ngoại lệHandledByPreinglyHandler ||! Bối cảnh. Ngoại lệ | Xử lý | Tui bỏ lỡ điều gì vậy?
joelvh

Đầu tiên tôi kiểm tra xem có bất kỳ Trình xử lý nào khác, được gọi trước hiện tại không, đã quản lý ngoại lệ và tôi lưu trữ kết quả trong biến: ngoại lệHandlerdByPreinglyHandler. Sau đó, tôi trao cơ hội cho trình xử lý hiện tại để tự quản lý ngoại lệ: base.OnException (bối cảnh).
ilmatte

Đầu tiên tôi kiểm tra xem có bất kỳ Trình xử lý nào khác, được gọi trước hiện tại không, đã quản lý ngoại lệ và tôi lưu trữ kết quả trong biến: ngoại lệHandlerdByPreinglyHandler. Sau đó, tôi trao cơ hội cho trình xử lý hiện tại để tự quản lý ngoại lệ: base.OnException (bối cảnh). Nếu ngoại lệ không được quản lý trước đó thì có thể là: 1 - Nó được quản lý bởi trình xử lý hiện tại, sau đó: ngoại lệHandledByPreinglyHandler = false và! Context.ExceptionHandled = false 2 - Nó không được quản lý bởi trình xử lý hiện tại và: exHandledByPreinglyHandler =. Ngoại lệ Được xử lý đúng. Chỉ có trường hợp 1 sẽ đăng nhập.
ilmatte
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.