ฉันจะบันทึกข้อยกเว้นทั้งหมดแบบโกลบอลสำหรับแอป C # MVC4 WebAPI ได้อย่างไร


175

พื้นหลัง

ฉันกำลังพัฒนา API Service Layer สำหรับลูกค้าและฉันถูกขอให้ตรวจจับและบันทึกข้อผิดพลาดทั้งหมดทั่วโลก

ดังนั้นในขณะที่บางสิ่งเช่นปลายทางที่ไม่รู้จัก (หรือการกระทำ) สามารถจัดการได้อย่างง่ายดายโดยใช้ 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 ของผู้ใช้ที่ขอวันที่เวลาและอื่น ๆ ฉันยังต้องการให้เจ้าหน้าที่สนับสนุนทางอีเมลโดยอัตโนมัติเมื่อพบข้อผิดพลาด ทั้งหมดนี้ฉันสามารถทำได้ก็ต่อเมื่อฉันสามารถดักจับข้อผิดพลาดเหล่านี้ได้เมื่อมันเกิดขึ้น!

แก้ไข!

ขอบคุณดารินดิมิทรอฟที่ตอบรับฉันได้รับการคำนวณนี้ 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

1
การเรียกตัวควบคุม api ที่ไม่รู้จักเช่นข้อผิดพลาดmyhost / api / undefinedapicontrollerยังไม่ได้รับการตรวจจับ Application_error และรหัสตัวกรองข้อยกเว้นไม่ได้ถูกเรียกใช้ วิธีจับพวกเขายัง?
Andrus

1
การจัดการข้อผิดพลาดระดับโลกถูกเพิ่มใน WebAPI v2.1 ดูคำตอบของฉันที่นี่: stackoverflow.com/questions/17449400/…
DarrellNorton

1
สิ่งนี้จะไม่ตรวจจับข้อผิดพลาดในบางสถานการณ์เช่น "ไม่พบทรัพยากร" หรือข้อผิดพลาดในตัวสร้างคอนโทรลเลอร์ อ้างอิงที่นี่: aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/ …
Jordan Morris

สวัสดี @Matt คุณได้เขียนคำตอบเป็นส่วนหนึ่งของคำถาม แต่นี่ไม่ใช่วิธีปฏิบัติที่ดีที่สุดใน SO คำตอบที่นี่ควรแยกจากคำถาม คุณกรุณาช่วยเขียนเป็นคำตอบแยกต่างหาก (คุณสามารถใช้ปุ่มสีฟ้า "ตอบคำถามของคุณเอง" ที่ด้านล่าง)
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
        }
    }
}

การจัดการข้อยกเว้นใน 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

@MatthewPatrickCashatt หากข้อยกเว้นนี้ไม่ได้รับการติดในApplication_Errorเหตุการณ์ซึ่งหมายความว่าบางรหัสอื่น ๆ ที่จะใช้มันก่อน ตัวอย่างเช่นคุณอาจมี HandleErrorAttributes ที่กำหนดเองโมดูลที่กำหนดเองบางส่วน ... มี gazillions ของสถานที่อื่นที่สามารถจับและจัดการข้อยกเว้นได้ แต่ที่ดีที่สุดที่จะทำคือเหตุการณ์ Application_Error เพราะนั่นคือที่ที่ข้อยกเว้นไม่ได้จัดการทั้งหมดจะสิ้นสุดลง
ดารินทร์ดิมิทรอฟ

ขอขอบคุณอีกครั้ง แต่ไม่ว่าอะไรก็ตาม/testตัวอย่างก็ไม่ได้รับผลกระทบ ฉันใส่เบรกพอยต์ในบรรทัดแรก ( Exception unhandledException = . . .) แต่ไม่สามารถกดเบรกพอยต์นั้นใน/testสถานการณ์ได้ อย่างไรก็ตามถ้าฉันใส่ url ปลอมเข้าไปจุดพักนั้นจะถูกโจมตี
Matt Cashatt

1
@MatthewPatrickCashatt คุณพูดถูก Application_Errorเหตุการณ์ไม่ได้เป็นสถานที่ถูกต้องเพื่อจัดการกับข้อยกเว้นสำหรับ Web API เพราะมันจะไม่ถูกเรียกในทุกกรณี ฉันได้พบบทความที่มีรายละเอียดมากซึ่งอธิบายถึงความเป็นไปได้ที่หลากหลายเพื่อให้บรรลุ: weblogs.asp.net/fredriknormen/archive/2012/06/11/…
Darin Dimitrov

1
@Darin Dimitrov ไม่รู้จักการเรียกใช้ตัวควบคุม api เช่นข้อผิดพลาดmyhost / api / undefinedapiยังไม่ได้รับการตรวจจับ 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

ล้อมสิ่งทั้งหมดไว้ในการลอง / จับและบันทึกข้อยกเว้นที่ไม่สามารถจัดการได้จากนั้นส่งต่อไป เว้นแต่จะมีวิธีที่ดีกว่าในตัวที่จะทำ

นี่คือการอ้างอิงข้อยกเว้นทั้งหมด (จัดการหรือจัดการ)

(แก้ไข: oh API)


ในกรณีที่เขาต้องการ rethrow ข้อยกเว้นเช่นกัน
DigCamara

@DigCamara ขออภัยนั่นคือสิ่งที่ฉันหมายถึงโดยผ่านมันไป โยน; ควรจัดการกับสิ่งนั้น เดิมทีฉันพูดว่า "ตัดสินใจว่าจะออกหรือโหลดซ้ำ" จากนั้นก็รู้ว่าเขาบอกว่าเป็น API ในกรณีดังกล่าวให้ดีที่สุดที่จะให้แอปตัดสินใจว่าต้องการทำอะไรโดยส่งต่อไป
ทิม

1
นี่เป็นคำตอบที่ไม่ดีเพราะมันจะส่งผลให้โหลดรหัสซ้ำในทุกการกระทำ
Jansky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.