C # MVC4 WebAPI 앱에 대해 모든 예외를 전체적으로 어떻게 기록합니까?


175

배경

클라이언트를위한 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]속성 으로 단일 컨트롤러를 장식 할 수 있습니다 .


나도 같은 문제가있어. 처리되지 않은 예외는 예외 필터 속성에서 잘 잡히지 만 새 예외를 throw하면 예외 필터 속성에서 잡히지 않습니다.
daveBM

1
myhost / api / undefinedapicontroller 오류 와 같은 알 수없는 API 컨트롤러 호출 은 여전히 ​​포착되지 않습니다. Application_error 및 예외 필터 코드가 실행되지 않습니다. 그들을 잡는 방법?
Andrus

1
전역 오류 처리가 WebAPI v2.1에 추가되었습니다. 내 답변보기 here : stackoverflow.com/questions/17449400/…
DarrellNorton

1
이것은 "자원을 찾을 수 없음"과 같은 일부 상황에서 또는 컨트롤러 생성자의 오류와 같은 오류를 포착하지 않습니다. 여기를 참조하십시오 : aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/…
Jordan Morris

안녕하세요, @Matt. 당신은 질문의 일부로 답변을 작성했지만 이것이 최선의 방법은 아닙니다. 여기서 답변은 질문과 분리되어야합니다. 별도의 답변으로 작성해 주시겠습니까 (아래에있는 "자신의 질문에 답변"파란색 버튼을 사용할 수 있음).
sashoalm

답변:


56

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

웹 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

@MatthewPatrickCashatt,이 예외가 Application_Error이벤트에 걸리지 않으면 다른 코드가 이전에 소비하고 있음을 의미합니다. 예를 들어, 일부 사용자 정의 HandleErrorAttributes, 사용자 정의 모듈 등이있을 수 있습니다. 예외를 포착하고 처리 할 수있는 다른 장소에는 여러 가지가 있습니다. 그러나 가장 좋은 방법은 Application_Error 이벤트입니다. 처리되지 않은 모든 예외가 끝나기 때문입니다.
Darin Dimitrov

다시 한 번 감사드립니다.하지만 어쨌든 /test예제는 맞지 않습니다. 첫 번째 줄 ( Exception unhandledException = . . .) 에 중단 점을 넣었 지만 /test시나리오 에서 해당 중단 점에 도달 할 수 없습니다 . 그러나 가짜 URL을 넣으면 중단 점이 발생합니다.
Matt Cashatt

1
@ MatthewPatrickCashatt, 당신은 완전히 맞아요. Application_Error이벤트는이 모든 경우에 트리거되지 않기 때문에 웹 API에 대한 예외를 처리 할 수있는 올바른 장소가 아니다. : 그 달성하기 위해 다양한 가능성을 설명하는 매우 상세한 기사 발견 weblogs.asp.net/fredriknormen/archive/2012/06/11/...
대린 디미트로프을

1
@Darin Dimitrov myhost / api / undefinedapi 오류 와 같은 알 수없는 API 컨트롤러 호출 은 여전히 포착 되지 않습니다. Application_error 및 예외 필터 코드가 실행되지 않습니다. 그들을 잡는 방법?
Andrus

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를 가져 와서 사용자가 주석을 추가 할 수 있도록 다시 전달하기 때문에) 동시에 기록하고 처리하므로 결과를 새로운 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

1

모든 것을 try / catch로 감싸고 처리되지 않은 예외를 기록한 다음 전달하십시오. 더 나은 기본 제공 방법이 없다면.

다음은 모두 처리 된 (처리 된 또는 처리되지 않은) 예외를 참조합니다.

(편집 : 오 API)


만일을 대비하여, 그는 예외를 다시 던져야 할 것이다.
DigCamara

@DigCamara 죄송합니다. 합격 한 것입니다. 던지다; 처리해야합니다. 나는 원래 "종료 또는 재 장전 여부 결정"이라고 말한 후 API라고 말했다. 이 경우 앱이 전달하여 원하는 것을 결정하도록하는 것이 가장 좋습니다.
Tim

1
이것은 모든 조치에 중복 된 코드가로드되기 때문에 잘못된 대답입니다.
Jansky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.