web-dev-qa-db-ja.com

ELMAHをASP.NET MVC [HandleError]属性と連携させるにはどうすればいいですか?

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

私が推測しているように、ELMAHは未処理のエラーのみをログに記録し、[HandleError]属性はエラーを処理しているのでログに記録する必要はありません。

ELMAHがエラーがあったことを認識して記録するように、どのようにして属性を変更しますか。

編集:誰もが理解していることを確認してください、私が求めている質問ではない属性を変更できることを知っています... handleerror属性を使用するとELMAHがバイパスされますそれは属性によって既に処理されていたのでエラーでした...私が頼んでいるのは属性がそれを処理したにもかかわらずELMAHにエラーを見させそしてそれをログに記録させる方法があるということです...エラーを記録するためにそれを強制するために呼び出すために....

559
dswatik

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つの例を参考にしてください。

498
Atif Aziz

すみません、しかし私は受け入れられた答えがやり過ぎだと思います。あなたがする必要があるのはこれだけです:

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

NuGetにELMAH.MVCパッケージが追加されました。これには、Atifによる改良されたソリューションと、MVCルーティング内でelmahインターフェースを処理するコントローラが含まれています(もうaxdを使用する必要はありません)。
その解決策(そしてここにあるすべてのもの)の問題は、何らかの方法でelmahエラーハンドラが実際にエラーを処理しているということです、customErrorタグとして、またはErrorHandlerを通して設定したいものは無視します。あなた自身のエラーハンドラ
私見の最善の解決策は、他のすべてのフィルタの最後に機能するフィルタを作成し、すでに処理されたイベントを記録することです。 elmahモジュールは、アプリケーションによって処理されない他のエラーをログに記録するように気をつけるべきです。これはまたあなたがヘルスモニタとエラーイベントを見るためにasp.netに追加することができる他のすべてのモジュールを使用することを可能にします

私は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モジュールによって処理されていないので、それをlohamする必要がありますが、次のフィルタは例外を処理済みとしてマークし、モジュールはそれを無視します。その結果、例外は決して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の場合は、customError設定を無視するデフォルトのHandleErrorHandlerを使用して実際に例外を処理するelmah.mvc内のハンドラを使用します。

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

14
Raul Vejar

上記のコードを利用して、HandleErrorWithElmah属性をすべてのコントローラにインジェクトするカスタムコントローラファクトリを導入することで、さらに一歩先を行くことができます。

より詳しい情報はMVCへのログインに関する私のブログシリーズをチェックしてください。最初の記事では、ElmahをMVC用にセットアップして実行する方法について説明しました。

記事の最後にダウンロード可能なコードへのリンクがあります。それが役立つことを願っています。

http://dotnetdarren.wordpress.com/

7
Darren

完全に代替的な解決策は、MVC HandleErrorAttributeを使用せず、その代わりにElmahが動作するように設計されているASP.Netエラー処理に頼ることです。

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

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

これはMVCルーティングされたURLでも構いませんので、エラーが発生したときに上記はErrorController.Indexアクションにリダイレクトします。

6
Ross McNab

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

それは簡単です!

6
user716264

私にとっては、電子メールのログ記録を機能させることが非常に重要でした。しばらくして、私はこれが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);
        [...]
    }
    [...]
}

これが誰かに役立つことを願っています:)

5
Komio

これがまさに私の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);
}
2
ilmatte