capturar todas as exceções não tratadas em ASP.NET Web Api

113

Como posso capturar todas as exceções não tratadas que ocorrem no ASP.NET Web Api para que eu possa registrá-las?

Até agora tentei:

  • Crie e registre um ExceptionHandlingAttribute
  • Implementar um Application_Errormétodo emGlobal.asax.cs
  • Inscrever-se para AppDomain.CurrentDomain.UnhandledException
  • Inscrever-se para TaskScheduler.UnobservedTaskException

O ExceptionHandlingAttributetrata com sucesso as exceções que são lançadas nos métodos de ação do controlador e filtros de ação, mas outras exceções não são tratadas, por exemplo:

  • Exceções lançadas quando um IQueryablemétodo retornado por um método de ação falha na execução
  • Exceções lançadas por um gerenciador de mensagens (ou seja HttpConfiguration.MessageHandlers)
  • Exceções lançadas ao criar uma instância de controlador

Basicamente, se uma exceção for fazer com que um 500 Erro Interno do Servidor seja retornado ao cliente, quero que seja registrado. A implementação Application_Errorfez bem este trabalho em Web Forms e MVC - o que posso usar na API Web?

Joe Daley
fonte
Você já tentou usar o ASP.NET Health Monitoring ? Basta habilitá-lo e ver se suas exceções não são registradas no log de eventos.
John Saunders de
O monitoramento de integridade captura minhas exceções de pipeline MVC, mas não minhas exceções de pipeline de API da Web.
Joe Daley,
Obrigado - demorei um pouco para descobrir por que não consegui registrar meus problemas de injeção de construtor / dependência, onde pensei que já tinha o registro WebAPI classificado ...
Overflew de

Respostas:

156

Isso agora é possível com WebAPI 2.1 (veja o que há de novo ):

Crie uma ou mais implementações de IExceptionLogger. Por exemplo:

public class TraceExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Em seguida, registre-se com o HttpConfiguration do seu aplicativo, dentro de um retorno de chamada de configuração assim:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

ou diretamente:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
decates
fonte
7
@NeilBarnwell Sim, o Web API 2.1 corresponde ao System.Web.Http assembly versão 5.1.0 . Portanto, você precisa desta versão ou superior para usar a solução descrita aqui. Veja as versões do pacote nuget
data de
2
Certos erros 500 ainda não são detectados por isso, por exemplo. HttpException - o host remoto fechou a conexão. Ainda existe um lugar para global.asax Application_Error lidar com erros fora do processamento da API da web?
Avner
11
Eu amo o quão detalhado o documento oficial é no msdn e o que 99% dos desenvolvedores realmente querem são apenas 8 linhas de código para registrar os erros.
Rocklan
20

A resposta do Yuval é para personalizar respostas a exceções não tratadas capturadas pela API da Web, não para registro, conforme observado na página vinculada . Consulte a seção Quando usar na página para obter detalhes. O logger é sempre chamado, mas o manipulador é chamado apenas quando uma resposta pode ser enviada. Resumindo, use o logger para registrar e o manipulador para personalizar a resposta.

A propósito, estou usando o assembly v5.2.3 e a ExceptionHandlerclasse não tem o HandleCoremétodo. O equivalente, eu acho, é Handle. No entanto, simplesmente criar subclasses ExceptionHandler(como na resposta de Yuval) não funciona. No meu caso, tenho que implementar da IExceptionHandlerseguinte maneira.

internal class OopsExceptionHandler : IExceptionHandler
{
    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    {
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    }

    public IExceptionHandler InnerHandler
    {
        get { return _innerHandler; }
    }

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        Handle(context);

        return Task.FromResult<object>(null);
    }

    public void Handle(ExceptionHandlerContext context)
    {
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    }
}

Observe que, ao contrário do logger, você registra seu manipulador substituindo o manipulador padrão, não adicionando.

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));
Duoc Tran
fonte
1
Esta é uma ótima solução, esta deve ser a solução aceita para 'capturar ou registrar todos os erros'. Eu nunca poderia ter descoberto por que não funcionou para mim quando eu estava apenas estendendo o ExceptionHandler.
Rajiv
Ótima solução. Depois que o pipeline MVC é carregado para uma solicitação, isso funciona muito bem. O IIS ainda lida com as exceções até então, incluindo ao aumentar o OWIN no startup.cs. No entanto, em algum ponto depois que o spin-up termina de processar startup.cs, ele faz seu trabalho maravilhosamente.
Gustyn
18

Para responder à minha própria pergunta, isso não é possível!

Lidar com todas as exceções que causam erros internos do servidor parece um recurso básico que a API da Web deveria ter, portanto, solicitei à Microsoft um gerenciador de erros global para a API da Web :

https://aspnetwebstack.codeplex.com/workitem/1001

Se você concorda, acesse esse link e vote nele!

Nesse ínterim, o excelente artigo Manipulação de exceções da API da Web do ASP.NET mostra algumas maneiras diferentes de detectar algumas categorias diferentes de erro. É mais complicado do que deveria ser e não detecta todos os erros do servidor interno, mas é a melhor abordagem disponível hoje.

Atualização: o tratamento de erros globais agora está implementado e disponível nas compilações noturnas! Ele será lançado em ASP.NET MVC v5.1. Funcionará assim: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

Joe Daley
fonte
parece ser uma razão para usar o controlador para chamadas ajax em vez da API da web .. os limites já estão borrados .. embora se ELMAH for capaz de capturá-lo, talvez haja uma maneira
Sonic Soul
4
O tratamento de erros globais agora foi adicionado na API Web 2.1. Veja minha resposta para mais detalhes.
decata
10

Você também pode criar um manipulador de exceção global implementando a IExceptionHandlerinterface (ou herdar a ExceptionHandlerclasse base). Será o último a ser chamado na cadeia de execução, afinal cadastrado IExceptionLogger:

O IExceptionHandler trata todas as exceções não tratadas de todos os controladores. Este é o último da lista. Se ocorrer uma exceção, o IExceptionLogger será chamado primeiro, depois o controlador ExceptionFilters e, se ainda não for tratado, a implementação de IExceptionHandler.

public class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

Mais sobre isso aqui .

Yuval Itzchakov
fonte
Só por curiosidade, onde está esse registro?
ThunD3eR
-1

Você pode ter blocos try-catch existentes dos quais não tem conhecimento.

Achei que meu novo global.asax.Application_Errormétodo não estava sendo chamado de maneira consistente para exceções não tratadas em nosso código legado.

Em seguida, encontrei alguns blocos try-catch no meio da pilha de chamadas que chamavam Response.Write no texto Exception. Foi isso. Despejou o texto na tela e matou a pedra de exceção.

Portanto, as exceções estavam sendo tratadas, mas o tratamento não estava fazendo nada útil. Depois de remover esses blocos try-catch, as exceções propagadas para o método Application_Error conforme o esperado.

Recurso
fonte