65

I just started using ELMAH and am a fan. My team supports a large number of web applications and I'm particularly excited that ELMAH lets us save exceptions from each application to the same MS SQL database table.

We also support a few console, DLL and desktop applications. Is it possible to use the ELMAH DLL to log exceptions in these apps to that same location?

Tim S. Van Haren
  • 8,617
  • 2
  • 28
  • 34
Michael La Voie
  • 26,205
  • 14
  • 68
  • 92
  • I'm planning to implement this in a Windows Service as well. Just wanted to check if there might be any performance issues/any other issues if we do so? It seems to me like this is primarily for web based applications. I haven't found too much material on how to implement it in windows applications. Thanks Soni – hangar18 Jul 13 '11 at 08:36

7 Answers7

77

We needed the ability to log from a console app and a windows service in addition to our ASP.NET site. I used the answer (ErrorLog.GetDefault(null);) which worked well until I needed to email too.

So, here is my solution. It handles the log, email, tweet and filtering (both in the config file and in code). I have also wrapped the main call as an extension to Exception so it can be called like: catch(Exception ex) { ex.LogToElmah(); }

To filter in code, hook the corresponding .Filtering event: ElmahExtension.ErrorLog.Filtering += new ExceptionFilterEventHandler(ErrorLog_Filtering);

Code:

using System;
using System.Web;
using Elmah;
namespace System
{
    public static class ElmahExtension
    {
        public static void LogToElmah(this Exception ex)
        {
            if (HttpContext.Current != null)
            {
                ErrorSignal.FromCurrentContext().Raise(ex);
            }
            else
            {
                if (httpApplication == null) InitNoContext();
                ErrorSignal.Get(httpApplication).Raise(ex);
            }
        }

            private static HttpApplication httpApplication = null;
            private static ErrorFilterConsole errorFilter = new ErrorFilterConsole();

            public static ErrorMailModule ErrorEmail = new ErrorMailModule();
            public static ErrorLogModule ErrorLog = new ErrorLogModule();
            public static ErrorTweetModule ErrorTweet = new ErrorTweetModule();

            private static void InitNoContext()
            {
                httpApplication = new HttpApplication();
                errorFilter.Init(httpApplication);

                (ErrorEmail as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorEmail);

                (ErrorLog as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorLog);                

                (ErrorTweet as IHttpModule).Init(httpApplication);
                errorFilter.HookFiltering(ErrorTweet);
            }

            private class ErrorFilterConsole : ErrorFilterModule
            {
                public void HookFiltering(IExceptionFiltering module)
                {
                    module.Filtering += new ExceptionFilterEventHandler(base.OnErrorModuleFiltering);
                }
            }
    }
}

In addition, you will need to add a reference to the System.Web.dll in your project for this to work.

EDIT: As per the comments, this code will send emails only if your config file has <errorMail async="false"/>. Refer to this code snippet should you want to keep <errorMail async="true"/> in your config file (to be used when HttpContext.Current is available).

Jason Kealey
  • 7,818
  • 10
  • 40
  • 53
Brian Chance
  • 1,229
  • 10
  • 15
  • Awesome post. This worked perfectly for me. Just had to put the normal stuff from the web.config in my app.config and smooth sailing! – Chris Kooken Nov 09 '10 at 18:48
  • 6
    This wasn't working for me in a unit test until I set . I had async previously set to true. – Todd Smith Feb 23 '11 at 17:36
  • Thanks for the post--worked great! We hooked up many events including ErrorLog_Filtering, ErrorLog_Logged, ErrorMail_Filtering, ErrorMail_Mailing, ErrorMail_Mailed, ErrorMail_DisposingMail using the appropriate classes (ExceptionFilterEventHandler, ErrorLoggedEventHandler, etc.) – Zugwalt May 13 '11 at 19:25
  • 2
    I copied your code and in my exception I do this ex.LogToElmah(); and it goes through the code but when I look in elmah.axd nothing is ever logged. On local host I don't use email sending or anything so I am not sure if that is the cause or not. Nothing crashes though. Do I have to hook something else up? – chobo2 Jun 08 '11 at 17:12
  • 3
    Using this extension. Adding the configsection and the elmah configuration to app.config, setting , made it work for me. – Jim Wolff Jun 26 '12 at 08:02
  • 1
    The errors were not appearing in my log either (I'm using a SQL server log). I had to set the applicationName attribute in my App.config to get it to work. I.e. `` – asgeo1 Aug 01 '12 at 06:43
  • I get this error: `System.ArgumentException: Path cannot be the empty string or all whitespace.`, does anyone know why ? There's no google search records for this!! – Ashkan Jul 01 '14 at 21:07
  • Am a little confused, do I just add a separate class with the above code, add `errorlog` settings in App.config. Is there anything else I need to do? Moreover, I am not able to find `applicationName` in main Project's Web.Config to be included in App.config of console app. – Shyamal Parikh Oct 09 '17 at 08:11
68

We have exactly the same situation here. Running ELMAH for all our web applications. A few of them have console based schedulers.

After doing some digging through the source code, the following code seems to work:

            ErrorLog errorLog = ErrorLog.GetDefault(null);
            errorLog.ApplicationName = "/LM/W3SVC/1/ROOT/AppName";
            errorLog.Log(new Error(ex));

The only real problem with the above is that you need to keep the application name somewhere in your config to be able to see the entries on the ELMAH.axd viewer.

So in our generic error handling code we do:

        if (HttpContext.Current != null)
            ErrorSignal.FromCurrentContext().Raise(ex);
        else
        {
            ErrorLog errorLog = ErrorLog.GetDefault(null);
            errorLog.ApplicationName = ErrorHandling.Application;
            errorLog.Log(new Error(ex));
        }
Neil Bostrom
  • 2,046
  • 19
  • 22
  • 2
    Hmm - pretty cool, but I can't seem to get it to send emails when the error occurs in the console app. Any ideas? – teedyay Mar 12 '10 at 13:46
  • 3
    Unlikely, the errorLog.Log seems to bypass all the other listeners. Anyone know any better? – Neil Bostrom Mar 15 '10 at 12:57
  • @Duke I liked this approach but Unfortunately My module sits in a separate project . I figured out if you reference elmah like this to use it in a non web applicaiton it wont compile until you add `System.Web` Dll to it . – Joy Jun 12 '14 at 09:06
14

If you just want to email the log without http you can do like this:

    public class MyElmahMail: ErrorMailModule
    {
        public MyElmahMail()
        {
//this basically just gets config from errorMail  (app.config)
            base.OnInit(new HttpApplication());
        }
        public void Log(Error error)
        {
//just send the email pls
            base.ReportError(error);
        }
    }

//to call it
var mail = new MyElmahMail();
mail.Log(new Error(new NullReferenceException()));//whatever exception u want to log

And in terms of app.config

//Under configSections
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
    </sectionGroup>

And

  <elmah>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="C:\Elmah.Error" applicationName="MyAwesomeApp" />
    <errorMail from="from@email.com" to="to@email.com" />
  </elmah>

And smtp settings of your choice.

All done. :-)

Jeff
  • 12,629
  • 22
  • 66
  • 99
5

Edit: This CAN be done - See this answer.


I'm pretty sure you can't do this. I'll try and dig up the relevant material.

http://groups.google.com/group/elmah/browse_thread/thread/f214c4f782dc2bf4/d96fe43b60765f0c?lnk=gst&q=app#d96fe43b60765f0c

So from what I can find searching the Google group is that it's not possible... Since ELMAH works off of HttpHandlers (an asp.net construct) it is ASP.NET only.

With that said, there are ways that you could utilize it on a console application. ELMAH provides a method to raise errors, so you could wrap ELMAH in your exception handling and then signal an error via:

ErrorSignal.FromCurrentContext().Raise(new NotSupportedException());

This would mean wrapping your entire application in an exception handler and signaling. It might take you some tweaking to get it down, but I think it's totally possible.

In case you require it, this is the link to the ELMAH code repository.

Community
  • 1
  • 1
Gavin Miller
  • 40,636
  • 19
  • 113
  • 178
  • Thank you. I understand that it wasn't build around this, but it seems like a need others might have, to share a common error db – Michael La Voie May 08 '09 at 19:49
2

For those that need Brian Chance's answer ported to VB.NET:

Imports System
Imports System.Web
Imports Elmah
Namespace System
    Public NotInheritable Class ElmahExtension
        Private Sub New()
        End Sub
        <System.Runtime.CompilerServices.Extension> _
        Public Shared Sub LogToElmah(ex As Exception)
            If HttpContext.Current IsNot Nothing Then
                ErrorSignal.FromCurrentContext().Raise(ex)
            Else
                If httpApplication Is Nothing Then
                    InitNoContext()
                End If
                ErrorSignal.[Get](httpApplication).Raise(ex)
            End If
        End Sub

        Private Shared httpApplication As HttpApplication = Nothing
        Private Shared errorFilter As New ErrorFilterConsole()

        Public Shared ErrorEmail As New ErrorMailModule()
        Public Shared ErrorLog As New ErrorLogModule()
        Public Shared ErrorTweet As New ErrorTweetModule()

        Private Shared Sub InitNoContext()
            httpApplication = New HttpApplication()
            errorFilter.Init(httpApplication)

            TryCast(ErrorEmail, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorEmail)

            TryCast(ErrorLog, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorLog)

            TryCast(ErrorTweet, IHttpModule).Init(httpApplication)
            errorFilter.HookFiltering(ErrorTweet)
        End Sub



    Private Class ErrorFilterConsole
        Inherits Elmah.ErrorFilterModule


        Public Sub HookFiltering([module] As Elmah.IExceptionFiltering)
            AddHandler [module].Filtering, New Elmah.ExceptionFilterEventHandler(AddressOf MyBase.OnErrorModuleFiltering)
        End Sub

    End Class


    End Class
End Namespace

However, for just logging errors to the database, this will be sufficient:

If System.Web.HttpContext.Current Is Nothing Then
    Dim req As System.Web.HttpRequest = New System.Web.HttpRequest(String.Empty, "https://www.domain.tld", Nothing)
    Dim res As System.Web.HttpResponse = New System.Web.HttpResponse(Nothing)
    System.Web.HttpContext.Current = New System.Web.HttpContext(req, res)

    'Dim request As System.Web.Hosting.SimpleWorkerRequest = New System.Web.Hosting.SimpleWorkerRequest("/blah", "c:\inetpub\wwwroot\blah", "blah.html", Nothing, New System.IO.StringWriter())
    'System.Web.HttpContext.Current = New System.Web.HttpContext(request)

    System.Web.HttpContext.Current.ApplicationInstance = New System.Web.HttpApplication()

    Dim ErrorLog As New Elmah.ErrorLogModule()
    TryCast(ErrorLog, System.Web.IHttpModule).Init(System.Web.HttpContext.Current.ApplicationInstance)
End If

As a complete solution:

Public parent As Elmah.ServiceProviderQueryHandler = Nothing




' http://stackoverflow.com/questions/5981750/configuring-elmah-with-sql-server-logging-with-encrypted-connection-string
Public Function Elmah_MS_SQL_Callback(objContext As Object) As System.IServiceProvider
    Dim container As New System.ComponentModel.Design.ServiceContainer(parent(objContext))
    Dim strConnectionString As String = COR.SQL.MS_SQL.GetConnectionString()

    Dim log As Elmah.SqlErrorLog = New Elmah.SqlErrorLog(strConnectionString)
    'Dim strApplicationName = System.Web.Compilation.BuildManager.GetGlobalAsaxType().BaseType.Assembly().FullName
    Dim strApplicationName As String = System.Reflection.Assembly.GetExecutingAssembly().FullName
    If Not String.IsNullOrEmpty(strApplicationName) Then
        log.ApplicationName = strApplicationName.Substring(0, strApplicationName.IndexOf(","))
    End If

    container.AddService(GetType(Elmah.ErrorLog), log)
    Return container
End Function ' Elmah_MS_SQL_Callback




Public Function Elmah_PG_SQL_Callback(objContext As Object) As System.IServiceProvider
    Dim container As New System.ComponentModel.Design.ServiceContainer(parent(objContext))
    Dim strConnectionString As String = COR.SQL.MS_SQL.GetConnectionString()

    Dim log As Elmah.PgsqlErrorLog = New Elmah.PgsqlErrorLog(strConnectionString)
    'Dim strApplicationName = System.Web.Compilation.BuildManager.GetGlobalAsaxType().BaseType.Assembly().FullName
    Dim strApplicationName As String = System.Reflection.Assembly.GetExecutingAssembly().FullName
    If Not String.IsNullOrEmpty(strApplicationName) Then
        log.ApplicationName = strApplicationName.Substring(0, strApplicationName.IndexOf(","))
    End If

    container.AddService(GetType(Elmah.ErrorLog), log)
    Return container
End Function ' Elmah_PG_SQL_Callback


' http://weblogs.asp.net/stevewellens/archive/2009/02/01/debugging-a-deployed-site.aspx
Public Sub Initialize()

    If System.Web.HttpContext.Current Is Nothing Then
        Dim req As System.Web.HttpRequest = New System.Web.HttpRequest(String.Empty, "https://www.domain.tld", Nothing)
        Dim res As System.Web.HttpResponse = New System.Web.HttpResponse(Nothing)
        System.Web.HttpContext.Current = New System.Web.HttpContext(req, res)

        'Dim request As System.Web.Hosting.SimpleWorkerRequest = New System.Web.Hosting.SimpleWorkerRequest("/blah", "c:\inetpub\wwwroot\blah", "blah.html", Nothing, New System.IO.StringWriter())
        'System.Web.HttpContext.Current = New System.Web.HttpContext(request)

        System.Web.HttpContext.Current.ApplicationInstance = New System.Web.HttpApplication()

        Dim ErrorLog As New Elmah.ErrorLogModule()
        TryCast(ErrorLog, System.Web.IHttpModule).Init(System.Web.HttpContext.Current.ApplicationInstance)
    End If



    parent = Elmah.ServiceCenter.Current

    If SQL.IsMsSql Then
        Elmah.ServiceCenter.Current = AddressOf Elmah_MS_SQL_Callback
    End If

    If SQL.IsPostGreSql Then
        Elmah.ServiceCenter.Current = AddressOf Elmah_PG_SQL_Callback
    End If
End Sub ' InitializeElmah

And

Elmah.ErrorSignal.FromCurrentContext().Raise(New NotImplementedException("Test"))

will work if it is called after Initialize()

Stefan Steiger
  • 68,404
  • 63
  • 337
  • 408
1

Well, since I can't comment I'll post this down here and maybe someone will see it.

After following Brian's method and the commentors I was able to get email working but I still wasn't seeing the SQL messages being logged, even though I had set the applicationName. What I didn't realize is that they were actually being logged I just wasn't seeing them because the applicationName must be the same as your web.config in order to be able to view it.

My web.config didn't have applicationName specified, so it was defaulting to "/LM/W3SVC/2/ROOT", which is basically what "asgeo1" commented, though I didn't realize it had to be the same.

Since I didn't really have any errors I was concerned with, I configured applicationName in my web.config and my app.config to be the same and now everything shows up like a champ.

<errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ELMAH" applicationName="MyAppName" />
John W.
  • 138
  • 3
  • 8
-6

ELMAH stands for Error Logging Modules and Handlers - referring, of course, to IHttpModule and IHttpHandler.

Console applications do not use HTTP, so would typically not be able to benefit much from Modules and Handlers built for HTTP.

yfeldblum
  • 63,188
  • 11
  • 126
  • 168