Structured Logging PR?

@mythz, just wondering if a PR to the ILog interface / implementations would be welcomed or not.

As a user (and a fan) of serilog’s message template-based structured logging in my apps I miss the ability to add properties to my logging when using native SS logging.

Currently the following methods are available from the interface for Debug as an example

void Debug(object message);
void Debug(object message, Exception exception);
void DebugFormat(string format, params object[] args);

Would adding the following method signature for each LogLevel to the interface be welcomed?

void Debug(Exception exception, string nessage, params object[] properties);

This would allow implementations such as Serilog and recent versions of nlog (or any others I’m not aware of) to utilise some of their structured logging capabilities.

bonus points…

going further than type scoping LogManager.GetLogger(typeof(MyType)) (but more complex to implement no doubt) by adding a ‘context’ to the ILog interface would allow properties to be added outwith the message template, or just appended to log message strings for implementations that don’t support structured logging either directly or scoped via a similar concept to serilog’s enrichers.

ILog ForContext(string propertyName, object value, bool destructureObjects = false);
ILog ForContext(ILogEnricher enricher);

hope that makes sense :slight_smile:

We wouldn’t be able to add it to ILog as that would break all existing logging providers (inc. not our own). You could add a new IStructuredLog interface and then use an extension method to

public static void Log(this ILog log, ..)
{
    (log as IStructuredLog)?.Debug(...);
}

I’m not sure what the method signature for this should be, is there an existing logging abstraction that supports multiple structured logging providers? If there is, lets use their method signature, if there isn’t lets not create the IStructuredLog interface now and instead wrap the extension method over the concrete SerilogLogger, e.g:

public static void Log(this ILog log, ..)
{
    (log as SerilogLogger)?.Debug(...);
}

We can create the interface later once we implement it on multiple Logging providers.

I’m not aware of any existing abstractions for structured logging but I’ll have a rummage about.

The second approach probably better for individual logging libs but more likely to fragment approaches without an interface contract to implement against.

Also without an interface, would be harder to promote discoverabllity/usage to gain adoption over time i.e. something like extending LogManager LogManager.GetStructuredLogger(typeof(MyType))

ok after a wee rummage it appears that schema-less structured logging is pretty thin in the .net space outwith serilog.
there is a project for nlog that uses anon classes to serialize json payloads into logs but it doesn’t leverage the message template/hashing/destructuring directives and property storage that serilog uses so effectively. It’s just a string + json payload appended.

I’ve hacked together an example using the IStructuredLog interface you suggested with the quick and dirty extension method for ILogFactory.GetStructuredLog() as I can’t add to the LogManager static i.e. LogManager.GetStructuredLogger(typeof(T)) so using the Serilog.Log.Logger is just a cheat

Here’s the interface where I’ve basically just copied the method signature of serilog using
(exception, messageTemplate, properties)

The reason for the other method signatures which essentially duplicates the DebugFormat style is not really required, but for me, using the term ‘format’ is not really what structured logging (and in particular serilog) is doing

The ForContext methods allow me to scope the logger with additional enrichments,Either globally (things like machine names, thread id’s etc) or locally, in particular the ability to add arbitrary props to logging events that aren’t rendered as part of the message template (again I don’t think exists outwith serilog). Message templates are not just strings but can control the destructuring of objects

i.e.

logger.With("bananaOwner", "Eric").Info("You now have {bananas.Count()} bananas", bananas)

If this is just for Serilog, we shouldn’t create an interface. Just have extension methods on ILog that delegate to Serilog. These extensions can live in Serilog in the ServiceStack.Logging namespace. If we ever have multiple implementations we can extract out an interface then.

for reference:
PR submitted https://github.com/ServiceStack/ServiceStack/pull/1127

2 Likes