Using ELMAH in a console application

.NetDesktop ApplicationConsole ApplicationElmah

.Net Problem Overview


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?

.Net Solutions


Solution 1 - .Net

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

Solution 2 - .Net

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

Solution 3 - .Net

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="[email protected]" to="[email protected]" />
  </elmah>

And smtp settings of your choice.

All done. :-)

Solution 4 - .Net

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.

Solution 5 - .Net

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

Solution 6 - .Net

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

Solution 7 - .Net

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael La VoieView Question on Stackoverflow
Solution 1 - .NetBrian ChanceView Answer on Stackoverflow
Solution 2 - .NetNeil BostromView Answer on Stackoverflow
Solution 3 - .NetJeffView Answer on Stackoverflow
Solution 4 - .NetGavin MillerView Answer on Stackoverflow
Solution 5 - .NetStefan SteigerView Answer on Stackoverflow
Solution 6 - .NetJohn W.View Answer on Stackoverflow
Solution 7 - .NetyfeldblumView Answer on Stackoverflow