Como fazer com que o ELMAH funcione com o atributo ASP.NET MVC [HandleError]?

564

Estou tentando usar o ELMAH para registrar erros no meu aplicativo ASP.NET MVC, no entanto, quando eu uso o atributo [HandleError] nos meus controladores, o ELMAH não registra nenhum erro quando eles ocorrem.

Como eu estou supondo que é porque o ELMAH registra apenas erros sem tratamento e o atributo [HandleError] está manipulando o erro, portanto, não há necessidade de registrá-lo.

Como modifico ou como modificaria o atributo para que o ELMAH saiba que houve um erro e o registre.

Edit: Deixe-me garantir que todos entendam, eu sei que posso modificar o atributo que não é a pergunta que estou fazendo ... O ELMAH é ignorado ao usar o atributo handleerror, o que significa que não verá que houve um erro porque foi tratado já pelo atributo ... O que estou perguntando é que existe uma maneira de fazer com que o ELMAH veja o erro e registre-o, mesmo que o atributo tenha manipulado ... Pesquisei e não vi nenhum método para ligar para forçá-lo a registrar o erro....

dswatik
fonte
12
Uau, espero que Jeff ou Jared respondam a essa pergunta. Eles estão usando ELMAH para Stackoverflow;)
Jon Limjap
11
Hmm, estranho - não usamos o HandleErrorAttribute - o Elmah está configurado na seção <modules> do web.config. Existem benefícios em usar o HandleErrorAttribute?
Jarrod Dixon
9
@ Jarrod - seria bom ver o que é "personalizado" no seu garfo ELMAH.
23711 Scott Scottelelman
3
@dswatik Você também pode impedir redirecionamentos configurando redirectMode como ResponseRewrite em web.config. Veja blog.turlov.com/2009/01/…
Pavel Chuchuva
6
Eu continuei correndo para a documentação da Web e postagens falando sobre o atributo [HandleError] e Elmah, mas não vi o comportamento que isso resolve (por exemplo, Elmah não registrou o erro "manipulado") quando configuro o caso fictício. Isso ocorre porque, a partir do Elmah.MVC 2.0.x, esse HandleErrorAttribute personalizado não é mais necessário; está incluído no pacote nuget.
plyawn

Respostas:

503

Você pode subclassificar HandleErrorAttributee substituir seu OnExceptionmembro (sem necessidade de copiar) para que ele registre a exceção no ELMAH e somente se a implementação básica lidar com isso. A quantidade mínima de código necessária é a seguinte:

using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled) 
            return;
        var httpContext = context.HttpContext.ApplicationInstance.Context;
        var signal = ErrorSignal.FromContext(httpContext);
        signal.Raise(context.Exception, httpContext);
    }
}

A implementação base é invocada primeiro, dando a chance de marcar a exceção como sendo manipulada. Somente então a exceção é sinalizada. O código acima é simples e pode causar problemas se usado em um ambiente onde o HttpContextpode não estar disponível, como teste. Como resultado, você desejará um código mais defensivo (ao custo de ser um pouco mais longo):

using System.Web;
using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled       // if unhandled, will be logged anyhow
            || TryRaiseErrorSignal(context) // prefer signaling, if possible
            || IsFiltered(context))         // filtered?
            return;

        LogException(context);
    }

    private static bool TryRaiseErrorSignal(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        if (httpContext == null)
            return false;
        var signal = ErrorSignal.FromContext(httpContext);
        if (signal == null)
            return false;
        signal.Raise(context.Exception, httpContext);
        return true;
    }

    private static bool IsFiltered(ExceptionContext context)
    {
        var config = context.HttpContext.GetSection("elmah/errorFilter")
                        as ErrorFilterConfiguration;

        if (config == null)
            return false;

        var testContext = new ErrorFilterModule.AssertionHelperContext(
                              context.Exception, 
                              GetHttpContextImpl(context.HttpContext));
        return config.Assertion.Test(testContext);
    }

    private static void LogException(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        var error = new Error(context.Exception, httpContext);
        ErrorLog.GetDefault(httpContext).Log(error);
    }

    private static HttpContext GetHttpContextImpl(HttpContextBase context)
    {
        return context.ApplicationInstance.Context;
    }
}

Esta segunda versão tentará usar a sinalização de erro do ELMAH primeiro, que envolve o pipeline totalmente configurado, como registro, correspondência, filtragem e o que você possui. Caso contrário, ele tenta ver se o erro deve ser filtrado. Caso contrário, o erro é simplesmente registrado. Esta implementação não trata de notificações por email. Se a exceção puder ser sinalizada, um email será enviado se configurado para isso.

Talvez você também precise cuidar para que, se várias HandleErrorAttributeinstâncias estiverem em vigor, o registro duplicado não ocorra, mas os dois exemplos acima devem começar.

Atif Aziz
fonte
1
Excelente. Eu não estava tentando implementar Elmah. Eu estava apenas tentando conectar meus próprios relatórios de erros que usei por anos de uma maneira que funcione bem com o MVC. Seu código me deu um ponto de partida. 1
Steve Wortham
18
Você não precisa subclassificar HandleErrorAttribute. Você pode simplesmente ter uma implementação IExceptionFilter e registrá-la junto com o HandleErrorAttribute. Também não entendo por que você precisa ter um fallback no caso de ErrorSignal.Raise (..) falhar. Se o pipeline estiver mal configurado, ele deverá ser corrigido. Para a 5 liner IExceptionFilter ponto de verificação 4. aqui - ivanz.com/2011/05/08/...
Ivan Zlatev
5
Por favor, você pode comentar a resposta abaixo de @IvanZlatev no que diz respeito à aplicabilidade, deficiências, etc. Seria bom ter sua perspectiva sobre isso e obter alguma clareza com essas respostas.
22412 Andrew Andrew
7
Isso ainda é relevante ou o ELMAH.MVC lida com isso?
Romias 03/04
2
Até eu gostaria de saber se ainda é relevante na versão de hoje
Refatorar
299

Desculpe, mas acho que a resposta aceita é um exagero. Tudo que você precisa fazer é o seguinte:

public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
    public void OnException (ExceptionContext context)
    {
        // Log only handled exceptions, because all other will be caught by ELMAH anyway.
        if (context.ExceptionHandled)
            ErrorSignal.FromCurrentContext().Raise(context.Exception);
    }
}

e registre-o (a ordem é importante) em Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Ivan Zlatev
fonte
3
+1 Muito bom, não há necessidade de estender a HandleErrorAttribute, não há necessidade de substituir OnExceptionon BaseController. Suponhamos que a resposta seja aceita.
CallMeLaNN
1
@bigb Eu acho que você teria que envolver a exceção em seu próprio tipo de exceção para anexar as coisas para a mensagem de exceção, etc (por exemplo, new UnhandledLoggedException(Exception thrown)que acrescenta algo ao Messageantes de devolvê-lo.
Ivan Zlatev
23
Atif Aziz criado ELMAH, eu iria com a sua resposta
jamiebarrow
48
@jamiebarrow Eu não percebi isso, mas sua resposta tem ~ 2 anos e provavelmente a API foi simplificada para dar suporte aos casos de uso da pergunta de uma maneira mais curta e independente.
Ivan Zlatev 5/08
6
@Ivan Zlatev realmente não consegue trabalhar, ElmahHandledErrorLoggerFilter()apenas registrando erros não tratados, mas não tratados. Registrei os filtros na ordem correta, como você mencionou, alguma opinião?
kuncevic.dev 14/09/11
14

Agora existe um pacote ELMAH.MVC no NuGet que inclui uma solução aprimorada da Atif e também um controlador que lida com a interface elmah no roteamento MVC (não é mais necessário usar esse axd). A melhor solução IMHO é criar um filtro que atue no final de todos os outros filtros e registre os eventos que já foram manipulados. O módulo elmah deve cuidar de registrar os outros erros que não são tratados pelo aplicativo. Isso também permitirá que você use o monitor de integridade e todos os outros módulos que podem ser adicionados ao asp.net para examinar eventos de erro
O problema com essa solução (e com todas as aqui) ) é que, de uma maneira ou de outra, o manipulador de erros elmah está realmente lidando com o erro, ignorando o que você pode querer configurar como uma tag customError ou através do ErrorHandler ou seu próprio manipulador de erros

Eu escrevi isso olhando com refletor no ErrorHandler dentro de elmah.mvc

public class ElmahMVCErrorFilter : IExceptionFilter
{
   private static ErrorFilterConfiguration _config;

   public void OnException(ExceptionContext context)
   {
       if (context.ExceptionHandled) //The unhandled ones will be picked by the elmah module
       {
           var e = context.Exception;
           var context2 = context.HttpContext.ApplicationInstance.Context;
           //TODO: Add additional variables to context.HttpContext.Request.ServerVariables for both handled and unhandled exceptions
           if ((context2 == null) || (!_RaiseErrorSignal(e, context2) && !_IsFiltered(e, context2)))
           {
            _LogException(e, context2);
           }
       }
   }

   private static bool _IsFiltered(System.Exception e, System.Web.HttpContext context)
   {
       if (_config == null)
       {
           _config = (context.GetSection("elmah/errorFilter") as ErrorFilterConfiguration) ?? new ErrorFilterConfiguration();
       }
       var context2 = new ErrorFilterModule.AssertionHelperContext((System.Exception)e, context);
       return _config.Assertion.Test(context2);
   }

   private static void _LogException(System.Exception e, System.Web.HttpContext context)
   {
       ErrorLog.GetDefault((System.Web.HttpContext)context).Log(new Elmah.Error((System.Exception)e, (System.Web.HttpContext)context));
   }


   private static bool _RaiseErrorSignal(System.Exception e, System.Web.HttpContext context)
   {
       var signal = ErrorSignal.FromContext((System.Web.HttpContext)context);
       if (signal == null)
       {
           return false;
       }
       signal.Raise((System.Exception)e, (System.Web.HttpContext)context);
       return true;
   }
}

Agora, na sua configuração de filtro, você deseja fazer algo assim:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //These filters should go at the end of the pipeline, add all error handlers before
        filters.Add(new ElmahMVCErrorFilter());
    }

Observe que deixei um comentário lá para lembrar às pessoas que, se elas quiserem adicionar um filtro global que realmente manipulará a exceção, ela deve ocorrer ANTES deste último filtro; caso contrário, você encontrará o caso em que a exceção não tratada será ignorada pelo ElmahMVCErrorFilter porque ele não foi tratado e deve ser registrado pelo módulo Elmah, mas o próximo filtro marca a exceção como manipulada e o módulo a ignora, resultando na exceção que nunca a transforma em elmah.

Agora, verifique se as configurações de aplicativos para elmah no seu webconfig são parecidas com esta:

<add key="elmah.mvc.disableHandler" value="false" /> <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" /> <!-- This uses the default filter for elmah, set to disabled to use our own -->
<add key="elmah.mvc.requiresAuthentication" value="false" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.allowedRoles" value="*" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.route" value="errortracking" /> <!-- Base route for elmah pages -->

O importante aqui é "elmah.mvc.disableHandleErrorFilter", se isso for falso, ele usará o manipulador dentro de elmah.mvc que realmente tratará a exceção usando o HandleErrorHandler padrão que ignorará suas configurações de customError

Essa configuração permite que você defina suas próprias tags ErrorHandler em classes e visualizações, enquanto ainda registra esses erros através do ElmahMVCErrorFilter, adicionando uma configuração customError ao seu web.config através do módulo elmah, até mesmo escrevendo seus próprios manipuladores de erros. A única coisa que você precisa fazer é lembrar de não adicionar nenhum filtro que realmente lide com o erro antes do filtro elmah que escrevemos. E eu esqueci de mencionar: não há duplicatas no elmah.

Raul Vejar
fonte
7

Você pode levar o código acima e ir um passo além, introduzindo uma fábrica de controladores personalizados que injeta o atributo HandleErrorWithElmah em cada controlador.

Para mais informações, confira minha série de blogs sobre como fazer login no MVC. O primeiro artigo aborda a instalação e execução do Elmah para o MVC.

Há um link para código para download no final do artigo. Espero que ajude.

http://dotnetdarren.wordpress.com/

Darren
fonte
6
Parece-me que seria muito mais fácil colocá-lo em uma classe de controlador de base!
Nathan Taylor
2
A série de Darren acima sobre registro e tratamento de exceções vale bem a pena a leitura !!! Muito completo!
Ryan Anderson
6

Eu sou novo no asp.net MVC. Enfrentei o mesmo problema, o seguinte é viável em meu Erorr.vbhtml (funcionará se você precisar registrar apenas o erro usando o Elmah log)

@ModelType System.Web.Mvc.HandleErrorInfo

    @Code
        ViewData("Title") = "Error"
        Dim item As HandleErrorInfo = CType(Model, HandleErrorInfo)
        //To log error with Elmah
        Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(New Elmah.Error(Model.Exception, HttpContext.Current))
    End Code

<h2>
    Sorry, an error occurred while processing your request.<br />

    @item.ActionName<br />
    @item.ControllerName<br />
    @item.Exception.Message
</h2> 

É simplesmente!

user716264
fonte
Esta é de longe a solução mais simples. Não há necessidade de escrever ou registrar manipuladores personalizados e outras coisas. Funciona bem para mim
ThiagoAlves
3
Será ignorado por quaisquer respostas JSON / não HTML.
Craig Stuntz
6
isso também está executando a funcionalidade de nível de serviço em uma exibição. Não pertence aqui.
Trevor de Koekkoek
6

Uma solução completamente alternativa é não usar o MVC HandleErrorAttributee confiar no tratamento de erros do ASP.Net, com o qual o Elmah foi projetado para trabalhar.

Você precisa remover o global padrão HandleErrorAttributede App_Start \ FilterConfig (ou Global.asax) e configurar uma página de erro no seu Web.config:

<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />

Observe que esse pode ser um URL roteado do MVC; portanto, o acima será redirecionado para a ErrorController.Indexação quando ocorrer um erro.

Ross McNab
fonte
Esta é a solução mais simples, de longe, e o redirecionamento padrão pode ser uma ação MVC :)
Jeremy Cozinhe
3
Isso será redirecionado para outros tipos de solicitações, como JSON etc. - não é bom.
Zvolkov
5

Para mim, era muito importante fazer o log de email funcionar. Depois de algum tempo, descobri que isso precisa de apenas 2 linhas de código a mais no exemplo do Atif.

public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
    static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();

    public override void OnException(ExceptionContext context)
    {
        error_mail_log.Init(HttpContext.Current.ApplicationInstance);
        [...]
    }
    [...]
}

Espero que isso ajude alguém :)

Komio
fonte
2

Isso é exatamente o que eu precisava para a configuração do meu site MVC!

Adicionei uma pequena modificação ao OnExceptionmétodo para lidar com várias HandleErrorAttributeinstâncias, conforme sugerido por Atif Aziz:

lembre-se de que talvez você precise cuidar para que, se várias HandleErrorAttributeinstâncias estiverem em vigor, o registro duplicado não ocorra.

Simplesmente context.ExceptionHandledcheco antes de chamar a classe base, apenas para saber se alguém manipulou a exceção antes do manipulador atual.
Funciona para mim e eu publico o código caso alguém precise dele e pergunte se alguém sabe se eu esqueci alguma coisa.

Espero que seja útil:

public override void OnException(ExceptionContext context)
{
    bool exceptionHandledByPreviousHandler = context.ExceptionHandled;

    base.OnException(context);

    Exception e = context.Exception;
    if (exceptionHandledByPreviousHandler
        || !context.ExceptionHandled  // if unhandled, will be logged anyhow
        || RaiseErrorSignal(e)        // prefer signaling, if possible
        || IsFiltered(context))       // filtered?
        return;

    LogException(e);
}
ilmatte
fonte
Você não parece ter uma declaração "if" ao invocar base.OnException () .... E (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) cancelam-se mutuamente e sempre serão verdadeiras. Estou esquecendo de algo?
Joelvh
Primeiro, verifico se algum outro manipulador, chamado antes da atual, gerenciava a exceção e armazeno o resultado na variável: exceptionHandlerdByPreviousHandler. Então, eu dou a chance ao manipulador atual de gerenciar a própria exceção: base.OnException (context).
ilmatte
Primeiro, verifico se algum outro manipulador, chamado antes da atual, gerenciava a exceção e armazeno o resultado na variável: exceptionHandlerdByPreviousHandler. Então, eu dou a chance ao manipulador atual de gerenciar a própria exceção: base.OnException (context). Se a exceção não foi gerenciada anteriormente, ela pode ser: 1 - Ela é gerenciada pelo manipulador atual; exceptionHandledByPreviousHandler = false e! Context.ExceptionHandled = false 2 - Ela não é gerenciada pelo manipulador atual e: exceptionHandledByPreviousHandler = false e! Context. ExceptionHandled true. Somente o caso 1 será registrado.
ilmatte