Bagaimana cara saya mencatat SEMUA pengecualian secara global untuk aplikasi C # MVC4 WebAPI?

175

Latar Belakang

Saya mengembangkan Lapisan Layanan API untuk klien dan saya telah diminta untuk menangkap dan mencatat semua kesalahan secara global.

Jadi, sementara sesuatu seperti titik akhir yang tidak diketahui (atau tindakan) mudah ditangani dengan menggunakan ELMAH atau dengan menambahkan sesuatu seperti ini ke Global.asax:

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

. . Kesalahan .unhandled yang tidak terkait dengan perutean tidak bisa login. Sebagai contoh:

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

Saya juga telah mencoba mengatur [HandleError]atribut secara global dengan mendaftarkan filter ini:

filters.Add(new HandleErrorAttribute());

Tapi itu juga tidak mencatat semua kesalahan.

Masalah / pertanyaan

Bagaimana cara mencegat kesalahan seperti yang dihasilkan oleh panggilan di /testatas sehingga saya bisa mencatatnya? Tampaknya jawaban ini harus jelas, tetapi saya telah mencoba semua yang dapat saya pikirkan sejauh ini.

Idealnya, saya ingin menambahkan beberapa hal ke log kesalahan, seperti alamat IP pengguna yang meminta, tanggal, waktu, dan sebagainya. Saya juga ingin dapat mengirim email kepada staf pendukung secara otomatis ketika kesalahan terjadi. Semua ini bisa saya lakukan seandainya saya bisa mencegat kesalahan ini ketika itu terjadi!

TERSELESAIKAN!

Terima kasih kepada Darin Dimitrov, yang jawabannya saya terima, saya mengerti hal ini. WebAPI tidak menangani kesalahan dengan cara yang sama seperti pengontrol MVC biasa.

Inilah yang berhasil:

1) Tambahkan filter khusus ke namespace Anda:

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) Sekarang daftarkan filter secara global di kelas 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());
     }
}

ATAU Anda dapat melewati pendaftaran dan hanya menghiasi satu pengontrol dengan [ExceptionHandling]atribut.

Matt Cashatt
sumber
Saya memiliki masalah yang sama. Pengecualian yang tidak tertangani terjebak dalam atribut filter pengecualian, tetapi ketika saya melempar pengecualian baru, ia tidak terperangkap dalam atribut filter pengecualian, ada ide yang berkaitan dengan itu?
daveBM
1
Panggilan pengontrol api yang tidak dikenal seperti kesalahan myhost / api / undefinedapicontroller masih belum diketahui. Kode filter Application_error dan Pengecualian tidak dijalankan. Bagaimana cara menangkapnya juga?
Andrus
1
Penanganan kesalahan global telah ditambahkan ke WebAPI v2.1. Lihat tanggapan saya di sini: stackoverflow.com/questions/17449400/…
DarrellNorton
1
Ini tidak akan menangkap kesalahan dalam beberapa keadaan, seperti "sumber daya tidak ditemukan", atau kesalahan dalam konstruktor pengontrol. Lihat di sini: aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/…
Jordan Morris
Hai, @Matt. Anda telah menulis jawaban sebagai bagian dari pertanyaan, tetapi ini bukan praktik terbaik di SO. Di sini jawaban harus terpisah dari pertanyaan. Bisakah Anda menuliskannya sebagai jawaban terpisah (Anda dapat menggunakan tombol biru "Jawab Pertanyaan Anda Sendiri" di bagian bawah).
sashoalm

Jawaban:

56

Jika API web Anda di-host di dalam aplikasi ASP.NET, Application_Erroracara tersebut akan dipanggil untuk semua pengecualian yang tidak ditangani dalam kode Anda, termasuk yang ada dalam tindakan uji yang telah Anda tunjukkan. Jadi yang harus Anda lakukan adalah menangani pengecualian ini di dalam acara Application_Error. Dalam kode sampel yang telah Anda tunjukkan, Anda hanya menangani pengecualian tipe HttpExceptionyang jelas tidak demikian dengan Convert.ToInt32("a")kode tersebut. Jadi pastikan Anda masuk dan menangani semua pengecualian di sana:

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

Penanganan pengecualian di API Web dapat dilakukan di berbagai tingkatan. Berikut ini detailed articlepenjelasan berbagai kemungkinan:

  • atribut filter pengecualian khusus yang dapat didaftarkan sebagai filter pengecualian global

    [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"
            });
        }
    }
  • penyerbu tindakan khusus

    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;
        }
    }
Darin Dimitrov
sumber
Saya berharap itu sesederhana itu, tetapi kesalahannya masih belum tertangkap. Saya telah memperbarui pertanyaan untuk menghindari kebingungan. Terima kasih.
Matt Cashatt
@ MatthewPatrickCashatt, jika pengecualian ini tidak tertangkap dalam Application_Erroracara tersebut, ini berarti bahwa beberapa kode lain mengkonsumsinya sebelumnya. Misalnya, Anda mungkin memiliki beberapa HandleErrorAttributes kustom, modul khusus, ... Ada gazillions tempat lain di mana pengecualian dapat ditangkap dan ditangani. Tapi tempat terbaik untuk melakukannya adalah acara Application_Error, karena di situlah semua pengecualian yang tidak tertangani akan berakhir.
Darin Dimitrov
Terima kasih lagi, tapi apa pun yang terjadi, /testcontohnya tidak kena. Saya telah meletakkan breakpoint pada baris pertama ( Exception unhandledException = . . .) tetapi tidak bisa mengenai breakpoint itu dalam /testskenario. Jika saya memasukkan url palsu, bagaimanapun, breakpoint terkena.
Matt Cashatt
1
@ MatthewPatrickCashatt, Anda sepenuhnya benar. The Application_Erroracara bukan tempat yang tepat untuk menangani pengecualian untuk Web API karena tidak akan memicu dalam semua kasus. Saya telah menemukan artikel yang sangat rinci menjelaskan berbagai kemungkinan untuk mencapai itu: weblogs.asp.net/fredriknormen/archive/2012/06/11/…
Darin Dimitrov
1
@Darin Dimitrov Panggilan pengontrol api yang tidak dikenal seperti kesalahan myhost / api / undefinedapi masih belum diketahui. Kode filter Application_error dan Pengecualian tidak dijalankan. Bagaimana cara menangkapnya juga?
Andrus
79

Sebagai tambahan untuk jawaban sebelumnya.

Kemarin, ASP.NET Web API 2.1 secara resmi dirilis .
Ini menawarkan kesempatan lain untuk menangani pengecualian secara global.
Rinciannya diberikan dalam sampel .

Secara singkat, Anda menambahkan log pengecualian global dan / atau handler pengecualian global (hanya satu).
Anda menambahkannya ke konfigurasi:

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

Dan realisasinya:

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);
  }
}
Vladimir
sumber
2
Ini bekerja dengan sempurna. Saya masuk dan menanganinya secara bersamaan (karena saya mendapatkan logID dan meneruskannya kembali sehingga pengguna dapat menambahkan komentar), jadi saya mengatur Hasil ke ResponseMessageResult yang baru. Ini telah mengganggu saya untuk sementara waktu, terima kasih.
Brett
8

Mengapa rethrow dll? Ini berfungsi dan itu akan membuat status pengembalian layanan 500 dll

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);
    }
}
Anders
sumber
2

pernahkah Anda berpikir untuk melakukan sesuatu seperti filter tindakan penanganan kesalahan

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

Anda juga dapat membuat versi khusus untuk [HandleError]menulis info kesalahan dan semua detail lainnya untuk dicatat

DINGIN MEMBERITAHU
sumber
Terima kasih, tapi saya sudah menetapkannya secara global. Ini menimbulkan masalah yang sama seperti di atas, tidak semua kesalahan dicatat.
Matt Cashatt
1

Bungkus semuanya dengan mencoba / menangkap dan mencatat pengecualian yang tidak tertangani, kemudian meneruskannya. Kecuali ada cara built-in yang lebih baik untuk melakukannya.

Berikut referensi Pengecualian Catch All (ditangani atau tidak ditangani)

(edit: oh API)

Tim
sumber
Untuk berjaga-jaga, dia perlu memikirkan kembali pengecualian itu juga.
DigCamara
@DigCamara Maaf, itulah yang saya maksudkan dengan meneruskannya. melemparkan; harus mengatasinya. Saya awalnya mengatakan "memutuskan apakah akan keluar atau memuat ulang," kemudian menyadari bahwa dia mengatakan itu adalah API. Dalam hal ini, sebaiknya biarkan Aplikasi memutuskan apa yang ingin dilakukan dengan meneruskannya.
Tim
1
Ini adalah jawaban yang buruk karena akan menghasilkan banyak kode yang digandakan dalam setiap tindakan.
Jansky