Как заставить ELMAH работать с атрибутом ASP.NET MVC [HandleError]?

564

Я пытаюсь использовать ELMAH для регистрации ошибок в моем приложении ASP.NET MVC, однако, когда я использую атрибут [HandleError] на своих контроллерах, ELMAH не регистрирует ошибки при их возникновении.

Как я догадываюсь, потому что ELMAH регистрирует только необработанные ошибки, а атрибут [HandleError] обрабатывает ошибку, поэтому нет необходимости регистрировать ее.

Как изменить или как изменить атрибут, чтобы ELMAH мог знать, что произошла ошибка, и зарегистрировать ее ...

Изменить: Позвольте мне убедиться, что все понимают, я знаю, что я могу изменить атрибут, который не вопрос, который я задаю ... ELMAH игнорируется при использовании атрибута handleerror, что означает, что он не увидит, что произошла ошибка, потому что она была обработана уже по атрибуту ... То, что я спрашиваю, есть ли способ заставить ELMAH увидеть ошибку и записать ее в журнал, даже если атрибут обработал ее ... Я искал и не вижу никаких методов для вызова, чтобы заставить его регистрировать Ошибка....

dswatik
источник
12
Надеюсь, Джефф или Джаред ответят на этот вопрос. Они используют ELMAH для Stackoverflow;)
Джон Лимджап,
11
Хм, странно - мы не используем HandleErrorAttribute - Elmah настроен в разделе <modules> нашего web.config. Есть ли преимущества использования HandleErrorAttribute?
Джаррод Диксон
9
@Jarrod - было бы приятно узнать, что "обычного" в вашей вилке ELMAH.
Скотт Хансельман
3
@dswatik Вы также можете предотвратить перенаправления, установив для redirectMode значение ResponseRewrite в web.config. Смотрите blog.turlov.com/2009/01/…
Павел Чучува
6
Я продолжал работать с веб-документацией и сообщениями, рассказывающими об атрибуте [HandleError] и Elmah, но я не видел поведения, которое это решает (например, Elmah не регистрирует «обработанную» ошибку), когда я настраивал фиктивный случай. Это связано с тем, что в Elmah.MVC 2.0.x этот пользовательский атрибут HandleErrorAttribute больше не требуется; это включено в пакет nuget.
plyawn

Ответы:

503

Вы можете создать подкласс HandleErrorAttributeи переопределить его OnExceptionчлен (не нужно копировать), чтобы он регистрировал исключение с помощью ELMAH, и только если базовая реализация обрабатывает его. Минимальное количество кода, которое вам нужно, выглядит следующим образом:

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

Сначала вызывается базовая реализация, что дает возможность пометить исключение как обрабатываемое. Только тогда сигнализируется исключение. Приведенный выше код прост и может вызвать проблемы при использовании в среде, где он HttpContextможет быть недоступен, например при тестировании. В результате вам понадобится более защищенный код (за счет того, что он немного длиннее):

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

Эта вторая версия будет пытаться сначала использовать сигнализацию об ошибках от ELMAH, которая включает в себя полностью сконфигурированный конвейер, такой как регистрация, рассылка, фильтрация и прочее. В противном случае он пытается увидеть, следует ли фильтровать ошибку. Если нет, ошибка просто регистрируется. Эта реализация не обрабатывает почтовые уведомления. Если об исключении может быть сообщено, то письмо будет отправлено, если настроено для этого.

Возможно, вам также придется позаботиться о том, чтобы, если HandleErrorAttributeдействуют несколько экземпляров, дублирование записи не происходило, но приведенные выше два примера должны помочь вам начать работу.

Атиф Азиз
источник
1
Отлично. Я вообще не пытался реализовать Эльму. Я просто пытался подключить свои собственные отчеты об ошибках, которые я использовал годами, таким образом, который хорошо работает с MVC. Ваш код дал мне отправную точку. +1
Стив Уортам,
18
Вам не нужно создавать подкласс HandleErrorAttribute. Вы можете просто иметь реализацию IExceptionFilter и зарегистрировать ее вместе с HandleErrorAttribute. Также я не понимаю, зачем вам нужен запасной вариант в случае сбоя ErrorSignal.Raise (..). Если конвейер плохо настроен, его следует исправить. Для 5-го лайнера IExceptionFilter проверьте пункт 4. здесь - ivanz.com/2011/05/08/…
Иван Златев
5
Пожалуйста, вы можете прокомментировать ответ ниже @IvanZlatev относительно применимости, недостатков и т. Д. Люди комментируют, что это проще / короче / проще и достигает того же, что и ваш ответ, и поэтому должно быть помечено как правильный ответ. Было бы хорошо иметь ваше мнение об этом и добиться некоторой ясности с этими ответами.
Андрей
7
Это все еще актуально или ELMAH.MVC справляется с этим?
Ромиас
2
Даже я хотел бы знать, актуально ли это в сегодняшней версии
рефакторинг
299

Извините, но я думаю, что принятый ответ является излишним. Все, что вам нужно сделать, это:

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

и затем зарегистрируйте его (порядок важен) в Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Иван Златев
источник
3
+1 Очень хорошо, не нужно расширять HandleErrorAttribute, не нужно переопределять OnExceptionдальше BaseController. Это предполагает принятый ответ.
CallMeLaNN
1
@bigb Я думаю, вам нужно было бы обернуть исключение в свой собственный тип исключения, чтобы добавить вещи к сообщению об исключении и т. д. (например, new UnhandledLoggedException(Exception thrown)который добавляет что-то к Messageпредыдущему.
Иван Златев,
23
Атиф Азиз создал ELMAH, я бы пошел с его ответом
jamiebarrow
48
@jamiebarrow Я не осознавал этого, но его ответу ~ 2 года, и, вероятно, API был упрощен для поддержки вариантов использования вопроса более коротким и автономным способом.
Иван Златев
6
@ Иван Златев действительно не может добраться до работы, ElmahHandledErrorLoggerFilter()просто записывая необработанные ошибки, но не обработанные. Я зарегистрировал фильтры в правильном порядке, как вы упомянули, какие-нибудь мысли?
kuncevic.dev
14

Теперь в NuGet есть пакет ELMAH.MVC, который включает в себя улучшенное решение Atif, а также контроллер, который обрабатывает интерфейс elmah в маршрутизации MVC (больше не нужно использовать этот axd)
. Проблема с этим решением (и со всеми приведенными здесь). ) так или иначе обработчик ошибок elmah фактически обрабатывает ошибку, игнорируя то, что вы, возможно, захотите настроить как тег customError или через ErrorHandler или ваш собственный обработчик ошибок
Лучшее решение IMHO - создать фильтр, который будет действовать в конце всех остальных фильтров и регистрировать события, которые уже были обработаны. Модуль elmah должен позаботиться о регистрации других ошибок, которые не обрабатываются приложением. Это также позволит вам использовать монитор работоспособности и все другие модули, которые можно добавить на asp.net, для просмотра событий ошибок.

Я написал это, глядя с отражателем на ErrorHandler внутри 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;
   }
}

Теперь в настройках фильтра вы хотите сделать что-то вроде этого:

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

Обратите внимание, что я оставил там комментарий, чтобы напомнить людям, что если они хотят добавить глобальный фильтр, который будет обрабатывать исключение, он должен идти ДО этого последнего фильтра, в противном случае вы столкнетесь со случаем, когда необработанное исключение будет игнорироваться ElmahMVCErrorFilter, потому что оно не было обработано и должно быть зарегистрировано модулем Elmah, но затем следующий фильтр помечает исключение как обработанное, а модуль игнорирует его, в результате чего исключение никогда не превращается в elmah.

Теперь убедитесь, что настройки app для elmah в вашем webconfig выглядят примерно так:

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

Важным здесь является «elmah.mvc.disableHandleErrorFilter», если это false, он будет использовать обработчик внутри elmah.mvc, который фактически обработает исключение с помощью HandleErrorHandler по умолчанию, который будет игнорировать ваши настройки customError

Эта настройка позволяет вам устанавливать собственные теги ErrorHandler в классах и представлениях, при этом регистрируя эти ошибки через ElmahMVCErrorFilter, добавляя конфигурацию customError в ваш web.config через модуль elmah, даже записывая свои собственные обработчики ошибок. Единственное, что вам нужно сделать, - это не добавлять фильтры, которые будут обрабатывать ошибку до того, как мы написали фильтр elmah. И я забыл упомянуть: в Эльме нет дубликатов.

Рауль Вехар
источник
7

Вы можете взять приведенный выше код и сделать еще один шаг вперед, представив собственную фабрику контроллеров, которая добавляет атрибут HandleErrorWithElmah в каждый контроллер.

Для получения дополнительной информации посмотрите мою серию блогов о входе в систему MVC. Первая статья посвящена настройке Elmah и работе на MVC.

В конце статьи есть ссылка на загружаемый код. Надеюсь, это поможет.

http://dotnetdarren.wordpress.com/

Даррен
источник
6
Мне кажется, что было бы намного проще просто прикрепить его к базовому классу контроллеров!
Натан Тейлор
2
Вышеприведенная серия статей Даррена о ведении журнала и обработке исключений стоит того, чтобы ее прочитать !!! Очень тщательно!
Райан Андерсон
6

Я новичок в ASP.NET MVC. Я столкнулся с той же проблемой, вот мой рабочий в моем Erorr.vbhtml (он работает, если вам нужно только зарегистрировать ошибку, используя журнал 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> 

Это просто!

user716264
источник
Это, безусловно, самое простое решение. Не нужно писать или регистрировать пользовательские обработчики и прочее. У меня отлично работает
ThiagoAlves
3
Будет игнорироваться для любых ответов JSON / не HTML.
Крейг Stuntz
6
также это делает функциональность уровня обслуживания в представлении. Здесь не место
Тревор де Коеккук
6

Полностью альтернативное решение состоит в том, чтобы не использовать MVC HandleErrorAttribute, а вместо этого полагаться на обработку ошибок ASP.Net, с которой Elmah предназначен для работы.

Вам необходимо удалить глобальный по умолчанию HandleErrorAttributeиз App_Start \ FilterConfig (или Global.asax), а затем настроить страницу ошибки в вашем Web.config:

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

Обратите внимание, что это может быть URL-адрес с маршрутизацией MVC, поэтому приведенное выше будет перенаправлять на ErrorController.Indexдействие при возникновении ошибки.

Росс Макнаб
источник
На сегодняшний день это самое простое решение, и перенаправление по умолчанию может быть действием MVC :)
Джереми Кук
3
Это будет перенаправлять для других типов запросов, таких как JSON и т. Д. - не очень хорошо.
zvolkov
5

Для меня было очень важно, чтобы регистрация электронной почты работала. Через некоторое время я обнаружил, что для этого нужно всего лишь 2 строки кода в примере 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);
        [...]
    }
    [...]
}

Надеюсь, это кому-нибудь поможет :)

Komio
источник
2

Это именно то, что мне нужно для конфигурации моего сайта MVC!

Я добавил небольшую модификацию в OnExceptionметод для обработки нескольких HandleErrorAttributeэкземпляров, как это предлагает Atif Aziz:

помните, что вам, возможно, придется позаботиться о том, чтобы, если HandleErrorAttributeдействуют несколько экземпляров, дублирование записи не происходило.

Я просто проверяю context.ExceptionHandledперед вызовом базового класса, просто чтобы узнать, обрабатывал ли кто-то еще исключение перед текущим обработчиком.
Это работает для меня, и я выкладываю код на случай, если кому-то еще это понадобится, и спрашиваю, знает ли кто-нибудь, пропустил ли я что-нибудь.

Надеюсь, это полезно:

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
источник
Похоже, у вас нет оператора "if" вокруг вызова base.OnException () .... И (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) отменяют друг друга и всегда будут истинными. Я что-то пропустил?
Джоэльв
Сначала я проверяю, управлял ли исключение любой другой обработчик, вызванный до текущего, и сохраняю результат в переменной: exceptionHandlerdByPreviousHandler. Затем я даю возможность текущему обработчику управлять самим исключением: base.OnException (context).
ilmatte
Сначала я проверяю, управлял ли исключение любой другой обработчик, вызванный до текущего, и сохраняю результат в переменной: exceptionHandlerdByPreviousHandler. Затем я даю возможность текущему обработчику управлять самим исключением: base.OnException (context). Если исключение ранее не управлялось, оно может быть: 1 - оно управляется текущим обработчиком, затем: exceptionHandledByPreviousHandler = false и! Context.ExceptionHandled = false 2 - оно не управляется текущим обработчиком и: exceptionHandledByPreviousHandler = false и! Context. Исключение Только случай 1 будет регистрироваться.
ilmatte