X

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.

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

Liked this post? Empower your friends by sharing it!
Categories: ASP.NET
Related Post