Elmah: return the ErrorId from Signal.Raise()

ELMAH is an awesome error logging framework for ASP.NET.  It provides both an elegant way of capturing errors, and an elegant way of displaying them later.  It’s available as a NuGet package, and is both Web Forms and MVC friendly.

Generally, Elmah “just works” and you can walk away happy as a clam.  But there’s times where you want to do custom error logging.  The general solution is to do something like this:

ErrorSignal.FromContext(HttpContext.Current).Raise(ex, HttpContext.Current);

The up-side is you can log anything you want then gently recover.  The down-side is you can’t get the logged ErrorId.  Granted, based on your Elmah filters, maybe it wasn’t logged.  But if it was and that location stores an id (see Elmah’s SqlErrorLog) then it’d be really nice for Raise() to return that Id.

The problem is elegantly described http://code.google.com/p/elmah/issues/detail?id=148 and http://stackoverflow.com/questions/7895271/getting-the-id-of-an-error-in-elmah-after-calling-raise/7902138 with nearly identical results: “You can’t get there from here, don’t bother.”

The closest work-around is to listen to the ErrorLogModule’s Logged event, and shim it to where you need it:

void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args) {
     string key = args.Entry.Id;</span>
     // stash this somewhere</span>
}

Then after the .Raise() call, you pull it from the stash location.  HttpRequest.Items is request-specific cache, and provides a descent stash location.  But this is at best a fragile hack.

As any good programmer would say, this is a problem that can easily be solved with code.  I downloaded the Elmah source, made some quick adjustments, and posted the solution to http://code.google.com/p/elmah/issues/detail?id=148#c3.  On the off chance that post gets deleted and to add more visibility to the simplicity to the solution, here’s the patch file that shows the (not kidding) 14 lines that made this feature a reality.  If you like this patch too, log into Elmah’s project site and “me too” comment to the ticket.

diff -r 2482ebd0a4ae src/Elmah/ErrorLogModule.cs
– a/src/Elmah/ErrorLogModule.cs    Tue May 01 19:31:20 2012 +0200
+++ b/src/Elmah/ErrorLogModule.cs    Wed May 02 16:13:53 2012 -0700
@@ -84,15 +84,21 @@
 
         protected virtual void OnErrorSignaled(object sender, ErrorSignalEventArgs args)
         {
–            LogException(args.Exception, args.Context);
+            string id = LogException(args.Exception, args.Context);
+            if (!string.IsNullOrEmpty(id)) {
+                args.ErrorId = id;
+            }
         }
 
         /// <summary>
         /// Logs an exception and its context to the error log.
         /// </summary>
+        /// <returns>ErrorId, may be null since not all errors are logged</returns>
 
–        protected virtual void LogException(Exception e, HttpContextBase context)
+        protected virtual string LogException(Exception e, HttpContextBase context)
         {
+            string id = null;
+
             if (e == null)
                 throw new ArgumentNullException("e");
 
@@ -105,7 +111,7 @@
             OnFiltering(args);
            
             if (args.Dismissed)
–                return;
+                return id;
            
             //
             // Log away...
@@ -118,7 +124,7 @@
                 Error error = new Error(e, context);
                 ErrorLog log = GetErrorLog(context);
                 error.ApplicationName = log.ApplicationName;
–                string id = log.Log(error);
+                id = log.Log(error);
                 entry = new ErrorLogEntry(log, id, error);
             }
             catch (Exception localException)
@@ -137,6 +143,8 @@
 
             if (entry != null)
                 OnLogged(new ErrorLoggedEventArgs(entry));
+
+            return id;
         }
 
         /// <summary>
diff -r 2482ebd0a4ae src/Elmah/ErrorSignal.cs
– a/src/Elmah/ErrorSignal.cs    Tue May 01 19:31:20 2012 +0200
+++ b/src/Elmah/ErrorSignal.cs    Wed May 02 16:13:53 2012 -0700
@@ -46,15 +46,19 @@
             Raise(e, null);
         }
 
–        public void Raise(Exception e, HttpContextBase context)
+        public string Raise(Exception e, HttpContextBase context)
         {
             if (context == null)
                 context = new HttpContextWrapper(HttpContext.Current);
 
             ErrorSignalEventHandler handler = Raised;
 
+            ErrorSignalEventArgs args = new ErrorSignalEventArgs(e, context);
+
             if (handler != null)
–                handler(this, new ErrorSignalEventArgs(e, context));
+                handler(this, args);
+
+            return args.ErrorId;
         }
 
         public static ErrorSignal FromCurrentContext()
@@ -154,6 +158,11 @@
             get { return _context; }
         }
 
+        /// <summary>
+        /// When you handle the signal, update this with the resulting ErrorId (if any)
+        /// </summary>
+        public string ErrorId { get; set; }
+
         public override string ToString()
         {
             return Mask.EmptyString(Exception.Message, Exception.GetType().FullName);
diff -r 2482ebd0a4ae src/Elmah/ErrorTweetModule.cs
– a/src/Elmah/ErrorTweetModule.cs    Tue May 01 19:31:20 2012 +0200
+++ b/src/Elmah/ErrorTweetModule.cs    Wed May 02 16:13:53 2012 -0700
@@ -129,15 +129,20 @@
 
         protected virtual void OnErrorSignaled(object sender, ErrorSignalEventArgs args)
         {
–            LogException(args.Exception, args.Context);
+            string id = LogException(args.Exception, args.Context);
+            if (!string.IsNullOrEmpty(id)) {
+                args.ErrorId = id;
+            }       
         }
 
         /// <summary>
         /// Logs an exception and its context to the error log.
         /// </summary>
 
–        protected virtual void LogException(Exception e, HttpContextBase context)
+        protected virtual string LogException(Exception e, HttpContextBase context)
         {
+            string id = null;
+
             if (e == null)
                 throw new ArgumentNullException("e");
 
@@ -150,7 +155,7 @@
             OnFiltering(args);
            
             if (args.Dismissed)
–                return;
+                return id;
 
             //
             // Tweet away...
@@ -240,6 +245,8 @@
 
                 OnWebPostError(request, localException);
             }
+
+            return id;
         }
 
         private void OnWebPostError(WebRequest request, Exception e)