Làm cách nào để tôi ghi nhật ký TẤT CẢ các ngoại lệ trên toàn cầu cho ứng dụng WebAPI C # MVC4?


175

Lý lịch

Tôi đang phát triển Lớp dịch vụ API cho khách hàng và tôi đã được yêu cầu bắt và ghi lại tất cả các lỗi trên toàn cầu.

Vì vậy, trong khi một cái gì đó như một điểm cuối (hoặc hành động) không xác định có thể được xử lý dễ dàng bằng cách sử dụng ELMAH hoặc bằng cách thêm một cái gì đó như thế này vào Global.asax:

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

. . .unhandled lỗi không liên quan đến định tuyến không được ghi lại. Ví dụ:

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

Tôi cũng đã thử thiết lập [HandleError]thuộc tính trên toàn cầu bằng cách đăng ký bộ lọc này:

filters.Add(new HandleErrorAttribute());

Nhưng điều đó cũng không ghi lại tất cả các lỗi.

Vấn đề / câu hỏi

Làm cách nào để chặn các lỗi như lỗi được tạo bằng cách gọi /test ở trên để tôi có thể ghi lại chúng? Có vẻ như câu trả lời này là hiển nhiên, nhưng tôi đã thử mọi cách tôi có thể nghĩ cho đến nay.

Lý tưởng nhất, tôi muốn thêm một số thứ vào việc ghi nhật ký lỗi, chẳng hạn như địa chỉ IP của người dùng yêu cầu, ngày, giờ, v.v. Tôi cũng muốn có thể tự động gửi e-mail cho nhân viên hỗ trợ khi gặp lỗi. Tất cả điều này tôi có thể làm nếu chỉ tôi có thể chặn những lỗi này khi chúng xảy ra!

GIẢI QUYẾT!

Nhờ Darin Dimitrov, người mà tôi đã chấp nhận câu trả lời, tôi đã hiểu ra điều này. WebAPI không xử lý lỗi theo cách tương tự như bộ điều khiển MVC thông thường.

Đây là những gì đã làm việc:

1) Thêm bộ lọc tùy chỉnh vào không gian tên của bạn:

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) Bây giờ hãy đăng ký bộ lọc trên toàn cầu trong lớp 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());
     }
}

HOẶC bạn có thể bỏ qua đăng ký và chỉ trang trí một bộ điều khiển duy nhất với [ExceptionHandling]thuộc tính.


Tôi có cùng một vấn đề. Các ngoại lệ chưa được xử lý bị bắt trong thuộc tính bộ lọc ngoại lệ, nhưng khi tôi ném một ngoại lệ mới, nó không bị bắt trong thuộc tính bộ lọc ngoại lệ, có ý tưởng nào liên quan đến điều đó không?
daveBM

1
Các cuộc gọi bộ điều khiển api không xác định như các lỗi myhost / api / undiniteapicontroll vẫn không bị bắt. Mã bộ lọc Application_error và Exception không được thực thi. Làm thế nào để bắt chúng cũng?
Andrus

1
Xử lý lỗi toàn cầu đã được thêm vào WebAPI v2.1. Xem phản hồi của tôi tại đây: stackoverflow.com/questions/17449400/
Kẻ

1
Điều này sẽ không bắt lỗi trong một số trường hợp, như "không tìm thấy tài nguyên" hoặc lỗi trong hàm tạo của bộ điều khiển. Tham khảo tại đây: aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/ Kẻ
Jordan Morris

Chào Matt. Bạn đã viết câu trả lời như một phần của câu hỏi nhưng đây không phải là cách thực hành tốt nhất trong SO. Ở đây câu trả lời nên tách biệt với câu hỏi. Bạn có thể vui lòng viết nó dưới dạng một câu trả lời riêng biệt (bạn có thể sử dụng nút màu xanh "Trả lời câu hỏi của riêng bạn" ở phía dưới).
sashoalm

Câu trả lời:


56

Nếu API web của bạn được lưu trữ bên trong một ứng dụng ASP.NET, Application_Errorsự kiện sẽ được gọi cho tất cả các ngoại lệ chưa được xử lý trong mã của bạn, bao gồm cả ngoại lệ trong hành động kiểm tra mà bạn đã thể hiện. Vì vậy, tất cả những gì bạn phải làm là xử lý ngoại lệ này trong sự kiện Application_Error. Trong mã mẫu bạn đã chỉ ra, bạn chỉ xử lý ngoại lệ của loại HttpExceptionrõ ràng không phải là trường hợp với Convert.ToInt32("a")mã. Vì vậy, hãy chắc chắn rằng bạn đăng nhập và xử lý tất cả các ngoại lệ trong đó:

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

Xử lý ngoại lệ trong API Web có thể được thực hiện ở nhiều cấp độ khác nhau. Đây là một detailed articlegiải thích các khả năng khác nhau:

  • thuộc tính bộ lọc ngoại lệ tùy chỉnh có thể được đăng ký làm bộ lọc ngoại lệ toàn cầu

    [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"
            });
        }
    }
  • kẻ xâm lược hành động tùy chỉnh

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

Tôi ước nó đơn giản như vậy, nhưng lỗi vẫn không bị bắt. Tôi đã cập nhật câu hỏi để tránh nhầm lẫn. Cảm ơn.
Matt Cashatt

@MatthewPatrickCashatt, nếu ngoại lệ này không bị bắt trong Application_Errorsự kiện, điều này có nghĩa là một số mã khác đang tiêu thụ nó trước đó. Ví dụ: bạn có thể có một số Xử lý tùy chỉnh, mô-đun tùy chỉnh, ... Có những ánh mắt của những nơi khác, nơi các ngoại lệ có thể bị bắt và xử lý. Nhưng nơi tốt nhất để làm điều đó là sự kiện Application_Error, bởi vì đó là nơi tất cả các ngoại lệ chưa được xử lý sẽ kết thúc.
Darin Dimitrov

Cảm ơn một lần nữa, nhưng không có vấn đề gì, /teství dụ này không bị ảnh hưởng. Tôi đã đặt một điểm dừng trên dòng đầu tiên ( Exception unhandledException = . . .) nhưng không thể đạt điểm dừng đó trong /testkịch bản. Tuy nhiên, nếu tôi đặt một url không có thật, điểm dừng sẽ bị tấn công.
Matt Cashatt

1
@MatthewPatrickCashatt, bạn hoàn toàn đúng. Sự Application_Errorkiện không phải là nơi chính xác để xử lý các ngoại lệ cho API Web vì nó sẽ không được kích hoạt trong mọi trường hợp. Tôi đã tìm thấy một bài viết rất chi tiết giải thích các khả năng khác nhau để đạt được điều đó: weblogs.asp.net/fredriknormen/archive/2012/06/11/...
Darin Dimitrov

1
@Darin Dimitrov Các cuộc gọi bộ điều khiển api không xác định như myhost / api / undiniteapi lỗi vẫn không được bắt. Mã bộ lọc Application_error và Exception không được thực thi. Làm thế nào để bắt chúng cũng?
Andrus

79

Như một bổ sung cho câu trả lời trước.

Hôm qua, ASP.NET Web API 2.1 đã được phát hành chính thức .
Nó cung cấp một cơ hội khác để xử lý các trường hợp ngoại lệ trên toàn cầu.
Các chi tiết được đưa ra trong mẫu .

Tóm lại, bạn thêm logger ngoại lệ toàn cầu và / hoặc xử lý ngoại lệ toàn cầu (chỉ một).
Bạn thêm chúng vào cấu hình:

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

Và nhận ra của họ:

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
Điều này làm việc hoàn hảo. Tôi đăng nhập và xử lý đồng thời (vì tôi nhận được logID và chuyển lại để người dùng có thể thêm bình luận), vì vậy tôi đang đặt Kết quả cho một FeedbackMessageResult mới. Điều này đã được tôi làm phiền trong một thời gian, cảm ơn.
Brett

8

Tại sao lại suy nghĩ vv? Điều này hoạt động và nó sẽ làm cho dịch vụ trở lại trạng thái 500 vv

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

Bạn đã nghĩ về việc làm một cái gì đó như một bộ lọc hành động xử lý lỗi như

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

bạn cũng có thể tạo một phiên bản tùy chỉnh [HandleError]mà bạn có thể viết thông tin lỗi và tất cả các chi tiết khác để đăng nhập


Cảm ơn, nhưng tôi đã thiết lập nó trên toàn cầu. Nó đặt ra vấn đề tương tự như trên, không phải tất cả các lỗi đều được ghi lại.
Matt Cashatt

1

Gói toàn bộ trong một thử / bắt và đăng nhập ngoại lệ chưa xử lý, sau đó chuyển nó vào. Trừ khi có một cách tích hợp tốt hơn để làm điều đó.

Đây là một tham chiếu Catch All (xử lý hoặc chưa xử lý) Ngoại lệ

(chỉnh sửa: oh API)


Chỉ trong trường hợp, anh ta cũng cần phải suy nghĩ lại ngoại lệ.
DigCamara

@DigCamara Xin lỗi, đó là những gì tôi muốn nói. phi; nên xử lý việc đó. Ban đầu tôi đã nói "quyết định nên thoát hay tải lại", sau đó nhận ra rằng anh ta đã nói đó là API. Trong trường hợp đó, tốt nhất là để Ứng dụng quyết định những gì nó muốn làm bằng cách chuyển nó vào.
Tim

1
Đây là một câu trả lời tồi vì nó sẽ dẫn đến vô số mã trùng lặp trong mọi hành động.
Jansky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.