1. ホーム
  2. c#

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

2022-04-21 17:56:03

質問

背景

私は顧客のためにAPIサービスレイヤーを開発しており、すべてのエラーをグローバルにキャッチしてログに残すよう要求されました。

未知のエンドポイント (またはアクション) のようなものは、ELMAH を使用するか、または以下のようなものを Global.asax :

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

. .ルーティングに関連しない未処理エラーはログに残りません。 例えば

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] 属性で指定します。

解決方法は?

<ストライク Web APIがASP.NETアプリケーションの中でホストされている場合、そのAPIを使用するために 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;
        }
    }