Stefan Holm Olsen

Logging from OWIN to the default Episerver log

In the process of implementing OWIN middlewares for my previous blog post, I needed to add logging to various parts of my code for easier debugging. And I wanted it to be as simple and transparent as possible.

Basically, I wanted a way to use the built-in OWIN log abstraction interface and make it write to the default Episerver log stream. This short blog post is about the log adapter I had to make.

A simple solution

The easiest way, which most of us Episerver developers are used to, would be to simply use a static Episerver logger in all our custom middlewares. Like this:

private static readonly ILogger Logger = LogManager.GetLogger(typeof(MyMiddleware));

This approach works well for our custom middleware classes. But any other middleware will still be logging to the OWIN logging abstraction interface, which may not have any listeners.

And besides, I believe that an OWIN middleware should be transient and have no static members.

So, in order to hook OWIN up to the Episerver logging framework, I created this very simple logging adapter. By using this, all your OWIN log entries will end up at the exact same destination as your regular logs, be it log4net files, Application Insights, Serilog or something else.

using System;
using System.Diagnostics;
using EPiServer.Logging;
using Microsoft.Owin.Logging;

namespace StefanOlsen.Episerver.Owin
{
    public class EpiserverLoggerFactory : Microsoft.Owin.Logging.ILoggerFactory
    {
        public ILogger Create(string name)
        {
            return new EpiserverLogger(name);
        }
    }
    
    public class EpiserverLogger : Microsoft.Owin.Logging.ILogger
    {
        private readonly ILogger _logger;

        public EpiserverLogger(
            string name)
        {
            _logger = LogManager.Instance.GetLogger(name);
        }

        public bool WriteCore(
            TraceEventType eventType,
            int eventId,
            object state,
            Exception exception,
            Func<object, Exception, string> formatter)
        {
            Level level = GetLogLevel(eventType);

            _logger.Log(level, formatter(state, exception));

            return true;
        }

        private static Level GetLogLevel(
            TraceEventType eventType)
        {
            switch (eventType)
            {
                case TraceEventType.Critical:
                    return Level.Critical;
                case TraceEventType.Error:
                    return Level.Error;
                case TraceEventType.Warning:
                    return Level.Warning;
                case TraceEventType.Information:
                    return Level.Information;
                case TraceEventType.Verbose:
                    return Level.Trace;
                case TraceEventType.Start:
                case TraceEventType.Stop:
                case TraceEventType.Suspend:
                case TraceEventType.Resume:
                case TraceEventType.Transfer:
                    return Level.Debug;
                default:
                    throw new ArgumentOutOfRangeException(nameof(eventType));
            }
        }
    }
}

For this to work, we also need to specify in the OWIN startup class that all of OWIN should use that new EpiserverLoggerFactory class. Like this:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetLoggerFactory(new EpiserverLoggerFactory());
        // ...
    }
}