ELMAHをASP.NET MVC [HandleError]属性と連携させる方法


564

ELMAHを使用してASP.NET MVCアプリケーションのエラーをログに記録しようとしていますが、コントローラーで[HandleError]属性を使用すると、エラーが発生してもELMAHはログに記録しません。

ELMAHは未処理のエラーのみをログに記録し、[HandleError]属性がエラーを処理しているため、ログに記録する必要がないため、私はそれを推測しています。

ELMAHがエラーがあったことを認識してログに記録できるように、属性を変更する方法または属性を変更する方法を教えてください。

編集:みんなが理解できることを確認しましょう、私が質問している問題ではない属性を変更できることを知っています... handleerror属性を使用するとELMAHがバイパスされ、処理されたためにエラーが発生したことがわかりませんすでに属性によって...私が求めているのは、属性がそれを処理したにもかかわらず、ELMAHにエラーを見てログに記録させる方法があります...私は周りを検索し、ログを強制するために呼び出すメソッドが表示されていませんエラー....


12
うわー、私はジェフまたはジャレッドがこの質問に答えることを望みます。StackoverflowにELMAHを使用しています;)
Jon Limjap '20

11
うーん、奇妙です-HandleErrorAttributeは使用しません-Elmahはweb.configの<modules>セクションで設定されています。HandleErrorAttributeを使用する利点はありますか?
ジャロッドディクソン

9
@Jarrod-ELMAHフォークの「カスタム」が何であるかを確認するとよいでしょう。
スコットハンセルマン

3
@dswatikまた、web.configでredirectModeをResponseRewriteに設定することにより、リダイレクトを防ぐことができます。blog.turlov.com/2009/01/…を
Pavel Chuchuva

6
[HandleError]属性とElmahに関するWebドキュメントと投稿を何度も実行しましたが、ダミーのケースを設定したときに、これが解決する動作(Elmahが「処理された」エラーをログに記録しないなど)がわかりませんでした。これは、Elmah.MVC 2.0.x以降、このカスタムHandleErrorAttributeが不要になったためです。nugetパッケージに含まれています。
2012年

回答:


503

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

基本実装が最初に呼び出され、例外を処理中としてマークする機会が与えられます。その後にのみ、例外が通知されます。上記のコードは単純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;
    }
}

この2番目のバージョンは、最初にELMAHからのエラーシグナリングを使用しようとします。これには、ロギング、メール、フィルタリングなどの完全に構成されたパイプラインが含まれます。それが失敗すると、エラーをフィルターする必要があるかどうかを確認しようとします。そうでない場合、エラーは単にログに記録されます。この実装はメール通知を処理しません。例外を通知できる場合、そのように構成されていればメールが送信されます。

また、複数のHandleErrorAttributeインスタンスが有効な場合に重複ロギングが発生しないように注意する必要がある場合もありますが、上記の2つの例から始めるとよいでしょう。


1
優秀な。Elmahを実装するつもりはまったくありませんでした。私はMVCでうまく機能する方法で長年使用してきた自分のエラー報告をフックアップしようとしていました。あなたのコードは私に出発点を与えました。+1
スティーブウォーサム

18
HandleErrorAttributeをサブクラス化する必要はありません。IExceptionFilterを実装し、それをHandleErrorAttributeと一緒に登録するだけです。また、ErrorSignal.Raise(..)が失敗した場合にフォールバックが必要な理由がわかりません。パイプラインが正しく構成されていない場合は、修正する必要があります。-ここ5ライナーIExceptionFilterチェックポイント4のivanz.com/2011/05/08/...
イヴァン・ズレートブ

5
適用可能性、欠点などに関して、@ IvanZlatevによって以下の回答にコメントしてください。人々は、それがより簡単/より短く/より簡単であり、あなたの回答と同じことを達成しているため、正しい回答としてマークする必要があるとコメントしています。これについてのあなたの見解を持ち、これらの答えでいくらか明確にすることは良いことです。
Andrew

7
これはまだ関連していますか、それともELMAH.MVCがこれを処理しますか?
ロミア14

2
それが今日のバージョンでもまだ関連があるかどうか知りたいのですが
リファクタリング

299

申し訳ありませんが、受け入れられた答えはやりすぎだと思います。あなたがする必要があるすべてはこれです:

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に登録します(順序が重要です)。

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

3
1非常に素晴らしい、拡張する必要がHandleErrorAttribute、必要は上書きしないようにOnExceptionBaseController。これは、受け入れられた回答を前提としています。
CallMeLaNN

1
@bigb私はあなたが例外メッセージなど(例えばに追加する事に独自の例外タイプで例外をラップしなければならないと思うnew UnhandledLoggedException(Exception thrown)に何かを追加するMessage前にそれを返すことに。
イヴァン・ズレートブ

23
Atif AzizがELMAHを作成しました。私は彼の答えを
使い

48
@jamiebarrow気づきませんでしたが、彼の回答は2年前のものであり、おそらくAPIは、より短い自己完結型の方法で質問のユースケースをサポートするために簡略化されています。
Ivan Zlatev 2011

6
@Ivan ZlatevはElmahHandledErrorLoggerFilter()、処理されていないエラーをログに記録するだけでelmah を動作させることはできません。あなたが言ったように私は正しい順序でフィルターを登録しました、何か考えはありますか?
kuncevic.dev 2011

14

AtGetによる改善されたソリューションと、MVCルーティング内のelmahインターフェイスを処理するコントローラーを含むNuMAのELMAH.MVCパッケージが追加されました(そのaxdを使用する必要はありません) 。他のすべてのフィルターの最後で、すでに処理されたイベントをログに記録します。elmahモジュールは、アプリケーションによって処理されないその他のエラーのログを記録する必要があります。これにより、ヘルスモニターと、asp.netに追加できる他のすべてのモジュールを使用して、エラーイベントを確認することもできます。
そのソリューションの問題(およびここにあるすべてのもの) )何らかの方法でelmahエラーハンドラーが実際にエラーを処理しており、customErrorタグとして設定したり、ErrorHandlerや独自のエラーハンドラーを設定したりすることを無視している

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

今、あなたのフィルター設定であなたはこのようなことをしたいです:

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

例外を実際に処理するグローバルフィルターを追加する場合、この最後のフィルターの前に移動する必要があることを人々に思い出させるためにコメントを残したことに注意してください。それは処理されておらず、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 -->

ここで重要なのは「elmah.mvc.disableHandleErrorFilter」です。これがfalseの場合、カスタムエラー設定を無視するデフォルトのHandleErrorHandlerを使用して実際に例外を処理するelmah.mvc内のハンドラーを使用します

この設定により、クラスとビューに独自のErrorHandlerタグを設定し、ElmahMVCErrorFilterを介してそれらのエラーを記録し、elmahモジュールを介してweb.configにcustomError設定を追加し、独自のエラーハンドラーを作成することもできます。あなたがしなければならない唯一のことは、私たちが書いたelmahフィルターの前に実際にエラーを処理するフィルターを追加しないことを忘れないでください。そして、私は言及するのを忘れていました:elmahに重複はありません。


7

上記のコードを使用して、すべてのコントローラーにHandleErrorWithElmah属性を挿入するカスタムコントローラーファクトリーを導入することで、さらに一歩進んでください。

詳細については、MVCへのログインに関するブログシリーズをご覧ください。最初の記事では、ElmahをMVC用にセットアップして実行する方法について説明します。

記事の最後にダウンロード可能なコードへのリンクがあります。お役に立てば幸いです。

http://dotnetdarren.wordpress.com/


6
ベースコントローラークラスに貼り付ける方がはるかに簡単だと私には思えます!
Nathan Taylor、

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> 

簡単です!


これは、最も簡単な解決策です。カスタムハンドラーなどを作成または登録する必要はありません。私には問題ありません
ThiagoAlves、2011

3
JSON /非HTML応答では無視されます。
Craig Stuntz、2012年

6
また、これはビューでサービスレベルの機能を実行しています。ここには属していません。
Trevor de Koekkoek 2012

6

完全に代替のソリューションはMVCを使用せずHandleErrorAttribute、代わりにElmahが動作するように設計されたASP.Netエラー処理に依存することです。

HandleErrorAttributeApp_Start \ FilterConfig(またはGlobal.asax)から既定のグローバルを削除し、Web.configにエラーページを設定する必要があります。

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

これはMVCルーティングされたURLである可能性があるのでErrorController.Index、エラーが発生したときに上記のアクションにリダイレクトされることに注意してください。


これはこれまでで最も簡単な解決策であり、デフォルトのリダイレクトはMVCアクションにすることができます:)
Jeremy Cook

3
それは、JSONなどの他のタイプのリクエストにリダイレクトします-良くありません。
zvolkov

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);
        [...]
    }
    [...]
}

私はこれが誰かを助けることを願っています:)


2

これはまさに私のMVCサイト構成に必要なものです!

Atif Azizの提案に従って、OnException複数のHandleErrorAttributeインスタンスを処理するようにメソッドに少し変更を加えました。

複数のHandleErrorAttributeインスタンスが有効である場合、重複したロギングが発生しないように注意する必要があるかもしれないことに注意してください。

context.ExceptionHandled基本クラスを呼び出す前に、現在のハンドラの前に誰かが例外を処理したかどうかを確認するだけです。
それは私のために機能し、誰かがそれを必要とする場合に備えてコードを投稿し、誰かが私が何かを見落としたかどうか知っているかどうか尋ねます。

それが役立つことを願っています:

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

base.OnException()....の呼び出しに関連する "if"ステートメントがないようです。そして(exceptionHandledByPreviousHandler ||!context.ExceptionHandled || ...)お互いを取り消し、常にtrueになります。何か不足していますか?
joelvh

最初に、現在のハンドラーの前に呼び出され、他のハンドラーが例外を管理しているかどうかを確認し、結果を変数exceptionHandlerdByPreviousHandlerに格納します。次に、現在のハンドラーに例外自体を管理する機会を与えます:base.OnException(context)。
ilmatte

最初に、現在のハンドラーの前に呼び出され、他のハンドラーが例外を管理しているかどうかを確認し、結果を変数exceptionHandlerdByPreviousHandlerに格納します。次に、現在のハンドラーに例外自体を管理する機会を与えます:base.OnException(context)。例外が以前に管理されていなかった場合は、次のいずれかになります。1-現在のハンドラーによって管理されている場合:exceptionHandledByPreviousHandler = falseおよび!context.ExceptionHandled = false 2-現在のハンドラーによって管理されておらず、exceptionHandledByPreviousHandler = falseおよび!context。 ExceptionHandled true。ケース1のみがログに記録します。
ilmatte
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.