Wie kann ELMAH mit dem ASP.NET MVC-Attribut [HandleError] arbeiten?

564

Ich versuche, ELMAH zum Protokollieren von Fehlern in meiner ASP.NET MVC-Anwendung zu verwenden. Wenn ich jedoch das Attribut [HandleError] auf meinen Controllern verwende, protokolliert ELMAH keine Fehler, wenn sie auftreten.

Wie ich vermute, liegt es daran, dass ELMAH nur nicht behandelte Fehler protokolliert und das Attribut [HandleError] den Fehler behandelt, sodass es nicht protokolliert werden muss.

Wie ändere ich oder wie würde ich das Attribut ändern, damit ELMAH erkennt, dass ein Fehler aufgetreten ist, und ihn protokolliere.

Bearbeiten: Lassen Sie mich sicherstellen, dass jeder versteht, ich weiß, dass ich das Attribut ändern kann, das nicht die Frage ist, die ich stelle ... ELMAH wird umgangen, wenn das handleerror-Attribut verwendet wird, was bedeutet, dass es keinen Fehler gibt, weil es behandelt wurde bereits nach dem Attribut ... Was ich frage, ist eine Möglichkeit, ELMAH dazu zu bringen, den Fehler zu sehen und zu protokollieren, obwohl das Attribut ihn behandelt hat ... Ich habe mich umgesehen und sehe keine Methoden zum Aufrufen, um ihn zum Protokollieren zu zwingen der Fehler....

dswatik
quelle
12
Wow, ich hoffe Jeff oder Jared würden diese Frage beantworten. Sie verwenden ELMAH für Stackoverflow;)
Jon Limjap
11
Hmm, seltsam - wir verwenden das HandleErrorAttribute nicht - Elmah wird im Abschnitt <modules> unserer web.config eingerichtet. Gibt es Vorteile bei der Verwendung des HandleErrorAttribute?
Jarrod Dixon
9
@ Jarrod - es wäre schön zu sehen, was "Brauch" an Ihrer ELMAH-Gabel ist.
Scott Hanselman
3
@dswatik Sie können Weiterleitungen auch verhindern, indem Sie redirectMode in web.config auf ResponseRewrite setzen. Siehe blog.turlov.com/2009/01/…
Pavel Chuchuva
6
Ich bin immer wieder auf Webdokumentation und Posts gestoßen, in denen über das Attribut [HandleError] und Elmah gesprochen wurde, aber ich habe das Verhalten, das dadurch behoben wird (z. B. Elmah protokolliert den "behandelten" Fehler nicht), beim Einrichten des Dummy-Falls nicht gesehen. Dies liegt daran, dass ab Elmah.MVC 2.0.x dieses benutzerdefinierte HandleErrorAttribute nicht mehr erforderlich ist. Es ist im Nuget-Paket enthalten.
Plyawn

Antworten:

503

Sie können HandleErrorAttributedas OnExceptionMitglied unterklassifizieren und überschreiben (kein Kopieren erforderlich), sodass es die Ausnahme mit ELMAH protokolliert und nur dann, wenn die Basisimplementierung dies behandelt. Die minimale Menge an Code, die Sie benötigen, ist wie folgt:

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

Die Basisimplementierung wird zuerst aufgerufen, sodass die Ausnahme als behandelt markiert werden kann. Erst dann wird die Ausnahme signalisiert. Der obige Code ist einfach und kann Probleme verursachen, wenn er in einer Umgebung verwendet wird, in der er HttpContextmöglicherweise nicht verfügbar ist, z. B. beim Testen. Infolgedessen möchten Sie Code, der defensiver ist (auf Kosten einer etwas längeren Länge):

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

Diese zweite Version wird versuchen, zuerst die Fehlersignalisierung von ELMAH zu verwenden, die die vollständig konfigurierte Pipeline wie Protokollierung, Mailing, Filterung und was Sie haben umfasst. Andernfalls wird versucht zu prüfen, ob der Fehler gefiltert werden soll. Wenn nicht, wird der Fehler einfach protokolliert. Diese Implementierung verarbeitet keine E-Mail-Benachrichtigungen. Wenn die Ausnahme signalisiert werden kann, wird eine E-Mail gesendet, sofern dies konfiguriert ist.

Möglicherweise müssen Sie auch darauf achten, dass bei mehreren HandleErrorAttributeInstanzen keine doppelte Protokollierung erfolgt. Die beiden oben genannten Beispiele sollten jedoch den Anfang machen.

Atif Aziz
quelle
1
Ausgezeichnet. Ich habe überhaupt nicht versucht, Elmah umzusetzen. Ich habe nur versucht, meine eigene Fehlerberichterstattung, die ich seit Jahren verwende, so zusammenzufügen, dass sie gut mit MVC funktioniert. Ihr Code gab mir einen Ausgangspunkt. +1
Steve Wortham
18
Sie müssen HandleErrorAttribute nicht unterordnen. Sie können einfach eine IExceptionFilter-Implementierung haben und diese zusammen mit dem HandleErrorAttribute registrieren lassen. Ich verstehe auch nicht, warum Sie einen Fallback benötigen, falls ErrorSignal.Raise (..) fehlschlägt. Wenn die Pipeline schlecht konfiguriert ist, sollte sie repariert werden. Für einen 5-Liner IExceptionFilter Check Point 4. hier - ivanz.com/2011/05/08/…
Ivan Zlatev
5
Bitte können Sie die Antwort von @IvanZlatev in Bezug auf Anwendbarkeit, Mängel usw. kommentieren. Die Leute kommentieren, dass es einfacher / kürzer / einfacher ist und dasselbe wie Ihre Antwort erreicht und als solche als die richtige Antwort markiert werden sollte. Es wäre gut, wenn Sie Ihre Sichtweise dazu haben und mit diesen Antworten Klarheit schaffen würden.
Andrew
7
Ist das noch relevant oder kümmert sich ELMAH.MVC darum?
Romias
2
Sogar ich würde gerne wissen, ob es in der heutigen Version noch relevant ist
Refactor
299

Entschuldigung, aber ich denke, die akzeptierte Antwort ist ein Overkill. Alles was Sie tun müssen ist Folgendes:

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

und registrieren Sie es dann (Reihenfolge ist wichtig) in Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Ivan Zlatev
quelle
3
Sehr schön + 1, die keine Notwendigkeit zu verlängern HandleErrorAttribute, keine Notwendigkeit , außer Kraft setzt OnExceptionauf BaseController. Dies setzt die akzeptierte Antwort voraus.
CallMeLaNN
1
@bigb Ich denke, Sie müssten die Ausnahme in Ihren eigenen Ausnahmetyp new UnhandledLoggedException(Exception thrown)einschließen , um Dinge an die Ausnahmemeldung usw. anzuhängen (z. B. die etwas an die anhängt, Messagebevor sie zurückgegeben wird.
Ivan Zlatev
23
Atif Aziz erstellt ELMAH, ich würde mit seiner Antwort gehen
Jamiebarrow
48
@jamiebarrow Ich habe das nicht bemerkt, aber seine Antwort ist ~ 2 Jahre alt und wahrscheinlich wurde die API vereinfacht, um die Anwendungsfälle der Frage kürzer und eigenständiger zu unterstützen.
Ivan Zlatev
6
@Ivan Zlatev kann wirklich nicht zur Arbeit kommen ElmahHandledErrorLoggerFilter()elmah protokolliert nur nicht behandelte Fehler, wird aber nicht behandelt. Ich habe Filter in der richtigen Reihenfolge registriert, als Sie das erwähnt haben. Irgendwelche Gedanken?
Kuncevic.dev
14

In NuGet gibt es jetzt ein ELMAH.MVC-Paket, das eine verbesserte Lösung von Atif sowie einen Controller enthält, der die elmah-Schnittstelle innerhalb des MVC-Routings verwaltet (diese Axd muss nicht mehr verwendet werden).
Das Problem mit dieser Lösung (und mit allen hier) ) ist, dass der elmah-Fehlerbehandler den Fehler auf die eine oder andere Weise tatsächlich behandelt und ignoriert, was Sie möglicherweise als customError-Tag oder über ErrorHandler oder Ihren eigenen Fehlerbehandler einrichten möchten
Die beste Lösung ist meiner Meinung nach, einen Filter zu erstellen, der am Ende aller anderen Filter wirkt und die bereits behandelten Ereignisse protokolliert. Das elmah-Modul sollte sich darum kümmern, die anderen Fehler zu protokollieren, die von der Anwendung nicht behandelt werden. Auf diese Weise können Sie auch den Integritätsmonitor und alle anderen Module verwenden, die asp.net hinzugefügt werden können, um Fehlerereignisse anzuzeigen

Ich habe diesen Blick mit Reflektor auf den ErrorHandler in elmah.mvc geschrieben

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

In Ihrer Filterkonfiguration möchten Sie nun Folgendes tun:

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

Beachten Sie, dass ich dort einen Kommentar hinterlassen habe, um die Leute daran zu erinnern, dass, wenn sie einen globalen Filter hinzufügen möchten, der die Ausnahme tatsächlich behandelt, dieser VOR diesem letzten Filter angezeigt wird. Andernfalls tritt der Fall auf, in dem die nicht behandelte Ausnahme vom ElmahMVCErrorFilter ignoriert wird, weil Es wurde nicht behandelt und sollte vom Elmah-Modul protokolliert werden. Der nächste Filter markiert die Ausnahme jedoch als behandelt und das Modul ignoriert sie. Dies führt dazu, dass die Ausnahme niemals zu elmah wird.

Stellen Sie nun sicher, dass die Apps für elmah in Ihrer Webkonfiguration ungefähr so ​​aussehen:

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

Das wichtigste hier ist "elmah.mvc.disableHandleErrorFilter". Wenn dies falsch ist, wird der Handler in elmah.mvc verwendet, der die Ausnahme tatsächlich behandelt, indem der Standard-HandleErrorHandler verwendet wird, der Ihre customError-Einstellungen ignoriert

Mit diesem Setup können Sie Ihre eigenen ErrorHandler-Tags in Klassen und Ansichten festlegen, während Sie diese Fehler weiterhin über den ElmahMVCErrorFilter protokollieren, Ihrer web.config über das elmah-Modul eine customError-Konfiguration hinzufügen und sogar Ihre eigenen Fehlerbehandler schreiben. Das einzige, was Sie tun müssen, ist daran zu denken, keine Filter hinzuzufügen, die den Fehler tatsächlich behandeln, bevor der von uns geschriebene elmah-Filter verwendet wird. Und ich habe vergessen zu erwähnen: keine Duplikate in Elma.

Raul Vejar
quelle
7

Sie können den obigen Code verwenden und einen Schritt weiter gehen, indem Sie eine benutzerdefinierte Controller-Factory einführen, die das HandleErrorWithElmah-Attribut in jeden Controller einfügt.

Weitere Informationen finden Sie in meiner Blogserie zum Anmelden bei MVC. Der erste Artikel behandelt die Einrichtung und den Betrieb von Elmah für MVC.

Am Ende des Artikels befindet sich ein Link zum herunterladbaren Code. Ich hoffe, das hilft.

http://dotnetdarren.wordpress.com/

Darren
quelle
6
Mir scheint, es wäre viel einfacher, es einfach auf eine Basis-Controller-Klasse zu kleben!
Nathan Taylor
2
Darrens obige Serie über Protokollierung und Ausnahmebehandlung ist es wert, gelesen zu werden !!! Sehr ausführlich!
Ryan Anderson
6

Ich bin neu in ASP.NET MVC. Ich hatte das gleiche Problem: Folgendes kann ich in meiner Datei Erorr.vbhtml bearbeiten (es funktioniert, wenn Sie den Fehler nur mit dem Elmah-Protokoll protokollieren müssen).

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

Es ist einfach!

user716264
quelle
Dies ist bei weitem die einfachste Lösung. Sie müssen keine benutzerdefinierten Handler und andere Dinge schreiben oder registrieren. Funktioniert gut für mich
ThiagoAlves
3
Wird bei JSON- / Nicht-HTML-Antworten ignoriert.
Craig Stuntz
6
Auch dies führt die Service Level-Funktionalität in einer Ansicht aus. Gehört nicht hierher.
Trevor de Koekkoek
6

Eine völlig alternative Lösung besteht darin, die MVC nicht zu verwenden HandleErrorAttributeund sich stattdessen auf die ASP.Net-Fehlerbehandlung zu verlassen, mit der Elmah arbeiten soll.

Sie müssen den globalen Standard HandleErrorAttributeaus App_Start \ FilterConfig (oder Global.asax) entfernen und dann eine Fehlerseite in Ihrer Web.config einrichten:

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

Beachten Sie, dass dies eine MVC-geroutete URL sein kann, sodass die oben genannten Informationen zur ErrorController.IndexAktion umleiten, wenn ein Fehler auftritt.

Ross McNab
quelle
Dies ist bei weitem die einfachste Lösung, und die Standardumleitung kann eine MVC-Aktion sein :)
Jeremy Cook
3
Das wird für andere Arten von Anfragen wie JSON usw. umgeleitet - nicht gut.
Zvolkov
5

Für mich war es sehr wichtig, dass die E-Mail-Protokollierung funktioniert. Nach einiger Zeit stelle ich fest, dass dies im Atif-Beispiel nur 2 Codezeilen mehr benötigt.

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

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

Ich hoffe das hilft jemandem :)

Komio
quelle
2

Genau das brauchte ich für meine MVC-Site-Konfiguration!

Ich habe der OnExceptionMethode eine kleine Änderung hinzugefügt , um mehrere HandleErrorAttributeInstanzen zu verarbeiten , wie von Atif Aziz vorgeschlagen:

Beachten Sie, dass Sie möglicherweise darauf achten müssen, dass bei mehreren HandleErrorAttributeInstanzen keine doppelte Protokollierung erfolgt.

Ich überprüfe einfach, context.ExceptionHandledbevor ich die Basisklasse aufrufe, um zu wissen, ob jemand anderes die Ausnahme vor dem aktuellen Handler behandelt hat.
Es funktioniert für mich und ich poste den Code, falls jemand anderes ihn benötigt, und frage, ob jemand weiß, ob ich etwas übersehen habe.

Hoffe es ist nützlich:

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);
}
ilmatte
quelle
Sie scheinen keine "if" -Anweisung zum Aufrufen von base.OnException () zu haben .... Und (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) heben sich gegenseitig auf und sind immer wahr. Vermisse ich etwas
Joelvh
Zuerst überprüfe ich, ob ein anderer Handler, der vor dem aktuellen aufgerufen wurde, die Ausnahme verwaltet hat, und speichere das Ergebnis in der Variablen: exceptionHandlerdByPreviousHandler. Dann gebe ich dem aktuellen Handler die Möglichkeit, die Ausnahme selbst zu verwalten: base.OnException (Kontext).
Ilmatte
Zuerst überprüfe ich, ob ein anderer Handler, der vor dem aktuellen aufgerufen wurde, die Ausnahme verwaltet hat, und speichere das Ergebnis in der Variablen: exceptionHandlerdByPreviousHandler. Dann gebe ich dem aktuellen Handler die Möglichkeit, die Ausnahme selbst zu verwalten: base.OnException (Kontext). Wenn die Ausnahme zuvor nicht verwaltet wurde, kann dies sein: 1 - Sie wird vom aktuellen Handler verwaltet, dann: exceptionHandledByPreviousHandler = false und! Context.ExceptionHandled = false 2 - Sie wird nicht vom aktuellen Handler verwaltet und: exceptionHandledByPreviousHandler = false und! ExceptionHandled true. Nur Fall 1 wird protokolliert.
Ilmatte