Pytanie Jak uzyskać ELMAH do pracy z atrybutem ASP.NET MVC [HandleError]?


Próbuję użyć ELMAH do rejestrowania błędów w mojej aplikacji ASP.NET MVC, jednak gdy używam atrybutu [HandleError] na moich kontrolerach, ELMAH nie rejestruje żadnych błędów, gdy one wystąpią.

Zgaduję, ponieważ ELMAH rejestruje tylko nieobsłużone błędy, a atrybut HandleError obsługuje błąd, więc nie trzeba go logować.

W jaki sposób mogę zmodyfikować lub jak zmodyfikować atrybut, aby ELMAH mógł wiedzieć, że wystąpił błąd i zalogować go?

Edytować: Pozwól mi upewnić się, że wszyscy rozumieją, Wiem, że mogę zmodyfikować atrybut, który nie jest pytaniem, które zadaję ... ELMAH zostaje ominięty przy użyciu atrybutu handleerror, co oznacza, że ​​nie zauważy on błędu, ponieważ został już obsłużony przez atrybut ... Pytam, czy istnieje sposób, aby ELMAH zobaczył błąd i logował go, mimo że atrybut go obsłużył ... Przeszukałem i nie widzę żadnych metod wywoływania, aby wymusić rejestrację błędu ....


556
2018-04-20 02:09


pochodzenie


Wow, mam nadzieję, że Jeff lub Jared odpowiedzieliby na to pytanie. Używają ELMAH dla Stackoverflow;) - Jon Limjap
Hmm, dziwne - nie używamy HandleErrorAttribute - Elmah jest ustawiony w sekcji <modules> naszego web.config. Czy korzystanie z HandleErrorAttribute ma korzyści? - Jarrod Dixon♦
@Jarrod - byłoby miło zobaczyć, co jest "niestandardowe" w twoim widelcu ELMAH. - Scott Hanselman
@dswatik Możesz także zapobiec przekierowaniom, ustawiając parametr redirectMode na ResponseRewrite w pliku web.config. Widzieć blog.turlov.com/2009/01/... - Pavel Chuchuva
Ciągle natknąłem się na dokumentację sieciową i posty mówiące o atrybucie [HandleError] i Elmah, ale nie widziałem zachowania, które to rozwiązuje (np. Elmah nie logującego błędu "obsługiwanego"), kiedy ustawiłem fikcyjną walizkę. Dzieje się tak, ponieważ od Elmah.MVC 2.0.x ten niestandardowy HandleErrorAttribute nie jest już wymagany; jest zawarty w pakiecie nuget. - plyawn


Odpowiedzi:


Możesz podklasę HandleErrorAttribute i zastąp go OnException członek (nie trzeba kopiować), aby rejestrował wyjątek za pomocą ELMAH i tylko wtedy, gdy podstawowa implementacja go obsługuje. Minimalna wymagana ilość kodu jest następująca:

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

Podstawowa implementacja jest wywoływana jako pierwsza, co daje szansę na zaznaczenie wyjątku jako obsługiwanego. Dopiero wtedy sygnalizowany jest wyjątek. Powyższy kod jest prosty i może powodować problemy, jeśli jest używany w środowisku, w którym HttpContext mogą być niedostępne, na przykład testowanie. W rezultacie będziesz chciał kodu, który jest bardziej defensywny (kosztem nieco dłuższego):

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

Ta druga wersja spróbuje użyć sygnalizacja błędu najpierw ELMAH, który obejmuje w pełni skonfigurowany potok, taki jak rejestrowanie, wysyłanie wiadomości, filtrowanie i to, co ty. W przeciwnym razie próbuje sprawdzić, czy błąd powinien zostać przefiltrowany. Jeśli nie, błąd jest po prostu rejestrowany. Ta implementacja nie obsługuje powiadomień pocztowych. Jeśli wyjątek może zostać zasygnalizowany, wiadomość zostanie wysłana, jeśli została skonfigurowana.

Możesz również zadbać, aby w przypadku wielu HandleErrorAttribute instancje działają, a następnie nie występuje rejestrowanie duplikatów, ale powyższe dwa przykłady powinny wystartować.


496
2018-04-23 01:28



Doskonały. Nie próbowałem w ogóle wdrożyć Elmah. Po prostu próbowałem podpiąć własne raporty o błędach, których używałem od lat w sposób, który działa dobrze z MVC. Twój kod dał mi punkt wyjścia. +1 - Steve Wortham
Nie potrzebujesz podklasy HandleErrorAttribute. Możesz po prostu mieć implementację IExceptionFilter i ją zarejestrować razem z HandleErrorAttribute. Również nie rozumiem, dlaczego musisz mieć awarię w przypadku błędu ErrorSignal.Raise (..). Jeśli potok jest źle skonfigurowany, powinien zostać naprawiony. Dla 5 liniowego IExceptionFilter sprawdź punkt 4. tutaj - ivanz.com/2011/05/08/... - Ivan Zlatev
Czy możesz skomentować poniższą odpowiedź @IvanZlatev odnośnie do możliwości zastosowania, braków itp. Ludzie komentują, że jest łatwiej / krótszy / prostszy i osiąga to samo co twoja odpowiedź i jako taki powinien być oznaczony jako poprawna odpowiedź. Dobrze byłoby mieć na to swoją perspektywę i uzyskać nieco jasności w tych odpowiedziach. - Andrew
Czy to nadal ma znaczenie lub czy ELMAH.MVC to obsługuje? - Romias
Nawet chciałbym się dowiedzieć, czy jest to aktualne w dzisiejszej wersji - refactor


Przepraszam, ale myślę, że zaakceptowana odpowiedź to przesada. Wszystko, co musisz zrobić, to:

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

a następnie zarejestrować go (kolejność jest ważna) w Global.asax.cs:

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

296
2018-05-09 12:27



+1 Bardzo ładne, nie trzeba przedłużać HandleErrorAttribute, nie trzeba zastępować OnException na BaseController. Przypuszcza się, że do zaakceptowanej odpowiedzi. - CallMeLaNN
@bigb Myślę, że musiałbyś zawrzeć wyjątek we własnym typie wyjątku, aby dołączyć elementy do komunikatu o wyjątku, itp. (np. new UnhandledLoggedException(Exception thrown) które dołącza coś do Message przed zwrotem. - Ivan Zlatev
Atif Aziz stworzył ELMAH, poszedłbym z jego odpowiedzią - jamiebarrow
@jamiebarrow Nie zdawałem sobie z tego sprawy, ale jego odpowiedź miała około 2 lata i prawdopodobnie API zostało uproszczone, aby wspierać przypadki użycia tego pytania w krótszym, bardziej zamkniętym trybie. - Ivan Zlatev
@Ivan Zlatev naprawdę nie może dostać się do pracy ElmahHandledErrorLoggerFilter() elmah właśnie rejestruje nieobsługiwane błędy, ale nie jest obsługiwane. Zarejestrowałem filtry w poprawnej kolejności, o której wspomniałeś, wszelkie myśli? - Kuncevič


Teraz w pakiecie NuGet znajduje się pakiet ELMAH.MVC, który zawiera ulepszone rozwiązanie Atif, a także kontroler obsługujący interfejs elmah w ramach routingu MVC (nie trzeba już używać tego axd)
Problem z tym rozwiązaniem (i wszystkimi tymi tutaj) polega na tym, że program obsługi błędów elmah faktycznie obsługuje błąd, ignorując to, co możesz ustawić jako tag customError lub przez ErrorHandler lub własną procedurę obsługi błędów
Najlepszym rozwiązaniem IMHO jest utworzenie filtru, który będzie działał na końcu wszystkich innych filtrów i rejestrował zdarzenia, które zostały już obsłużone. Moduł elmah powinien zająć się protokołowaniem innych błędów, które nie są obsługiwane przez aplikację. Umożliwi to również korzystanie z monitora kondycji i wszystkich innych modułów, które można dodać do asp.net w celu obejrzenia zdarzeń związanych z błędami

Napisałem to patrząc z reflektorem na ErrorHandler wewnątrz 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;
   }
}

Teraz w konfiguracji filtra chcesz zrobić coś takiego:

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

Zauważ, że zostawiłem tam komentarz, aby przypomnieć ludziom, że jeśli chcą dodać filtr globalny, który będzie obsługiwał wyjątek, powinien on przejść PRZED ostatnim filtrem, w przeciwnym razie pojawi się przypadek, w którym nieobsługiwany wyjątek zostanie zignorowany przez ElmahMVCErrorFilter, ponieważ nie został obsłużony i powinien zostać zarejestrowany przez moduł Elmah, ale następny filtr oznacza wyjątek jako obsługiwany, a moduł go ignoruje, co powoduje, że wyjątek nigdy nie przechodzi do elmah.

Teraz upewnij się, że appsettings dla elmah w twoim webconfig wygląda mniej więcej tak:

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

Ważne tutaj jest "elmah.mvc.disableHandleErrorFilter", jeśli jest to fałsz, użyje programu obsługi wewnątrz elmah.mvc, który będzie obsługiwał wyjątek, używając domyślnego HandleErrorHandler, który zignoruje twoje ustawienia customError

Ta konfiguracja pozwala ustawić własne tagi ErrorHandler w klasach i widokach, nadal rejestrując te błędy za pośrednictwem ElmahMVcrrorFilter, dodając konfigurację customError do pliku web.config za pośrednictwem modułu elmah, nawet wpisując własne procedury obsługi błędów. Jedyne, co musisz zrobić, to pamiętać, aby nie dodawać żadnych filtrów, które będą obsługiwały błąd przed filtrem elmah, który napisaliśmy. I zapomniałem wspomnieć: nie ma duplikatów w elmah.


14
2018-01-24 18:10



Wow ... mały świat, Raul. Dzięki za zamieszczenie tego. Sprawdziło się doskonale dla mnie. - Jason Barile


Możesz wziąć powyższy kod i pójść o krok dalej, wprowadzając fabrykę niestandardowych kontrolerów, która wstrzykuje atrybut HandleErrorWithElmah do każdego kontrolera.

Aby uzyskać więcej informacji, zapoznaj się z moją serię blogów na temat logowania w MVC. Pierwszy artykuł dotyczy przygotowania i uruchomienia Elmah dla MVC.

Na końcu artykułu znajduje się link do kodu do pobrania. Nadzieja, która pomaga.

http://dotnetdarren.wordpress.com/


7
2017-07-28 00:19



Wydaje mi się, że byłoby o wiele łatwiej po prostu przykleić go do klasy kontrolera bazowego! - Nathan Taylor
Seria Darrena powyżej dotycząca logowania i obsługi wyjątków jest warta przeczytania !!! Bardzo dokładny! - Ryan Anderson
Która odpowiedź brzmi "powyższy kod?" - Tim Lovell-Smith


Jestem nowy w ASP.NET MVC. Zmierzyłem się z tym samym problemem, w moim Erorr.vbhtml znajduje się moja operacja (działa, jeśli logujesz się tylko za pomocą dziennika 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> 

To jest po prostu!


6
2018-04-20 01:40



Jest to zdecydowanie najprostsze rozwiązanie. Nie trzeba pisać ani rejestrować niestandardowych procedur obsługi i innych rzeczy. Działa dobrze dla mnie - ThiagoAlves
Zostanie zignorowany dla wszystkich odpowiedzi JSON / non-HTML. - Craig Stuntz
także to robi funkcjonalność poziomu usługi w widoku. Nie pasuje tutaj. - Trevor de Koekkoek


Całkowicie alternatywnym rozwiązaniem jest niestosowanie MVC HandleErrorAttributei zamiast tego polegać na obsłudze błędów ASP.Net, z którą Elmah ma współpracować.

Musisz usunąć domyślny globalny HandleErrorAttribute z App_Start \ FilterConfig (lub Global.asax), a następnie skonfiguruj stronę błędu w pliku Web.config:

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

Zauważ, że może to być adresowany przez MVC URL, więc powyższe polecenie przekieruje Cię do ErrorController.Index akcja po wystąpieniu błędu.


6
2017-08-01 07:32



To jest najprostsze rozwiązanie, a domyślnym przekierowaniem może być akcja MVC :) - Jeremy Cook
To przekieruje do innych typów wniosków, takich jak JSON itd. - nie jest dobre. - zvolkov


Dla mnie bardzo ważne było, aby rejestrowanie poczty e-mail działało. Po pewnym czasie odkryłem, że w przykładzie Atif wymaga to tylko 2 linii kodu.

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

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

Mam nadzieję, że to pomoże komuś :)


5
2018-01-26 09:11





Dokładnie tego potrzebowałem w mojej konfiguracji strony MVC!

Dodałem niewielką modyfikację do OnException metoda obsługi wielu HandleErrorAttribute instancje, jak sugeruje Atif Aziz:

pamiętaj, że możesz być ostrożny, jeśli jest ich wiele HandleErrorAttribute wystąpią instancje, a następnie nie następuje rejestrowanie duplikatów.

Po prostu sprawdzam context.ExceptionHandled przed wywołaniem klasy bazowej, aby wiedzieć, czy ktoś inny potraktował wyjątek przed bieżącym programem obsługi.
Działa to dla mnie i umieszczam kod na wypadek, gdyby ktoś go potrzebował i zapytał, czy ktoś wie, czy przeoczyłem cokolwiek.

Mam nadzieję, że jest to przydatne:

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

2
2017-11-06 22:35



Wydaje się, że nie ma instrukcji "if" wokół wywoływania base.OnException () .... I (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) anulują się nawzajem i zawsze będą prawdziwe. Czy czegoś brakuje? - joelvh
Najpierw sprawdzam, czy jakikolwiek inny Handler, wywołany przed bieżącym, zarządzał wyjątkiem i zapisuję wynik w zmiennej: exceptionHandlerdByPreviousHandler. Następnie daję szansę bieżącemu programowi obsługi do zarządzania samym wyjątkiem: base.OnException (context). - ilmatte
Najpierw sprawdzam, czy jakikolwiek inny Handler, wywołany przed bieżącym, zarządzał wyjątkiem i zapisuję wynik w zmiennej: exceptionHandlerdByPreviousHandler. Następnie daję szansę bieżącemu programowi obsługi do zarządzania samym wyjątkiem: base.OnException (context). Jeśli wyjątek nie był wcześniej zarządzany, może to być: 1 - Jest zarządzany przez bieżący moduł obsługi, a następnie: exceptionHandledByPreviousHandler = false i! Context.ExceptionHandled = false 2 - Nie jest zarządzany przez bieżący moduł obsługi i: exceptionHandledByPreviousHandler = false i! Context. ExceptionHandled true. Tylko przypadek 1 będzie rejestrowany. - ilmatte