Quantcast
Channel: Nicholas Blumhardt
Viewing all 102 articles
Browse latest View live

Serilog 2.0 short level names

$
0
0

Serilog renders plain text logs in a simple format with each event’s timestamp, level and message:

2016-07-18 08:53:53.691 +10:00 [Information] Starting up at 07/18/2016 08:53:53
2016-07-18 08:54:23.716 +10:00 [Error] Timeout reached while waiting for input

If you find the full-width level names like Information and Error awkward to read, you can instruct Serilog 2.0 to write fixed-width names instead:

2016-07-18 08:53:53.691 +10:00 [INF] Starting up at 07/18/2016 08:53:53
2016-07-18 08:54:23.716 +10:00 [ERR] Timeout reached while waiting for input

This is done by overriding the sink’s outputTemplate and specifying a format like u3 for the Level property:

Log.Logger=newLoggerConfiguration().WriteTo.File("log.txt",outputTemplate:"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message}{NewLine}{Exception}").CreateLogger();

Level formats consist of either u (for uppercase) or w (lowercase) and a number of characters. The level names are abbreviated so that Warning is shortened to WRN rather than WAR for example.

All plain text sinks support this option, including Console, File, RollingFile, and Trace. Thanks are due to Antony Koch for driving this feature.


Serilog 2.0 adventures with sub-loggers

$
0
0

The WriteTo.Logger() method pipes events from one Serilog logger into another. While this has been around since Serilog 1.3, some rough edges made it harder to configure correctly than it should have been. In Serilog 2.0 the handling of sub-loggers has been polished up, so now is a good time to write a little about this API.

Why sub-loggers?

Behind the scenes, Serilog treats logging as an event pipeline. It’s not general-purpose like Rx, but if you’re familiar with the idea there are some similarities.

You might expect that because of this, you could write a logger configuration like the following to send all events to one sink (RollingFile()) and a subset to another (everything not from a Microsoft log source to Seq()):

Log.Logger=newLoggerConfiguration().WriteTo.RollingFile("logs-{Date}.txt").Filter.ByExcluding(Matching.FromSource("Microsoft")).WriteTo.Seq("https://my-seq/prd").CreateLogger();

This isn’t the case; in fact, each LoggerConfiguration produces only a single element in the pipeline, with filtering, enrichment, sink dispatching and so-on integrated into it in a fixed order. The configuration above results in events being filtered before reaching either sink.

Here’s another example where we might like to have ordered configuration:

Log.Logger=newLoggerConfiguration().WriteTo.Seq("https://my-seq/prd").Enrich.WithProperty("AppName","Test").WriteTo.Console(newJsonFormatter()).CreateLogger();

The intention of this snippet is to add the AppName property to the console logs but not send it to Seq. Yet, because the Seq sink runs a background thread to serialize and transmit batches of events after some delay, extra work would be needed to prevent the AppName property showing up anyway. Instead, the configuration above performs enrichment before writing to either sink.

Making these kinds of configurations efficient and safe would have added overhead throughout Serilog, regardless of whether a particular configuration called for it or not. Combined with the challenge of representing and preserving an ordered configuration everywhere, for example through XML configuration, it’s unclear whether the end result could be made as simple and robust as the single-element-per-LoggerConfiguration design.

Serilog chose instead to side-steps these issues and impose a set order of activities that can be implemented efficiently and safely. The escape hatch is WriteTo.Logger().

WriteTo.Logger(), shallow copies, and immutability

To control ordering through LoggerConfiguration Serilog provides the WriteTo.Logger() method. Here’s the first example rewritten to perform as intended:

Log.Logger=newLoggerConfiguration().WriteTo.RollingFile("logs-{Date}.txt").WriteTo.Logger(lc=>lc.Filter.ByExcluding(Matching.FromSource("Microsoft")).WriteTo.Seq("https://my-seq/prd")).CreateLogger();

WriteTo.Logger() configures a whole additional logger element with enrichers, filters and sinks.

When an event reaches the the sub-logger, Serilog makes a shallow copy of it and it’s this shallow copy that passes through the sub-logger.

Now, using the same API to implement selective enrichment, the Properties dictionary that AppName is added to isn’t the same dictionary carried by the event in the outer pipeline.

Log.Logger=newLoggerConfiguration().WriteTo.Seq("https://my-seq/prd").WriteTo.Logger(lc=>lc.Enrich.WithProperty("AppName","Test").WriteTo.Console(newJsonFormatter())).CreateLogger();

Serilog can do this shallow copy reasonably cheaply - for each sub-logger, only a LogEvent object and the dictionary need to be allocated. The property values themselves, i.e. log data carried on the event, is immutable and therefore can be shared safely between the original event and the copy.

Two minor caveats

There are two small things that bear mentioning, though now that you know how sub-loggers work, neither should be surprising:

  1. The minimum level set on the sub-logger is limited by the minimum level of the outer, root logger: you can’t set a sub-logger to Debug-level and get Debug events if the root logger is only generating Information, and
  2. Destructuring policies set on the sub-logger won’t have any effect, since by the time an event reaches the sub-logger all of the associated data has already been captured.

When to use sub-loggers

Logging pipelines are best kept simple, so sub-loggers aren’t something you should see or use every day. In a nutshell, if the events or event properties sent to different sinks should differ, WriteTo.Logger() is probably the solution.

One exception; if all you need to do is limit the events going to a sink by level, use the much more efficient restrictedToMinimumLevel parameter instead:

Log.Logger=newLoggerConfiguration().WriteTo.RollingFile("logs-{Date}.txt").WriteTo.Seq("https://my-seq/prd",restrictedToMinimumLevel:LogEventLevel.Warning).CreateLogger();

All sink configuration methods support this argument, and it can be applied with virtually zero overhead.

Hope this helps!

Viewing all 102 articles
Browse latest View live