¿Cómo hacer que ELMAH funcione con el atributo ASP.NET MVC [HandleError]?

Estoy tratando de usar ELMAH para registrar errores en mi aplicación ASP.NET MVC, sin embargo, cuando uso el atributo [HandleError] en mis controladores, ELMAH no registra ningún error cuando ocurren.

Como supongo, es porque ELMAH solo registra errores no controlados y el atributo [HandleError] está manejando el error, por lo que no es necesario registrarlo.

¿Cómo modifico o cómo modificaría el atributo para que ELMAH pueda saber que hubo un error y registrarlo?

Edit: Permítanme asegurarme de que todos entiendan, sé que puedo modificar el atributo, esa no es la pregunta que estoy haciendo ... ELMAH se omite cuando se usa el atributo handleerror, lo que significa que no verá que hubo un error porque ya fue manejado por el atributo ... Lo que estoy preguntando es que hay una manera de hacer que ELMAH vea el error y lo registre a pesar de que el atributo lo manejó ... Busqué y no veo ningún método para llamar para forzarlo a registrar el error ....

preguntado el 20 de abril de 09 a las 02:04

Vaya, espero que Jeff o Jared respondan a esta pregunta. Están usando ELMAH para Stackoverflow;) -

Hmm, extraño - no usamos el HandleErrorAttribute - Elmah está configurado en nuestro web.config's sección. ¿Hay beneficios de usar HandleErrorAttribute? -

@ Jarrod: sería bueno ver qué es lo "personalizado" de su bifurcación ELMAH. -

@dswatik También puede evitar las redirecciones configurando redirectMode en ResponseRewrite en web.config. Ver blog.turlov.com/2009/01/… -

Seguí encontrando documentación web y publicaciones que hablaban sobre el atributo [HandleError] y Elmah, pero no veía el comportamiento que esto resuelve (por ejemplo, Elmah no registra el error "manipulado") cuando configuré el caso ficticio. Esto se debe a que a partir de Elmah.MVC 2.0.x este HandleErrorAttribute personalizado ya no es necesario; está incluido en el paquete nuget. -

8 Respuestas

Puedes subclasificar HandleErrorAttribute y anular su OnException miembro (no es necesario copiar) para que registre la excepción con ELMAH y solo si la implementación base la maneja. La cantidad mínima de código que necesita es la siguiente:

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

La implementación base se invoca primero, lo que le da la oportunidad de marcar la excepción como manejada. Solo entonces se señala la excepción. El código anterior es simple y puede causar problemas si se usa en un entorno donde el HttpContext puede no estar disponible, como las pruebas. Como resultado, querrá un código que sea más defensivo (a costa de ser un poco más largo):

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 versión intentará utilizar señalización de error de ELMAH primero, que involucra la canalización completamente configurada como registro, envío de correos, filtrado y lo que sea. De lo contrario, intenta ver si el error debe filtrarse. De lo contrario, el error simplemente se registra. Esta implementación no maneja notificaciones por correo. Si se puede señalar la excepción, se enviará un correo electrónico si está configurado para hacerlo.

Es posible que también deba tener cuidado de que si HandleErrorAttribute instancias están en efecto, entonces no se produce el registro duplicado, pero los dos ejemplos anteriores deberían ayudarlo a comenzar.

Respondido el 06 de junio de 12 a las 16:06

Excelente. No estaba tratando de implementar Elmah en absoluto. Solo estaba tratando de conectar mi propio informe de errores que he usado durante años de una manera que funcione bien con MVC. Tu código me dio un punto de partida. +1 - Steve Wortham

No es necesario crear una subclase de HandleErrorAttribute. Simplemente puede tener una implementación IExceptionFilter y registrarla junto con HandleErrorAttribute. Además, no entiendo por qué necesita tener un respaldo en caso de que ErrorSignal.Raise (..) falle. Si la canalización está mal configurada, debería arreglarse. Para un IExceptionFilter de 5 líneas, verifique el punto 4. aquí - ivanz.com/2011/05/08/… - Ivan Zlatev

Por favor, puede comentar la respuesta de @IvanZlatev a continuación con respecto a la aplicabilidad, las deficiencias, etc. La gente comenta que es más fácil / más corta / más simple y logra lo mismo que su respuesta y, como tal, debe marcarse como la respuesta correcta. Sería bueno tener su perspectiva sobre esto y lograr algo de claridad con estas respuestas. - Andrés

¿Sigue siendo relevante o ELMAH.MVC lo maneja? - Romias

Incluso me gustaría saber si sigue siendo relevante en la versión de hoy: refactorizar

Lo siento, pero creo que la respuesta aceptada es exagerada. Todo lo que necesitas hacer es esto:

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

y luego regístrelo (el orden es importante) en Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}

contestado el 09 de mayo de 11 a las 16:05

+1 Muy bonito, no es necesario ampliar el HandleErrorAttribute, no es necesario anular OnException on BaseController. Esto se supone que es la respuesta aceptada. - LlamarMeLaNN

@bigb Creo que tendrías que envolver la excepción en tu propio tipo de excepción para agregar cosas al mensaje de excepción, etc. (p. ej. new UnhandledLoggedException(Exception thrown) que añade algo a la Message antes de devolverlo. - Ivan Zlatev

Atif Aziz creó ELMAH, yo iría con su respuesta: Jamiebarrow

@jamiebarrow No me di cuenta de eso, pero su respuesta tiene ~ 2 años y probablemente la API se ha simplificado para admitir los casos de uso de la pregunta de una manera más corta y autónoma. - Ivan Zlatev

@Ivan Zlatev realmente no puede ir a trabajar ElmahHandledErrorLoggerFilter() elmah solo registra errores no manejados, pero no manejados. Registré los filtros en el orden correcto como lo mencionaste, ¿alguna idea? - kuncevic.dev

Ahora hay un paquete ELMAH.MVC en NuGet que incluye una solución mejorada de Atif y también un controlador que maneja la interfaz elmah dentro del enrutamiento MVC (ya no es necesario usar ese axd)
El problema con esa solución (y con todas las de aquí) es que de una forma u otra, el controlador de errores de elmah está manejando el error, ignorando lo que podría querer configurar como una etiqueta customError o mediante ErrorHandler o su propio controlador de errores.
En mi humilde opinión, la mejor solución es crear un filtro que actuará al final de todos los demás filtros y registrará los eventos que ya se han manejado. El módulo elmah debería encargarse de registrar los otros errores que no son manejados por la aplicación. Esto también le permitirá usar el monitor de salud y todos los demás módulos que se pueden agregar a asp.net para ver los eventos de error.

Escribí esto mirando con reflector en el 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;
   }
}

Ahora, en su configuración de filtro, desea hacer algo como esto:

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

Tenga en cuenta que dejé un comentario allí para recordarle a las personas que si quieren agregar un filtro global que realmente manejará la excepción, debe ir ANTES de este último filtro; de lo contrario, se encontrará con el caso en el que ElmahMVCErrorFilter ignorará la excepción no controlada porque no ha sido manejado y debería ser registrado por el módulo Elmah pero luego el siguiente filtro marca la excepción como manejada y el módulo la ignora, resultando en que la excepción nunca llegue a elmah.

Ahora, asegúrese de que la configuración de la aplicación para elmah en su configuración web se parezca a esto:

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

El importante aquí es "elmah.mvc.disableHandleErrorFilter", si esto es falso, usará el controlador dentro de elmah.mvc que realmente manejará la excepción usando el HandleErrorHandler predeterminado que ignorará su configuración customError

Esta configuración le permite establecer sus propias etiquetas ErrorHandler en clases y vistas, mientras sigue registrando esos errores a través de ElmahMVCErrorFilter, agregando una configuración customError a su web.config a través del módulo elmah, incluso escribiendo sus propios Controladores de errores. Lo único que debe hacer es recordar no agregar ningún filtro que realmente maneje el error antes del filtro elmah que hemos escrito. Y olvidé mencionar: no hay duplicados en elmah.

Respondido el 24 de enero de 13 a las 22:01

Puede tomar el código anterior e ir un paso más allá al introducir una fábrica de controladores personalizados que inyecta el atributo HandleErrorWithElmah en cada controlador.

Para obtener más información, consulte mi serie de blogs sobre cómo iniciar sesión en MVC. El primer artículo cubre la configuración y ejecución de Elmah para MVC.

Hay un enlace al código descargable al final del artículo. Espero que ayude.

http://dotnetdarren.wordpress.com/

Respondido 28 Jul 10, 04:07

¡Me parece que sería mucho más fácil pegarlo en una clase de controlador base! - Nathan Taylor

Vale la pena leer la serie anterior de Darren sobre registro y manejo de excepciones. ¡Muy minucioso! - Ryan Anderson

Soy nuevo en ASP.NET MVC. Enfrenté el mismo problema, el siguiente es mi factible en mi Erorr.vbhtml (funciona si solo necesita registrar el error usando el registro de Elmah)

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

¡Es sencillo!

Respondido 20 Abr '11, 05:04

Ésta es, con mucho, la solución más sencilla. No es necesario escribir o registrar controladores personalizados y demás. Funciona bien para mí Thiago Alves

Se ignorará para cualquier respuesta JSON / no HTML. - Craig Stuntz

también esto está haciendo la funcionalidad de nivel de servicio en una vista. No pertenece aquí. - Trevor de Koekkoek

Una solución completamente alternativa es no usar MVC HandleErrorAttributey, en su lugar, confía en el manejo de errores de ASP.Net, con el que Elmah está diseñado para trabajar.

Necesita eliminar el global predeterminado HandleErrorAttribute desde App_Start \ FilterConfig (o Global.asax), y luego configure una página de error en su Web.config:

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

Tenga en cuenta que esta puede ser una URL enrutada MVC, por lo que lo anterior redirigiría a la ErrorController.Index acción cuando ocurre un error.

Respondido 01 ago 13, 11:08

Esta es la solución más simple con diferencia, y la redirección predeterminada puede ser una acción MVC :) - Jeremy Cook

Eso redirigirá para otros tipos de solicitudes, como JSON, etc., no es bueno. - zvolkov

Para mí era muy importante que el registro de correo electrónico funcionara. Después de un tiempo descubrí que esto solo necesita 2 líneas de código más en el ejemplo de 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 esto ayude a alguien :)

Respondido el 26 de enero de 11 a las 12:01

¡Esto es exactamente lo que necesitaba para la configuración de mi sitio MVC!

Agregué una pequeña modificación al OnException método para manejar múltiples HandleErrorAttribute instancias, como sugiere Atif Aziz:

Tenga en cuenta que es posible que deba tener cuidado de que si HandleErrorAttribute instancias están en efecto, entonces no se produce un registro duplicado.

Yo simplemente reviso context.ExceptionHandled antes de invocar la clase base, solo para saber si alguien más manejó la excepción antes que el manejador actual.
Funciona para mí y publico el código en caso de que alguien más lo necesite y para preguntar si alguien sabe si pasé por alto algo.

Espero que sea ú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);
}

respondido 07 nov., 10:01

No parece tener una declaración "if" sobre la invocación de base.OnException () .... Y (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) se cancelan entre sí y siempre serán verdaderas. ¿Me estoy perdiendo de algo? - joelvh

Primero verifico si algún otro Handler, invocado antes que el actual, administró la excepción y almaceno el resultado en la variable: exceptionHandlerdByPreviousHandler. Luego le doy la oportunidad al controlador actual de administrar la excepción en sí: base.OnException (contexto). - ilmatte

Primero verifico si algún otro Handler, invocado antes que el actual, administró la excepción y almaceno el resultado en la variable: exceptionHandlerdByPreviousHandler. Luego le doy la oportunidad al controlador actual de administrar la excepción en sí: base.OnException (contexto). Si la excepción no se administró previamente, puede ser: 1 - La administra el controlador actual, entonces: exceptionHandledByPreviousHandler = false y! Context.ExceptionHandled = false 2 - No la administra el controlador actual y: exceptionHandledByPreviousHandler = false y! Context. ExceptionHandled true. Solo se registrará el caso 1. - ilmatte

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.