Logging All Exceptions in ASP.NET and WCF

I find it useful to log all uncaught exception centrally in ASP.NET web applications and WCF services. In ASP.NET web applications, it is relatively easy – just put logging code in Global.asax.cs like this:

        protected void Application_Error(object sender, EventArgs e)
        {
            var ex = Server.GetLastError();
            if (ex is HttpUnhandledException && ex.InnerException != null)
                ex = ex.InnerException;

            var httpException = ex as HttpException;
            if (httpException != null && httpException.GetHttpCode() == 404)
            {
                // Log this
                Uri urlReferrer = null;
                try
                {
                    urlReferrer = Request.UrlReferrer;
                }
                catch (Exception)
                {
                    // ignored
                }
                Log.WarnFormat("{0} (Client address={1}, UrlReferrer={2})", ex.Message, Request.UserHostAddress, urlReferrer);
            }
            else
                Log.Error(ex.Message, ex);
        }

One thing to note is that uncaught exceptions are wrapped in an HttpUnhandledException – that is why I extract the inner exception. Another thing to note is that you may not want to log 404 not found or as here, log it with a different level.

With WCF, it is (as always) a little more complicated. Putting code in Application_Error in Global won’t work. You have to write one or two classes that implement System.ServiceModel.Dispatcher.IErrorHandler and System.ServiceModel.Description.IServiceBehavior, and do some configuration work. There are different ways to do this. See e.g. http://codeidol.com/csharp/wcf/Faults/Error-Handling-Extensions/. But here is my simplistic solution:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using log4net;

namespace MyNamespace
{
    public class ErrorHandler : IErrorHandler, IServiceBehavior
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(ErrorHandler));

        #region IErrorHandler Members

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            var faultException = new FaultException("Error caught in API error handler. Please see API server log for details.");
            var messageFault = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, messageFault, faultException.Action);
        }

        public bool HandleError(Exception error)
        {
            Log.Error(error.Message, error);
            return false;
        }

        #endregion

        #region IServiceBehavior Members

        // This behavior modifies no binding parameters.
        public void AddBindingParameters(
            ServiceDescription description,
            ServiceHostBase serviceHostBase,
            Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection parameters
            )
        {
        }

        // This behavior is an IErrorHandler implementation and must be applied to each ChannelDispatcher.
        public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
            {
                chanDisp.ErrorHandlers.Add(this);
            }
        }

        public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        #endregion
    }

    public class ErrorHandlerBehavior : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new ErrorHandler();
        }

        public override Type BehaviorType
        {
            get { return typeof(ErrorHandler); }
        }
    }
}

In ProvideFault, which executes on the request thread, I return a generic error to the client (for security reasons). It is logged in HandleError, which executes after the response has been sent.

Here is part of web.config to use this error handler:

  </system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="ErrorHandler" type="MyNamespace.ErrorHandlerBehavior, MyNamespace" />
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyBehavior">
          <!-- ... -->
          <ErrorHandler/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s