C#MVC4 WebAPIアプリのすべての例外をグローバルに記録するにはどうすればよいですか?


175

バックグラウンド

クライアント用のAPIサービスレイヤーを開発していて、すべてのエラーをグローバルにキャッチしてログに記録するように依頼されました。

したがって、不明なエンドポイント(またはアクション)のようなものは、ELMAHを使用するか、次のようなものをに追加することによって簡単に処理されGlobal.asaxます。

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

。。ルーティングに関連しない.unhandledエラーはログに記録されません。例えば:

public class ReportController : ApiController
{
    public int test()
    {
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    }
}

また、[HandleError]このフィルターを登録して、属性をグローバルに設定してみました。

filters.Add(new HandleErrorAttribute());

ただし、すべてのエラーがログに記録されるわけではありません。

問題/質問

/test上記の呼び出しで生成されたようなエラーをインターセプトして、ログに記録するにはどうすればよいですか?この答えは明白であるように思われますが、私はこれまで考えられるすべてのことを試みました。

理想的には、要求しているユーザーのIPアドレス、日付、時刻など、いくつかをエラーログに追加したいと思います。また、エラーが発生したときにサポートスタッフに自動的にメールを送信できるようにしたいと考えています。エラーが発生したときにこれらのエラーをインターセプトできる場合にのみ、これらすべてを実行できます。

解決しました!

その答えを受け入れたDarin Dimitrovのおかげで、これを理解できました。 WebAPIは、通常のMVCコントローラーと同じ方法でエラーを処理しませ

これがうまくいきました:

1)名前空間にカスタムフィルターを追加します。

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is BusinessException)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            });

        }

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        });
    }
}

2)WebApiConfigクラスでグローバルにフィルターを登録します。

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
         config.Filters.Add(new ExceptionHandlingAttribute());
     }
}

または、登録をスキップして、単一のコントローラーを[ExceptionHandling]属性で装飾することもできます。


私は同じ問題を抱えています。未処理の例外は例外フィルター属性でうまく捕捉されますが、新しい例外をスローしたときに例外フィルター属性で捕捉されません、それに関して何か考えはありますか?
daveBM 2013年

1
myhost / api / undefinedapicontrollerエラーのような不明なAPIコントローラー呼び出しはまだキャッチされません。Application_errorおよびExceptionフィルターコードは実行されません。それらをどのように捕まえるのですか?
Andrus 2013年

1
グローバルエラー処理がWebAPI v2.1に追加されました。ここに私の応答を参照してください:stackoverflow.com/questions/17449400/...
DarrellNorton

1
これは、「リソースが見つかりません」などの状況やコントローラーコンストラクターのエラーをキャッチしません。ここで参照してください:aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/...
ヨルダンモリス

こんにちは、@マット。質問の一部として回答を書きましたが、これはSOのベストプラクティスではありません。ここでは、回答は質問とは別にする必要があります。別の回答として書いてください(下部にある[自分の質問に答える]青いボタンを使用できます)。
sashoalm

回答:


56

Web APIがASP.NETアプリケーション内でホストされている場合、表示さApplication_Errorれているテストアクションの例外を含め、コード内のすべての未処理の例外に対してイベントが呼び出されます。したがって、Application_Errorイベント内でこの例外を処理するだけです。あなたが示したサンプルコードHttpExceptionでは、タイプの例外のみを処理していることは明らかですが、Convert.ToInt32("a")コードではそうではありません。そのため、すべての例外をログに記録して処理するようにしてください。

protected void Application_Error()
{
    Exception unhandledException = Server.GetLastError();
    HttpException httpException = unhandledException as HttpException;
    if (httpException == null)
    {
        Exception innerException = unhandledException.InnerException;
        httpException = innerException as HttpException;
    }

    if (httpException != null)
    {
        int httpCode = httpException.GetHttpCode();
        switch (httpCode)
        {
            case (int)HttpStatusCode.Unauthorized:
                Response.Redirect("/Http/Error401");
                break;

            // TODO: don't forget that here you have many other status codes to test 
            // and handle in addition to 401.
        }
        else
        {
            // It was not an HttpException. This will be executed for your test action.
            // Here you should log and handle this case. Use the unhandledException instance here
        }
    }
}

Web APIでの例外処理は、さまざまなレベルで実行できます。ここだdetailed articleさまざまな可能性を説明するには:

  • グローバル例外フィルターとして登録できるカスタム例外フィルター属性

    [AttributeUsage(AttributeTargets.All)]
    public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is BusinessException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "Exception"
                });
            }
    
            //Log Critical errors
            Debug.WriteLine(context.Exception);
    
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("An error occurred, please try again or contact the administrator."),
                ReasonPhrase = "Critical Exception"
            });
        }
    }
  • カスタムアクションインボーカー

    public class MyApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
    
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.GetBaseException();
    
                if (baseException is BusinessException)
                {
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Error"
    
                    });
                }
                else
                {
                    //Log critical error
                    Debug.WriteLine(baseException);
    
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Critical Error"
                    });
                }
            }
    
            return result;
        }
    }

私はそれが単純であることを望みますが、エラーはまだ捕捉されていません。混乱を避けるために質問を更新しました。ありがとう。
Matt Cashatt 2013年

@MatthewPatrickCashatt。この例外がApplication_Errorイベントでキャッチされない場合、これは他のコードが以前にそれを消費していることを意味します。たとえば、いくつかのカスタムHandleErrorAttributes、カスタムモジュールなどがある場合があります。他にも、例外をキャッチして処理できる場所が何十億とあります。ただし、これを行うのに最適な場所はApplication_Errorイベントです。これにより、すべての未処理の例外が終了します。
Darin Dimitrov 2013年

改めて感謝しますが、/test例が何であれヒットしません。最初の行(Exception unhandledException = . . .)にブレークポイントを設定しましたが、/testシナリオでそのブレークポイントにヒットできません。ただし、偽のURLを入力すると、ブレークポイントがヒットします。
Matt Cashatt 2013

1
@MatthewPatrickCashatt、あなたは完全に正しいです。このApplication_Errorイベントは、すべての場合にトリガーされるわけではないため、Web APIの例外を処理するには適切な場所ではありません。:私はそれを達成するために様々な可能性を説明する非常に詳細な記事を発見したweblogs.asp.net/fredriknormen/archive/2012/06/11/...
ダーリンディミトロフを

1
@Darin Dimitrov myhost / api / undefinedapiエラーなどの不明なAPIコントローラー呼び出しはまだキャッチされません。Application_errorおよびExceptionフィルターコードは実行されません。それらをどのように捕まえるのですか?
Andrus 2013年

79

以前の回答への追加として。

昨日、ASP.NET Web API 2.1が正式にリリースされました
グローバルに例外を処理する別の機会を提供します。
詳細はサンプルに記載されています

簡単に言えば、グローバル例外ロガーやグローバル例外ハンドラー(あるいはその両方)を追加します。
それらを構成に追加します。

public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());
}

そしてその実現:

public class ElmahExceptionLogger : ExceptionLogger
{
  public override void Log(ExceptionLoggerContext context)
  {
    ...
  }
}

public class GenericTextExceptionHandler : ExceptionHandler
{
  public override void Handle(ExceptionHandlerContext context)
  {
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  }
}

2
これは完全に機能しました。同時にログに記録して処理するため(ユーザーがコメントを追加できるようにlogIDを取得して渡すため)、Resultを新しいResponseMessageResultに設定しています。これはしばらくの間私を悩ませてきました、ありがとう。
Brett

8

なぜ再投げるのか?これは機能し、サービスの戻りステータスは500などになります

public class LogExceptionFilter : ExceptionFilterAttribute
{
    private static readonly ILog log = LogManager.GetLogger(typeof (LogExceptionFilter));

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        log.Error("Unhandeled Exception", actionExecutedContext.Exception);
        base.OnException(actionExecutedContext);
    }
}

2

あなたはハンドルエラーアクションフィルターのような何かをすることを考えましたか?

[HandleError]
public class BaseController : Controller {...}

[HandleError]エラー情報やその他すべての詳細をログに書き込むことができるカスタムバージョンを作成することもでき ます


おかげで、私はすでにグローバルに設定しています。上記と同じ問題が発生し、すべてのエラーがログに記録されるわけではありません。
Matt Cashatt 2013年

1

すべてをtry / catchでラップし、未処理の例外をログに記録してから渡します。それを行うためのより良い組み込みの方法がない限り。

これがリファレンスです すべてキャッチ(処理済みまたは未処理)例外です

(編集:ああAPI)


念のため、例外も再スローする必要があります。
DigCamara 2013年

@DigCamaraすみません、それは私がそれを渡すことによって意味したものです。スロー; それを処理する必要があります。私は最初に「終了するか再ロードするかを決める」と言ったが、それがAPIだと彼が言っていたことに気付いた。その場合、アプリに渡して、何をしたいかをアプリに決定させるのが最善です。
Tim

1
すべてのアクションで重複したコードがロードされるため、これは悪い答えです。
ジャンスキー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.