Skip to Main Menu

Exploring Serilog v2 - The Console Sink

Following on from my initial post here a some quick notes on the Serilog Console Sink.

This sink has a varied history, originally packaged with the Serilog library. It was split out during the great sink split of 2015, and at that time the features were split into two projects namely the Console Sink and the Coloured Console Sink. Soon after Nick introduced the Literate Console Sink inspired by literate programming. However not too long ago after a series of updates and changes, it made sense to merge these three projects/packages back together.

So what do you get, and why would you want to use a console sink?

First the why… Many developers today are using container services which often default to STDOUT for logging. In fact this is my primary use case for using the console sink. The Console Sink makes provides a low barrier to entry if you are using .Net Core on Docker and just want to get moving.

Let’s open the box…

Taking a quick shortcut from my last post create a new console project with the following code.

using System;
using Serilog;

namespace example_console
{
    class Program
    {
        static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .WriteTo.Console()
                .CreateLogger();
 
            Log.Verbose("This is a verbose statement");
            Log.Debug("This is a debug statement");
            Log.Information("This is a info statement");
            Log.Warning("This is a warning statement");
            Log.Error(new IndexOutOfRangeException(), "This is an error statement");
            Log.Fatal(new AggregateException(), "This is an fatal statement");
        }
    }
}

Restricted Level

Like most sinks, the Console Sink will allow you to override the specific events piped to the sink for a particular log level. In the following example, only events with a severity level of Error or above will be sent to the Console Sink.

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Error)
    .CreateLogger();

NOTE: Be wary, this filtering is subject to the minimum level placed on the logger. If the logger your sink is attached to, is set to MinimumLevel.Error, the sink will never recieve any events lower than this severity.

Themes

The Console Sink comes with a number of themes out of the box. This can be applied in config or via the fluent interface. To use the themes, you will need using Serilog.Sinks.SystemConsole.Themes;. Below is the output of the core themes.

ConsoleTheme.None

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: ConsoleTheme.None)
    .CreateLogger();

Output

SystemConsoleTheme.Literate

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: SystemConsoleTheme.Literate)
    .CreateLogger();

Output

SystemConsoleTheme.Grayscale

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: SystemConsoleTheme.Grayscale)
    .CreateLogger();

Output

AnsiConsoleTheme.Literate

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: AnsiConsoleTheme.Literate)
    .CreateLogger();

Output

AnsiConsoleTheme.Grayscale

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: AnsiConsoleTheme.Grayscale)
    .CreateLogger();

Output

AnsiConsoleTheme.Code

Example config

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: AnsiConsoleTheme.Code)
    .CreateLogger();

Output

Custom Themes

If the in-built themes are not working for you, a custom theme is a simple implementation. In short you can implement a ConsoleTheme and apply that configuration to the Console sink. Taking a look at the Ansi Themes illustrates the process of returning a Dictionary<ConsoleThemeStyle, string> to map between the ConsoleThemeStyle and the expected format. For a full list of the styles, check out the definitions at ConsoleThemeStyle.

If you really do need to create a custom theme, you will need to implement a few key methods.

Here is a quick example in action that beeps on Fatal and Error events.

class BeepingTheme : Serilog.Sinks.SystemConsole.Themes.ConsoleTheme
{
    /// <summary>
    /// True if styling applied by the theme is written into the output, and can thus be
    /// buffered and measured.
    /// </summary>
    public override bool CanBuffer => false;

    /// <summary>
    /// Begin a span of text in the specified <paramref name="style"/>.
    /// </summary>
    /// <param name="output">Output destination.</param>
    /// <param name="style">Style to apply.</param>
    /// <returns></returns>
    protected override int ResetCharCount => 0;

    /// <summary>
    /// Reset the output to un-styled colors.
    /// </summary>
    /// <param name="output">The output.</param>
    public override void Reset(TextWriter output)
    {
        Console.ResetColor();
    }

    /// <summary>
    /// The number of characters written by the <see cref="Reset(TextWriter)"/> method.
    /// </summary>
    public override int Set(TextWriter output, ConsoleThemeStyle style)
    {
        Console.BackgroundColor = ConsoleColor.Black;
        
        if(style == ConsoleThemeStyle.LevelFatal || style == ConsoleThemeStyle.LevelError)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Beep();        
        }   
        else
        {
            Console.ForegroundColor = ConsoleColor.White;  
        }
        
        return 0;
    }
}

.....

static class MyThemes
{
    public static BeepingTheme Beep {get; } = new BeepingTheme();
}


....

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(theme: MyThemes.Beep)
    .CreateLogger();

Output templates

The console sink also gives you the opportunity to change the format of the event rendered using the in built message template syntax. In the following example we can show the full date and time along with a full level description.

var template = "[{Timestamp:yyyy-mmm-dd HH:mm:ss} {Level}] {Message}{NewLine}{Exception}";

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(outputTemplate: template)
    .CreateLogger();

If exceptions are thrown, we see the exception is placed on a new line after the log message.

[2018-16-04 16:16:21 Information] This is a info statement
[2018-16-04 16:16:21 Warning] This is a warning statement
[2018-16-04 16:16:21 Error] This is an error statement
System.IndexOutOfRangeException: Index was outside the bounds of the array.
[2018-16-04 16:16:21 Fatal] This is an fatal statement
System.AggregateException: One or more errors occurred.

Configuration

On occasion, you may want to configure the Console Sink via a file rather than the fluent interface. I generally use the JSON configuration however you can also use the XML setup found in a traditional app.config.

First add the required packages.

  • dotnet add package Serilog.Settings.Configuration - This is a Serilog package which is a nice wrapper around Microsoft.Extensions.Configuration
  • dotnet add package Microsoft.Extensions.Configuration.Json - This package is for the Microsoft JSON file support.

Then add an appsettings.json, with the following setup.

{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console"], // Set the namespace to find sinks etc.
    "MinimumLevel": "Information", // Set the min level of logging
    "WriteTo": [{
      "Name": "Console", // Set the sink to write events to.
      "Args": {
        "outputTemplate": "[{Timestamp:HH:mm:ss} {Properties} {SourceContext} [{Level}] {Message}{NewLine}{Exception}", // Set the min level of logging.
        "theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console"  // Set the theme.
      }
    }],
    "Properties": {
      "Application": "Exploring Serilog" // Enrich events with global properties.
    }
  }
}

Finally, update the setup to use the appsettings.json file.

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .CreateLogger();

This is just a quick overview of some of the features available in the Serilog Console Sink.

Get Amongst It!!

comments powered by Disqus