Як змусити ELMAH працювати з атрибутом ASP.NET MVC [HandleError]?


564

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

Як я здогадуюсь, тому що ELMAH реєструє лише необроблені помилки, а атрибут [HandleError] обробляє помилку, тому не потрібно реєструвати її.

Як я можу змінити або як би я пішов про зміну атрибуту, щоб ELMAH міг знати, що сталася помилка та ввійти в систему ..

Редагувати: Дозвольте переконатися, що всі розуміють, я знаю, що можу змінити атрибут - це не питання, про яке я задаюсь ... ELMAH обійдеться при використанні атрибута handerror, тобто, він не побачить, що сталася помилка, оскільки вона оброблялася вже за атрибутом ... Що я запитую, чи є спосіб змусити ELMAH бачити помилку та реєструвати її, навіть незважаючи на те, що атрибут обробляв її ... Я шукав навколо і не бачу жодних методів виклику, щоб змусити його ввійти помилка ....


12
О, я сподіваюся, що Джефф або Джаред відповіли б на це питання. Вони використовують ELMAH для Stackoverflow;)
Джон Лімжап,

11
Хм, дивно - ми не використовуємо HandleErrorAttribute - Elmah встановлюється в розділі <modules> нашого web.config. Чи є переваги використання HandleErrorAttribute?
Jarrod Dixon

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
Відмінно. Я взагалі не намагався реалізувати Elmah. Я просто намагався підключити моє власне повідомлення про помилки, яким я користувався роками, таким чином, що добре працює з MVC. Ваш код дав мені вихідну точку. +1
Стів Вортем

18
Вам не потрібно підклас HandleErrorAttribute. Ви можете просто здійснити реалізацію IExceptionFilter і зареєструвати її разом з HandleErrorAttribute. Крім того, я не розумію, чому вам потрібно мати резервний випадок у випадку помилки ErrorSignal.Raise (..). Якщо трубопровід погано налаштований, його слід виправити. Для пункту пропуску IExceptionFilter 5-ти вкладишів тут - 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
Atif Aziz створив ELMAH, я б пішов з його відповіддю
jamiebarrow

48
@jamiebarrow Я цього не усвідомлював, але його відповідь становить ~ 2 роки, і, ймовірно, API було спрощено, щоб підтримувати випадки використання питання коротшим більш самостійним способом.
Іван Златев

6
@Ivan Zlatev насправді не може дійти до роботи в ElmahHandledErrorLoggerFilter()elmah, просто записуючи необроблені помилки, але не обробляються. Я зареєстрував фільтри в правильному порядку, як ви це згадали, якісь думки?
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.

Тепер переконайтеся, що програми для elmah у вашій вебконфігурації виглядають приблизно так:

<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", якщо це неправда, він використовуватиме обробник всередині 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> 

Це просто!


Це, безумовно, найпростіше рішення. Не потрібно писати чи реєструвати власні обробники та інші речі. Для мене добре працює
ThiagoAlves

3
Буде ігноровано для будь-яких відповідей JSON / не-HTML.
Крейг Штунц

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

Для мене дуже важливо було працювати з журналом електронної пошти. Через деякий час я дізнаюся, що для прикладу Atif для цього потрібно лише 2 рядки коду.

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

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

Сподіваюся, це комусь допоможе :)


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

У вас, здається, не існує твердження "якщо" навколо виклику base.OnException () .... І (за виняткомHandledByPreviousHandler ||! Context.ExceptionHandled || ...) скасовують один одного і завжди будуть істинні. Я щось пропускаю?
joelvh

Спочатку я перевіряю, чи керував виняток будь-який інший обробник, викликаний перед поточним, і я зберігаю результат у змінній: izjemHandlerdByPreviousHandler. Тоді я даю можливість поточному оброблювачу керувати самим винятком: base.OnException (контекст).
ilmatte

Спочатку я перевіряю, чи керував виняток будь-який інший обробник, викликаний перед поточним, і я зберігаю результат у змінній: izjemHandlerdByPreviousHandler. Тоді я даю можливість поточному оброблювачу керувати самим винятком: base.OnException (контекст). Якщо виняток раніше не керувався, він може бути: 1 - ним керує поточний обробник, тоді: виключенняHandledByPreviousHandler = false та! ВинятокДокладний правда. Лише випадок 1 увійде до журналу.
ilmatte
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.