2

I've adopted Serilog for my logging needs. I (do my best to) follow the SOLID principles and have thus adopted Steven's adapter which is an excellent implementation.

For the most part, this is great. I have a class called LogEntryDetail which contains certain properties:

class LogEntryDetail
{
    public string Message {get;set;}
    public string MessageTemplate {get;set;}
    public string Properties {get;set;}

    // etc. etc.
}

I will log the LogEntryDetail like this:

    public void Log(LogEntryDetail logEntryDetail)
    {
        if (ReferenceEquals(null, logEntryDetail.Layer))
        {
            logEntryDetail.Layer = typeof(T).Name;
        }

        _logger.Write(ToLevel(logEntryDetail.Severity), logEntryDetail.Exception, logEntryDetail.MessageTemplate, logEntryDetail);
    }

I am using the MSSqlServer sink (Serilog.Sinks.MSSqlServer) For error logging, all is well.

I have a perf logger, which I plug into my request pipeline. For this logger, I don't want to save every property in the LogEntry object. I only want to save the Message property in the Message column of the table which I have created.

So, normally, when you call write on the serilog logger and pass in a complex object, the Message column contains the whole object, serialized as JSON.

I want to know if there is some way that I can specify the MessageTemplate to be something like {Message} or {@Message}, so that the Message column in the database only contains the string stored in the Message property of the LogEntryDetail object. Any other property is redundant and a waste of storage space.

When I specify the MessageTemplate to be {Message}, the Message property contains the full name of the LogEntryDetail type (including namespace).

I feel like I am close and just missing some little thing in my comprehension of Serilog's MessageTemplate feature.

onefootswill
  • 3,707
  • 6
  • 47
  • 101
  • The simple in the linked answer doesn't address how structured data can be passed through the logging pipeline, nor does it account for static analyses like those in https://github.com/Suchiman/SerilogAnalyzer. There's a lot more to Serilog - you might find some value in https://nblumhardt.com/2016/06/structured-logging-concepts-in-net-series-1/ for some broader coverage of how the API works :-) - HTH! – Nicholas Blumhardt Oct 29 '18 at 03:45
  • @NicholasBlumhardt You are right. But I showed how it is being passed in the Log method in my post, which is using a different part of the API i.e. `_logger.write` with the overload that accepts a MessageTemplate. I could always solve the problem just by creating a separate logger which implements a separate interface for perf logging (which is only used in Dev and Staging anyway). I was just curious to see if I could funnell everything through the one Log method, as suggested by Steven. Cheers. – onefootswill Oct 29 '18 at 04:22

1 Answers1

0

I'll just explain what I did here to try and get the best of both worlds. It seems here we have the age-old developer conundrum of sacrificing specific features of a library in order to comply with the SOLID principles. We've seen this before with things like repository abstractions which make it impossible to leverage the granular features of some of the ORMs which they abstract.

So, my SerilogAdapter looks like this:

public class SerilogLogAdapter<T> : ILogger
{
    private readonly Serilog.ILogger _logger;

    public SerilogLogAdapter(Serilog.ILogger logger)
    {
        _logger = logger;
    }

    public void Log(LogEntryDetail logEntryDetail)
    {
        if (ReferenceEquals(null, logEntryDetail.Layer))
        {
            logEntryDetail.Layer = typeof(T).Name;
        }

        if (logEntryDetail.MessageTemplate.Equals(MessageTemplates.LogEntryDetailMessageTemplate, StringComparison.Ordinal))
        {
            _logger.Write(ToLevel(logEntryDetail.Severity), logEntryDetail.Exception, logEntryDetail.MessageTemplate, logEntryDetail);
        }
        else
        {
            _logger.Write(ToLevel(logEntryDetail.Severity), logEntryDetail.MessageTemplate, logEntryDetail.Message, logEntryDetail.AdditionalInfo);
        }
    }

    private static LogEventLevel ToLevel(LoggingEventType severity) =>
        severity == LoggingEventType.Debug ? LogEventLevel.Debug :
            severity == LoggingEventType.Information ? LogEventLevel.Information :
                severity == LoggingEventType.Warning ? LogEventLevel.Warning :
                    severity == LoggingEventType.Error ? LogEventLevel.Error :
                        LogEventLevel.Fatal;
}

If the MessageTemplate is one which represents the whole object, then that will be logged. Otherwise, a custom MessageTemplate can be used and the Message property, along with the AdditionalInfo property (a dictionary) can be logged.

We at least squeeze one more thing out of Serilog, and it is one of its strengths - the ability log using different Message templates and to search the log by Message Template.

By all means let me know if it could be better!

onefootswill
  • 3,707
  • 6
  • 47
  • 101