C # MVC4 WebAPI uygulaması için tüm dünyadaki TÜM istisnaları nasıl günlüğe kaydederim?


175

Arka fon

Bir istemci için bir API Hizmet Katmanı geliştiriyorum ve tüm hataları küresel olarak yakalayıp günlüğe kaydetmem istendi.

Yani, bilinmeyen bir son nokta (veya eylem) gibi bir şey ELMAH kullanılarak veya aşağıdakilere böyle bir şey ekleyerek kolayca ele alınabilir Global.asax:

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

. . Yönlendirme ile ilgili olmayan .unhandled hataları günlüğe kaydedilmez. Örneğin:

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

Ayrıca, [HandleError]bu filtreyi kaydederek özniteliği ayarlamayı denedim :

filters.Add(new HandleErrorAttribute());

Ancak bu aynı zamanda tüm hataları günlüğe kaydetmez.

Sorun / Soru

/testBunları günlüğe kaydedebilmem için yukarıda çağrılarak oluşturulan hata gibi hataları nasıl kesebilirim? Görünüşe göre bu cevap açık olmalı ama şimdiye kadar aklıma gelen her şeyi denedim.

İdeal olarak, hata günlüğüne, istekte bulunan kullanıcının IP adresi, tarih, saat vb. Gibi bazı şeyler eklemek istiyorum. Ayrıca, bir hatayla karşılaşıldığında destek personeline otomatik olarak e-posta gönderebilmek istiyorum. Bütün bunlar sadece bu hataları meydana geldiklerinde arayabilirsem yapabilirim!

ÇÖZÜLDÜ!

Cevabını kabul ettiğim Darin Dimitrov sayesinde bunu anladım. WebAPI yok değil normal bir MVC denetleyicisi aynı şekilde hataları işlemek.

İşte işe yarayan:

1) Ad alanınıza özel bir filtre ekleyin:

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) Şimdi filtreyi genel olarak WebApiConfig sınıfına kaydedin :

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

VEYA kaydı atlayabilir ve tek bir denetleyiciyi [ExceptionHandling]öznitelikle dekore edebilirsiniz .


Aynı problemim var. İşlenmeyen istisnalar istisna filtresi özniteliğine yakalanır ancak yeni bir istisna attığımda istisna filtresi özniteliğine yakalanmaz, bununla ilgili herhangi bir fikir var mı?
daveBM

1
Myhost / api / undefinedapicontroller hataları gibi bilinmeyen api denetleyicisi çağrıları hala yakalanmıyor. Application_error ve Exception filtre kodu yürütülmez. Onları nasıl yakalayabilirim?
Andrus

1
WebAPI v2.1'e genel hata işleme eklendi.
Cevabımı

1
Bu, "kaynak bulunamadı" gibi bazı durumlarda hataları veya bir denetleyici yapıcısındaki hataları yakalamaz. Buraya bakın: aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/…
Jordan Morris

Merhaba, @ Matt. Cevabı sorunun bir parçası olarak yazdınız, ancak bu SO'daki en iyi uygulama değildir. Burada cevaplar sorudan ayrı olmalıdır. Lütfen bunu ayrı bir cevap olarak yazabilir misiniz (alttaki "Kendi Sorunuzu Cevaplayın" mavi düğmesini kullanabilirsiniz).
sashoalm

Yanıtlar:


56

Web API'niz bir ASP.NET uygulamasının içinde barındırılıyorsa, Application_Erroretkinlik, gösterdiğiniz test işleminde dahil olmak üzere kodunuzdaki tüm işlenmeyen istisnalar için çağrılır. Tek yapmanız gereken Application_Error olayının içindeki bu istisnayı işlemek. Gösterdiğiniz örnek kodda, yalnızca kodda geçerli HttpExceptionolmayan tip istisnasını işlersiniz Convert.ToInt32("a"). Buradaki tüm istisnaları günlüğe kaydettiğinizden ve işlediğinizden emin olun:

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'sında kural dışı durum işleme çeşitli düzeylerde yapılabilir. İşte detailed articlefarklı olasılıkları açıklayan:

  • genel istisna filtresi olarak kaydedilebilen özel istisna filtresi özelliği

    [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"
            });
        }
    }
  • özel eylem invoker

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

Keşke bu kadar basit olsaydı, ama hata hala yakalanmıyor. Karışıklığı önlemek için soruyu güncelledim. Teşekkürler.
Matt Cashatt

@MatthewPatrickCashatt, eğer bu istisna Application_Errorolayda yakalanmazsa, başka bir kodun daha önce tükettiği anlamına gelir. Örneğin, bazı özel HandleErrorAttributes, özel modüller, olabilir ... İstisnaların yakalanıp işlenebileceği başka yerlerin cepheleri var. Ancak bunu yapmak için en iyi yer Application_Error olayıdır, çünkü işlenmeyen tüm istisnalar burada sona erecektir.
Darin Dimitrov

Tekrar teşekkürler, ama ne olursa olsun, /testörnek vurulmaz. İlk satıra bir kesme noktası koydum ( Exception unhandledException = . . .) ancak /testsenaryoda o kesme noktasına vuramıyorum. Ancak sahte bir url koyarsam, kesme noktası vurulur.
Matt Cashatt

1
@MatthewPatrickCashatt, tamamen haklısın. Application_ErrorOlay tüm durumlarda tetiklenir olmayacak çünkü Web API için istisnalar işlemek için doğru yer değil. Bunu başarmak için çeşitli olasılıkları açıklayan çok ayrıntılı bir makale buldum: weblogs.asp.net/fredriknormen/archive/2012/06/11/…
Darin Dimitrov

1
@Darin Dimitrov myhost / api / undefinedapi hataları gibi bilinmeyen api denetleyicisi çağrıları hala yakalanmıyor. Application_error ve Exception filtre kodu yürütülmez. Onları nasıl yakalayabilirim?
Andrus

79

Önceki cevaplara ek olarak.

Dün, ASP.NET Web API 2.1 resmi olarak piyasaya sürüldü .
Global olarak istisnaları ele almak için başka bir fırsat sunuyor.
Ayrıntılar örnekte verilmiştir .

Kısaca, global istisna günlükçilerini ve / veya global istisna işleyicisini (yalnızca bir tane) eklersiniz.
Bunları yapılandırmaya eklersiniz:

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

Ve aydınlanmalarını:

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
Bu mükemmel çalıştı. (Ben logID almak ve kullanıcı yorum ekleyebilirsiniz böylece geri iletmek) aynı anda işlemek ve işlemek, bu yüzden sonuç yeni bir ResponseMessageResult ayarlıyorum. Bu bir süredir beni rahatsız ediyor, teşekkürler.
Brett

8

Neden tekrar vb? Bu çalışır ve hizmet iade durumunu 500 vb.

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

gibi bir hata eylem filtresi gibi bir şey yapmayı düşündün mü

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

ayrıca [HandleError]hata bilgilerini ve günlüğe kaydedilecek diğer tüm ayrıntıları yazabileceğiniz özel bir sürüm de oluşturabilirsiniz


Teşekkürler, ama bunu zaten dünya çapında ayarladım. Yukarıdaki ile aynı sorunu oluşturur, tüm hatalar günlüğe kaydedilmez.
Matt Cashatt

1

Her şeyi bir dene / yakala sarın ve işlenmeyen istisnayı günlüğe kaydedin, sonra iletin. Bunu yapmanın daha iyi bir yerleşik yolu olmadığı sürece.

İşte tüm Catch (işlenmiş veya işlenmemiş) İstisnaları referansı

(düzenle: oh API)


Her ihtimale karşı, istisnayı da yeniden ele alması gerekecekti.
DigCamara

@DigCamara Üzgünüm, bunu anlatmak istediğim buydu. atmak; halletmeliyiz. Başlangıçta "çıkmak veya yeniden yüklemek için karar verin" dedim, daha sonra onun bir API olduğunu fark etmişti. Bu durumda, App geçerek ne yapmak istediğine karar vermek için en iyi.
Tim

1
Bu kötü bir yanıttır, çünkü her eylemde çok sayıda yinelenen kodla sonuçlanacaktır.
Jansky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.