ASP.NET Core: Implementing Syslog logger

My previous blog post Windows IoT Core: Logging to Syslog server introduced how to communicate with Syslog server from Windows 10 IoT Core applications. This blog post shows how to log messages to Syslog server from ASP.NET Core applications.

Syslog server is popular logs server from Linux world. It is usually separate box or virtual machine that accepts log messages but it is not accessible by external users. It can be specially useful for web applications as hacking to the site doesn’t open a way to internal network and logs are still safe as malicious users cannot access these.

Syslog logger provider

Logger provider creates instance of logger with given parameters and returns it. We will use it later when introducing Syslog logger to logger factory.

public class SyslogLoggerProvider : ILoggerProvider
{
   
private string
_host;
   
private int
_port;

   
private readonly Func<string, LogLevel, bool
> _filter;

   
public SyslogLoggerProvider(string host, int port, Func<string, LogLevel, bool
> filter)
    {
        _host = host;
        _port = port;

        _filter = filter;
    }

   
public ILogger CreateLogger(string
categoryName)
    {
       
return new SyslogLogger
(categoryName, _host, _port, _filter);
    }

   
public void Dispose()
    {
    }
}

ILoggerProvider extends IDisposable and this is why we need Dispose() method here. As we have nothing to dispose the method is empty.

Syslog logger

Logger is created by logger provider and all parameters we use with it will land here.

public class SyslogLogger : ILogger
{
   
private const int
SyslogFacility = 16;

   
private string
_categoryName;
   
private string
_host;
   
private int
_port;

   
private readonly Func<string, LogLevel, bool
> _filter;

   
public SyslogLogger(string
categoryName,
                       
string
host,
                       
int
port,
                       
Func<string, LogLevel, bool
> filter)
    {
        _categoryName = categoryName;
        _host = host;
        _port = port;

        _filter = filter;
    }

   
public IDisposable BeginScope<TState>(TState
state)
    {
       
return NoopDisposable
.Instance;
    }

   
public bool IsEnabled(LogLevel
logLevel)
    {
       
return (_filter == null
|| _filter(_categoryName, logLevel));
    }

   
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string
> formatter)
    {
       
if
(!IsEnabled(logLevel))
        {
           
return
;
        }

       
if (formatter == null
)
        {
           
throw new ArgumentNullException(nameof
(formatter));
        }

       
var
message = formatter(state, exception);

       
if (string
.IsNullOrEmpty(message))
        {
           
return
;
        }

        message =
$"{ logLevel }: {message}"
;

       
if (exception != null
)
        {
            message +=
Environment.NewLine + Environment
.NewLine + exception.ToString();
        }

       
var
syslogLevel = MapToSyslogLevel(logLevel);
        Send(syslogLevel, message);
    }

   
internal void Send(SyslogLogLevel logLevel, string
message)
    {
       
if (string
.IsNullOrWhiteSpace(_host) || _port <= 0)
        {
            
return
;
        }

       
var hostName = Dns
.GetHostName();
       
var level = SyslogFacility * 8 + (int
)logLevel;
       
var logMessage = string.Format("<{0}>{1} {2}"
, level, hostName, message);
       
var bytes = Encoding
.UTF8.GetBytes(logMessage);

       
using (var client = new UdpClient
())
        {
             client.SendAsync(bytes, bytes.Length, _host, _port).Wait();
        }
    }

   
private SyslogLogLevel MapToSyslogLevel(LogLevel
level)
    {
       
if (level == LogLevel
.Critical)
           
return SyslogLogLevel
.Critical;
       
if (level == LogLevel
.Debug)
           
return SyslogLogLevel
.Debug;
       
if (level == LogLevel
.Error)
           
return SyslogLogLevel
.Error;
       
if (level == LogLevel
.Information)
           
return SyslogLogLevel
.Info;
       
if (level == LogLevel
.None)
           
return SyslogLogLevel
.Info;
       
if (level == LogLevel
.Trace)
           
return SyslogLogLevel
.Info;
       
if (level == LogLevel
.Warning)
           
return SyslogLogLevel
.Warn;

       
return SyslogLogLevel.Info;
    }
}

To make this class work we need SyslogLogLevel enumerator.

public enum SyslogLogLevel
{
    Emergency,
    Alert,
    Critical,
    Error,
    Warn,
    Notice,
    Info,
    Debug
}

We need also NoopDisposable class that I borrowed from blog post Building Application Insights Logging Provider for ASP.NET Core by Hisham Bin Ateya.

public class NoopDisposable : IDisposable
{
   
public static NoopDisposable Instance = new NoopDisposable
();

   
public void Dispose()
    {
    }
}

Now we have logger implemented and it’s time to write extension methods that introduce it to logger factory.

Introducing Syslog to logger factory

We follow the same pattern as other loggers including those that come with ASP.NET Core. Loggers are introduced to logger factory by AddSomething() method like AddConsole(), AddDebug() etc. Our method is called AddSyslog().

public static class SyslogLoggerExtensions
{
   
public static ILoggerFactory AddSyslog(this ILoggerFactory
factory,
                                   
string host, int
port,
                                   
Func<string, LogLevel, bool> filter = null
)
    {
        factory.AddProvider(
new SyslogLoggerProvider
(host, port, filter));
       
return factory;
    }
}

Here is how we introduce Syslog logger to logger factory.

loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddFile(
"wwwroot/logs/ts-{Date}.txt"
);
loggerFactory.AddSyslog(
"192.168.210.56", 514);

When we run our application we can see something like this logged to our Syslog server.

ASP.NET Core logs in Visual Syslog Server

Wrapping up

ASP.NET Core logging mechanism may seem like something big at first but it is actually easy. Writing messages to Syslog server is easy too. We implemented Syslog logger provider, logger and extension methods for logger factory. Introducing Syslog logger to logger factory follows the same AddSomething() pattern as other loggers do. It’s easy to try it out on Windows using Visual Syslog Server by Max Belkov.

References

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    Leave a Reply

    Your email address will not be published. Required fields are marked *