Bagaimana cara agar ELMAH bekerja dengan atribut ASP.NET MVC [HandleError]?

564

Saya mencoba menggunakan ELMAH untuk mencatat kesalahan dalam aplikasi ASP.NET MVC saya, namun ketika saya menggunakan atribut [HandleError] pada pengontrol saya, ELMAH tidak mencatat kesalahan apa pun ketika terjadi.

Seperti yang saya duga ini karena ELMAH hanya mencatat kesalahan yang tidak tertangani dan atribut [HandleError] menangani kesalahan sehingga tidak perlu untuk mencatatnya.

Bagaimana cara saya memodifikasi atau bagaimana saya akan memodifikasi atribut sehingga ELMAH dapat mengetahui bahwa ada kesalahan dan mencatatnya ..

Sunting: Biarkan saya memastikan semua orang mengerti, saya tahu saya bisa memodifikasi atribut itu bukan pertanyaan yang saya tanyakan ... ELMAH dilewati ketika menggunakan atribut handleerror yang berarti tidak akan melihat bahwa ada kesalahan karena ditangani sudah berdasarkan atribut ... Apa yang saya minta adalah ada cara untuk membuat ELMAH melihat kesalahan dan mencatatnya meskipun atribut menanganinya ... Saya mencari-cari dan tidak melihat metode apa pun untuk memanggil untuk memaksa untuk login kesalahan ....

dswatik
sumber
12
Wow, saya berharap Jeff atau Jared akan menjawab pertanyaan ini. Mereka menggunakan ELMAH untuk Stackoverflow;)
Jon Limjap
11
Hmm, aneh - kami tidak menggunakan HandleErrorAttribute - Elmah diatur di bagian <modules> web.config kami. Apakah ada manfaat menggunakan HandleErrorAttribute?
Jarrod Dixon
9
@Jarrod - alangkah baiknya untuk melihat apa "kebiasaan" tentang garpu ELMAH Anda.
Scott Hanselman
3
@dswatik Anda juga dapat mencegah pengalihan dengan mengatur redirectMode ke ResponseRewrite di web.config. Lihat blog.turlov.com/2009/01/...
Pavel Chuchuva
6
Saya terus berlari ke dokumentasi web dan posting yang berbicara tentang atribut [HandleError] dan Elmah, tapi saya tidak melihat perilaku ini diselesaikan (mis. Elmah tidak mencatat kesalahan "ditangani") ketika saya mengatur case dummy. Ini karena pada Elmah.MVC 2.0.x custom HandleErrorAttribute ini tidak lagi diperlukan; itu termasuk dalam paket nuget.
plyawn

Jawaban:

503

Anda dapat subkelas HandleErrorAttributedan menimpa OnExceptionanggotanya (tidak perlu menyalin) sehingga ia mencatat pengecualian dengan ELMAH dan hanya jika implementasi basis menanganinya. Jumlah minimal kode yang Anda butuhkan adalah sebagai berikut:

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

Implementasi basis dipanggil terlebih dahulu, memberikannya kesempatan untuk menandai pengecualian yang sedang ditangani. Hanya saat itulah pengecualian diisyaratkan. Kode di atas sederhana dan dapat menyebabkan masalah jika digunakan di lingkungan di mana HttpContextmungkin tidak tersedia, seperti pengujian. Akibatnya, Anda akan menginginkan kode yang lebih defensif (dengan biaya sedikit lebih lama):

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

Versi kedua ini akan mencoba menggunakan pensinyalan kesalahan dari ELMAH terlebih dahulu, yang melibatkan pipa yang sepenuhnya dikonfigurasi seperti pencatatan, pengiriman surat, pemfilteran, dan apa yang Anda miliki. Gagal itu, ia mencoba untuk melihat apakah kesalahan harus disaring. Jika tidak, kesalahan hanya dicatat. Implementasi ini tidak menangani notifikasi surat. Jika pengecualian dapat ditandai maka email akan dikirim jika dikonfigurasi untuk melakukannya.

Anda mungkin juga harus berhati-hati bahwa jika beberapa HandleErrorAttributeinstance berlaku maka duplikat logging tidak terjadi, tetapi dua contoh di atas harus memulai.

Atif Aziz
sumber
1
Luar biasa. Saya tidak mencoba menerapkan Elmah sama sekali. Saya hanya mencoba untuk menghubungkan pelaporan kesalahan saya sendiri yang telah saya gunakan selama bertahun-tahun dengan cara yang berfungsi baik dengan MVC. Kode Anda memberi saya titik awal. +1
Steve Wortham
18
Anda tidak perlu mensubclass HandleErrorAttribute. Anda dapat memiliki implementasi IExceptionFilter dan mendaftarkannya bersama dengan HandleErrorAttribute. Saya juga tidak mengerti mengapa Anda harus mundur jika ErrorSignal.Raise (..) gagal. Jika pipa dikonfigurasi dengan buruk, itu harus diperbaiki. Untuk titik pemeriksaan IExceptionFilter 5 liner 4. di sini - ivanz.com/2011/05/08/…
Ivan Zlatev
5
Anda dapat mengomentari jawaban di bawah ini dengan @IvanZlatev sehubungan dengan penerapan, kekurangan, dll. Orang-orang berkomentar bahwa itu lebih mudah / lebih pendek / sederhana dan mencapai hal yang sama dengan jawaban Anda dan karenanya harus ditandai sebagai jawaban yang benar. Akan lebih baik jika Anda memiliki perspektif tentang hal ini dan mencapai kejelasan dengan jawaban ini.
Andrew
7
Apakah ini masih relevan atau apakah ELMAH.MVC menangani ini?
Romias
2
Bahkan saya ingin tahu apakah masih relevan dalam versi hari ini
refactor
299

Maaf, tapi saya pikir jawaban yang diterima adalah berlebihan. Yang perlu Anda lakukan adalah ini:

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

dan kemudian daftarkan (urutan penting) di Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Ivan Zlatev
sumber
3
1 Sangat bagus, tidak perlu memperpanjang HandleErrorAttribute, tidak perlu menimpa OnExceptionpada BaseController. Ini kira jawaban yang diterima.
CallMeLaNN
1
@ bigb Saya pikir Anda harus membungkus pengecualian dalam jenis pengecualian Anda sendiri untuk menambahkan hal-hal ke pesan pengecualian, dll (misalnya new UnhandledLoggedException(Exception thrown)yang menambahkan sesuatu Messagesebelum mengembalikannya.
Ivan Zlatev
23
Atif Aziz menciptakan ELMAH, saya akan menjawabnya
jamiebarrow
48
@ jamiebarrow Saya tidak menyadarinya, tetapi jawabannya adalah ~ 2 tahun dan mungkin API telah disederhanakan untuk mendukung kasus penggunaan pertanyaan dengan cara yang lebih pendek dan mandiri.
Ivan Zlatev
6
@Ivan Zlatev benar-benar tidak bisa bekerja ElmahHandledErrorLoggerFilter()elmah hanya mencatat kesalahan yang tidak ditangani, tetapi tidak ditangani. Saya mendaftarkan filter dengan urutan yang benar ketika Anda menyebutkan itu, ada pemikiran?
kuncevic.dev
14

Sekarang ada paket ELMAH.MVC di NuGet yang mencakup solusi yang ditingkatkan oleh Atif dan juga pengontrol yang menangani antarmuka elmah dalam perutean MVC (tidak perlu menggunakan axd itu lagi)
Masalah dengan solusi itu (dan dengan semua yang ada di sini) ) adalah bahwa salah satu cara penangan kesalahan elmah sebenarnya menangani kesalahan, mengabaikan apa yang mungkin ingin Anda atur sebagai tag customError atau melalui ErrorHandler atau penangan kesalahan Anda sendiri
Solusi terbaik IMHO adalah membuat filter yang akan bertindak di akhir semua filter lain dan mencatat peristiwa yang sudah ditangani. Modul elmah harus berhati-hati dalam mencatat kesalahan lain yang tidak ditangani oleh aplikasi. Ini juga akan memungkinkan Anda untuk menggunakan monitor kesehatan dan semua modul lain yang dapat ditambahkan ke asp.net untuk melihat peristiwa kesalahan

Saya menulis ini dengan reflektor di ErrorHandler di dalam 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;
   }
}

Sekarang, dalam konfigurasi filter Anda, Anda ingin melakukan sesuatu seperti ini:

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

Perhatikan bahwa saya meninggalkan komentar di sana untuk mengingatkan orang bahwa jika mereka ingin menambahkan filter global yang benar-benar akan menangani pengecualian itu harus pergi SEBELUM filter terakhir ini, jika tidak Anda mengalami kasus di mana pengecualian yang tidak ditangani akan diabaikan oleh ElmahMVCErrorFilter karena itu belum ditangani dan harus dicatat oleh modul Elmah tetapi kemudian filter berikutnya menandai pengecualian sebagai ditangani dan modul mengabaikannya, sehingga pengecualian tidak pernah membuatnya menjadi elmah.

Sekarang, pastikan pengaturan aplikasi untuk elmah di konfigurasi web Anda terlihat seperti ini:

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

Yang penting di sini adalah "elmah.mvc.disableHandleErrorFilter", jika ini salah, ia akan menggunakan handler di dalam elmah.mvc yang benar-benar akan menangani pengecualian dengan menggunakan HandleErrorHandler default yang akan mengabaikan pengaturan customError Anda

Setup ini memungkinkan Anda untuk mengatur tag ErrorHandler Anda sendiri di kelas dan tampilan, sambil tetap mencatat kesalahan tersebut melalui ElmahMVCErrorFilter, menambahkan konfigurasi customError ke web Anda. Mengkonfigurasi melalui modul elmah, bahkan menulis Error Handler Anda sendiri. Satu-satunya hal yang perlu Anda lakukan adalah ingat untuk tidak menambahkan filter yang benar-benar akan menangani kesalahan sebelum filter elmah yang telah kami tulis. Dan saya lupa menyebutkan: tidak ada duplikat di elmah.

Raul Vejar
sumber
7

Anda dapat mengambil kode di atas dan melangkah lebih jauh dengan memperkenalkan pabrik pengontrol khusus yang menyuntikkan atribut HandleErrorWithElmah ke setiap pengontrol.

Untuk informasi lebih lanjut, lihat seri blog saya tentang login di MVC. Artikel pertama mencakup bagaimana mengatur dan menjalankan Elmah untuk MVC.

Ada tautan ke kode yang dapat diunduh di akhir artikel. Semoga itu bisa membantu.

http://dotnetdarren.wordpress.com/

Darren
sumber
6
Sepertinya saya akan jauh lebih mudah untuk hanya menempelkannya pada kelas pengontrol dasar!
Nathan Taylor
2
Seri Darren di atas tentang penebangan dan penanganan pengecualian layak dibaca !!! Sangat teliti!
Ryan Anderson
6

Saya baru di ASP.NET MVC. Saya menghadapi masalah yang sama, berikut ini adalah yang bisa diterapkan di Erorr.vbhtml saya (berfungsi jika Anda hanya perlu mencatat kesalahan menggunakan 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> 

Itu sederhana!

pengguna716264
sumber
Sejauh ini, ini adalah solusi paling sederhana. Tidak perlu menulis atau mendaftarkan penangan dan barang khusus. Bekerja dengan baik untuk saya
ThiagoAlves
3
Akan diabaikan untuk respons JSON / non-HTML.
Craig Stuntz
6
juga ini melakukan fungsionalitas tingkat layanan dalam tampilan. Bukan di sini.
Trevor de Koekkoek
6

Solusi yang sepenuhnya alternatif adalah dengan tidak menggunakan MVC HandleErrorAttribute, dan sebaliknya mengandalkan penanganan kesalahan ASP.Net, yang dirancang untuk bekerja dengan Elmah.

Anda perlu menghapus global default HandleErrorAttributedari App_Start \ FilterConfig (atau Global.asax), dan kemudian mengatur halaman kesalahan di Web.config Anda:

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

Catatan, ini bisa berupa URL yang dirutekan MVC, sehingga hal di atas akan mengarahkan ulang ke ErrorController.Indextindakan saat terjadi kesalahan.

Ross McNab
sumber
Ini adalah solusi paling sederhana sejauh ini, dan pengalihan standar bisa menjadi aksi MVC :)
Jeremy Cook
3
Itu akan mengarahkan ulang untuk jenis permintaan lainnya, seperti JSON dll. - tidak baik.
zvolkov
5

Bagi saya itu sangat penting untuk membuat logging email berfungsi. Setelah beberapa waktu saya menemukan bahwa ini hanya membutuhkan 2 baris kode lebih dalam contoh 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);
        [...]
    }
    [...]
}

Saya harap ini akan membantu seseorang :)

Komio
sumber
2

Inilah yang saya butuhkan untuk konfigurasi situs MVC saya!

Saya menambahkan sedikit modifikasi pada OnExceptionmetode untuk menangani banyak HandleErrorAttributekejadian, seperti yang disarankan oleh Atif Aziz:

ingatlah bahwa Anda mungkin harus berhati-hati bahwa jika beberapa HandleErrorAttributeinstance berlaku maka duplikat logging tidak terjadi.

Saya cukup memeriksa context.ExceptionHandledsebelum memanggil kelas dasar, hanya untuk mengetahui apakah ada orang lain yang menangani pengecualian sebelum penangan saat ini.
Ini bekerja untuk saya dan saya memposting kode kalau-kalau ada orang lain yang membutuhkannya dan bertanya apakah ada yang tahu jika saya mengabaikan sesuatu.

Semoga bermanfaat:

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
sumber
Tampaknya Anda tidak memiliki pernyataan "jika" di sekitar basis pemanggilan. OnException () .... Dan (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) membatalkan satu sama lain dan akan selalu benar. Apakah saya melewatkan sesuatu?
joelvh
Pertama saya memeriksa apakah ada Handler lain, dipanggil sebelum saat ini, mengelola pengecualian dan saya menyimpan hasilnya dalam variabel: exceptionHandlerdByPreviousHandler. Kemudian saya memberikan kesempatan kepada penangan saat ini untuk mengelola pengecualian itu sendiri: base.OnException (konteks).
ilmatte
Pertama saya memeriksa apakah ada Handler lain, dipanggil sebelum saat ini, mengelola pengecualian dan saya menyimpan hasilnya dalam variabel: exceptionHandlerdByPreviousHandler. Kemudian saya memberikan kesempatan kepada penangan saat ini untuk mengelola pengecualian itu sendiri: base.OnException (konteks). Jika pengecualian tidak dikelola sebelumnya dapat: 1 - Ini dikelola oleh penangan saat ini, maka: exceptionHandledByPreviousHandler = konteks salah dan !.ExceptionHandled = false 2 - Tidak dikelola oleh penangan saat ini dan: exceptionHandledByPreviousHandler = konteks salah dan! Exception Ditangani dengan benar. Hanya case 1 yang akan masuk.
ilmatte