Querying collection properties in Seq 1.5

Seq implements a filtering syntax that’s loosely based on C#. The following expression matches the events you’d expect it to, if you’re used to working with C# or a similar language:

Tag == "seq"

Which is to say: “Match events where the Tag property has the value "seq". Sub-properties of objects can be accessed using the familiar dotted syntax (Property.Subproperty.Subproperty) and so-on.

Seq 1.5, currently a preview, steps into some new territory.

What about a collection of tags?

It’s not uncommon for Serilog events to carry properties that are collections. For example we might log events on a Q&A site like:

Log.Information("User {Username} posted a question tagged {Tags}", username, tags);

Resulting in events like:

Event with Collection Property

Instead of being marked with a single tag, each event carries a collection of zero-or-more.

Matt Hamilton asks:

This is where Seq 1.4 draws a blank. Translating this to the Q&A example, about the best we can manage is a text search for "seq" and requiring the existence of the Tags property using Has(Tags).

This is a bit of a shame – the promise of Seq and structured logging in general is to make querying log events precise – so why aren’t collections covered?

The C# way

Seq isn’t only about precise queries; the whole foundation of the app is the idea that it should also be easy and natural to query log data.

I’d thought about collection queries quite a lot when putting the original filtering syntax together. C#-style the query would look something like:

Any(Tags, tag => tag == "seq")

Seq doesn’t use extension method-style calls, but this is pretty close to the Enumerable.Any() call you’d write using Linq.

I don’t like it!

In a full-featured programming language this syntax makes sense, but it’s too noisy, complex, and easy to mistype to make a good filtering syntax for Seq. And so, that’s where things have sat for the last twelve months.

Pattern matching

With a bit more thought, Matt and I both ended up thinking the problem might be better suited to a pattern-matching syntax like:

Tags[?] == "seq"

So, this is what Seq 1.5 provides. The question mark wildcard ? matches any element in the collection, while an asterisk * wildcard only matches if all elements satisfy the condition.

Questions tagged seq

Wildcards work in any comparison where one side is a property path (including indexers and dotted sub-properties). Multiple wildcards along the path are supported.

You can write the following expression to find questions where all answers are "Yes!":

Answers[*].Content == "Yes!"

(Under the hood, Seq rewrites these expressions into the equivalent lambda-style syntax – much the same way as the C# compiler transforms Linq query comprehensions. Working through implementing this reminded me just how fantastic the C# compiler really is!)

Try it out!

You can download a preview build of Seq 1.5 here. It’s not ready for production use yet, but should be just fine for dev/non-mission-critical servers. We’d love to hear what you think!

How (not) to parameterize Serilog events

Writing an event with Serilog is just like formatting a string:

Log.Information("The time is {Time}", DateTime.Now);

There’s one subtle and important difference though. The first parameter to Information(), here given the value "The time is {Time}" isn’t just an arbitrary string – it’s a Serilog message template.

You might be tempted occasionally to write events like:

Log.Information("The time is " + DateTime.Now);

Don’t do this. While never particularly good logging practise (the string concatenation occurs regardless of whether logging is enabled or not, wasting RAM and CPU cycles), with Serilog this is strongly considered an anti-pattern.

Why an anti-pattern?

The first example, using Serilog correctly, creates events that are logically like:

{
  "MessageTemplate": "The time is {Time}",
  "Properties": { "Time": "2014-09-11T09:35.55.000" }
}

The second, incorrect example will yield:

{
  "MessageTemplate": "The time is 2014-09-11T09:35.55.000",
  "Properties": {}
}

While you might not notice the difference initially when logging to the console or a text file, the first event is much more useful than the second:

  • Time is stored as a queryable property
  • All events of this type share the same message template, making the events queryable by type as well

Serilog is also optimised for creating well-formed events like the first one; while the second example won’t blow up, since each event has a unique “template” it will not take advantage of message template caching, requiring the message to be parsed each time. The extra junk in the message template cache will eventually cause the cache to flush, reducing logging performance throughout the app.

Be on the lookout…

There are a few ways your code can fall into this trap. Here’s (a simplification of) one that got me recently:

catch (Exception ex)
{
  Log.Error(ex.Message);
}

It’s a bit harder to spot the logging of an arbitrary string here, but like the broken example above, this will generate a potentially-unique template for every message.

(In case you’re wondering, the recommended way to log an exception with Serilog is:

catch (Exception ex)
{
  Log.Error(ex, "Failed while trying to open the database");
}

The exception will be attached to the event as a first-class property, and fully-rendered in both text and JSON… Easy!)

Event = Template + Properties

Just remember, a Serilog event isn’t a string – it’s the combination of a message template and zero-or-more properties.

Thinking this way will help you get more value out of Serilog as your application grows and you start to consider more advanced storage/querying options. Happy logging!

Seq 1.5 preview

TL;DR: The next point release of Seq is a bit broader in scope than what we’ve shipped in previous point releases. Storage and caching changes in Seq 1.5 bring some noticeable improvements to performance, responsiveness and manageability.

Since Seq first arrived a year ago, its storage has been a simple b-tree based data file managed by ESENT. This had pros as well as cons: on the pro-side, we’ve been able to keep our efforts focused on making Seq quick to set up and easy to use while ESENT does its thing keeping data safe and accessible on disk.

This approach has taken us a long way, but coupled with the particular access patterns generated by log data, it does lead us to some cons, the biggest of which we’re addressing in Seq 1.5.

Query performance and cache efficiency

In Seq 1.4, we left caching decisions to ESENT – what to cache, when to expire it, and how much RAM to use in the process. For the most part this did a fine job, but it left a couple of important opportunities on the table.

Segmented caching

Log data has a very specific access pattern: recent events are always more interesting than historical ones. A couple of queries deep into history shouldn’t cause more recent events to be purged from cache. Recently-written events should get to the cache before needing queries to “warm them up” and so-on.

In 1.5, Seq keeps a time-ordered list of cached event segments. Each segment is a time slice – the preview uses 1-day slices as the unit of granularity. As RAM fills up, segments are dropped from the end of the list, preserving more recent events.

Cache

It’s a very simple strategy, but an effective one – response times on queries improve dramatically when all data is in cache, and most queries of interest are filled by recent events.

Naturally, response time drops as the query progresses into un-cached regions of storage. Our current “usability benchmark” is to consider a million 1 KB events generated per day. On a 16 GB server, around a week’s data will be cacheable, and response time on this will be within a few seconds. Most Seq installations carry a lot fewer events and so will run completely in RAM.

Past the cache bounds, “archival” data is available at a response time a couple of orders of magnitude below this. Optimally, a retention policy will thin out the events past this point, e.g. by deleting “debug” or tracing events, thus bringing response times back up within a similar range.

Optimal in-memory representation

Using the storage layer’s cache meant Seq still had quite a bit of work to do when scanning events to fulfil query results. This work not only added to response time – it also generated garbage that pushed the GC harder, reducing overall performance.

Since we’re now in control of our own caching destiny, in Seq 1.5 we’re free to choose an in-memory representation that’s much closer to our needs when querying. This eliminates all JSON processing and allocation on a per-event basis, and makes it possible to run practically all queries in compiled code.

Schema-sharing

The most exciting outcome of choosing our own in-memory representation is the opportunity we then have to extract common features from events.

When caching raw event data at the storage layer, each “blob” is unique – property names and so-on get stored for every event. If two events are created by the same line of code, thus having the same property names and message template, the storage layer will record two copies of this information.

In RAM, we’re free to share schema information and duplicated values between many events. While the following two events share nothing on disk:

{"Latitude": 25, "Longitude": 134}
{"Latitude": 12, "Longitude": 54}

In the Seq caching layer they’ll share a single schema object:

Schema

Over many events the savings brought by this approach are significant.

Manageability

Using a single, large data file brought with it a couple of major drawbacks.

Sheer size

First, storing everything in a single large file makes it hard to work with large Seq installations. One user ended up with a single 350 GB default.seq that was impossible to work with in any way. We don’t want to see that again!

In Seq 1.5, files on disk are split into 7-day extents:

Storage

The extents are completely self-contained, meaning they can be deleted, moved or backed-up individually.

Compaction time

ESENT data files can only be compacted (shrunk to remove free space) off-line. Coupled with creating a very large data file, this could mean considerable downtime while disk space was reclaimed, and having to use command-line tools to do it was inconvenient.

Compaction would also require enough free disk space to hold a complete copy of the data – a challenge and a waste when a lot of event data is stored.

Using a series of extents on disk means that segments can be compacted one at a time, ideally in the background on a running server, and only the storage for a single extent is required during the compaction process.

The 1.5.5-pre build still relies on a command-line seq compact facility, but storage requirements are lessened and the process is much more responsive. We’ll be working on background compaction for the next preview.

Installing Seq 1.5.5-pre

There are a few things to know before you upgrade to Seq 1.5.5-pre.

The migration process requires approximately the same disk space as is taken by the existing C:\ProgramData\Seq\Data folder. Seq will refuse to migrate or run without this available. After migration is run, the space will be freed and won’t be needed again except to store events.

Running the following commands prior to migration will free as much space as possible for the process:

seq stop
seq compact

Migration from a large Seq 1.4 database can also take considerable time – allowing 10 minutes per million events would give a safe margin; better hardware will move data much faster. If this makes migration prohibitive, and you can’t use retention policies to cut down on event volume beforehand, please email support for help.

After migration completes, it will take a little while for the cache to warm up. If you see an asterisk (*) after the Loading… indicator text, this means events are being scanned in un-cached storage regions. Give it a few more minutes :).

Finally, the new cache is designed for server use and thus consumes as much system memory as it can. If the system comes under memory pressure, it will release memory to compensate. This mightn’t be convenient on all systems – if you’re in this position we’d like to know a bit more about your setup: email us at the address above and we’ll let you know when configuration options are available to control this.

What else is to come from 1.5?

Already included in the current preview are some major improvements to start-up/shut-down time, and some long overdue features such as manual deletion of events. There are several other tickets planned for the full release, and I’ll post some details on those as they land.

How does 1.5.5-pre work for you?

We haven’t published any figures with this post; when Seq 1.5 is ready to ship I’ll make sure we share some 1.4 vs. 1.5 benchmarks, but for the time being we’re looking for real-world usage and feedback to validate the direction we’re taking and provide a qualitative measure of how the changes stack up.

If you’re able to try Seq 1.5, we’d love to hear your impressions. It’s not a production-ready release, but we’ll do our very best to support migration from the preview to RTW when that happens (we’ve migrated our own servers!) so with some small risk it should be fine to use on dev/non-mission-critical systems.

Download it now!

You can grab the installer here.

Seq and Serilog around the web – August

Things have been quiet on my blog for the last few months, but certainly lively elsewhere around the web. I thought I’d post a few pointers to what’s new in the worlds of Seq and Serilog.

Hey— I’ve started cross-posting anything related to Seq on the Seq blog; this (nblumhardt.com) site will continue as my personal blog with a bit more varied material including most Seq content for now, so stay tuned in!

Modern Structured Logging with Serilog and Seq

If you or your company has a Pluralsight subscription, make sure you check out the great Modern Structured Logging with Serilog and Seq course written and presented by Jason Roberts.

The first two major sections of the course give a deep introduction to structured logging and Serilog, while the third section introduces Seq; the course is worth watching for either or both.

If you’re championing structured logging in your organisation, Jason’s course would make a great way to bring others up to speed.

Improve and Repeat

Johnny Graber’s Improve and Repeat blog is featuring Serilog in a series on modern structured logging. The first two instalments are up:

  1. The Missed Opportunities of Log Files
  2. Structured Logging with Serilog

It’s shaping up to be another good resource for teams starting to introduce structured logging.

If your team still needs convincing that learning about Serilog is worth their time, Kristoffer Jansson’s Why You Should Try Out Serilog should help.

Event-processing angles

I thought it was cool to see this sample from the XSockets.NET team showing how to connect to a running (Serilog-enabled) application to collect logs.

Unlike other sinks we’ve seen to date, in this case the sink is a server, and clients connecting via XSockets request the level of log events they wish to be sent. It’s a really exciting angle that I think deserves serious exploration (think on-demand real-time diagnostics…)

Tangentially related (since we’re talking event processing here :)) Daniel Little posted a nice little primer on Debugging Rx with Seq, which is a pretty imaginative use for it.

Site redesigns!

Both Serilog and Seq themselves got new websites in the last couple of months.

Serilog is now an eye-catching red design:

Serilog.net

I’m really happy with the way this one turned out – it’s nice and simple, and keeps all of the important info (how to use Serilog; where to find documentation and so-on) front-and-center.

Seq now has a new layout that makes it easier to organize content:

GetSeq.net

Currently the biggest improvement, other than the livelier design, is better access to older versions and release notes via the improved Downloads area. It’s also nice to have complete setup instructions back on the front page to show just how easy it is to get started.

What have I missed?

Let me know :)

Can we change the face of JavaScript logging, too?

If you use Serilog in .NET and write code for the web, chances are you’ve wondered whether similar hybrid “structured/text” loggers exist for the browser and Node.js

serilog-js

If you haven’t tried Serilog, here’s a one-minute primer (using JavaScript syntax) so you can follow the rest of this post. Using Serilog is similar to every logging framework, ever:

var name = "Nick";
var wait = 45;
log("Hi {name}, your flight leaves in {wait} minutes", name, wait);

Look carefully at the string formatting though – particularly the way we give names to each of the parameters. The “message” part isn’t destructively rendered into text, but preserved as an object with property values. It can be formatted to the console etc. as usual:

[inf] Hi Nick, your flight leaves in 45 minutes

But more interestingly, it can be serialized in a format like JSON for processing and querying:

{
  "message": "Hi Nick, your flight leaves in 45 minutes",
  "messageTemplate": "Hi {name}, your flight leaves in {wait} minutes",
  "properties":  {
    "name": "Nick",
    "wait": 45
  }
}

Preserving the structure of log data, and making it trivially easy to enrich that data with more contextual information, makes it possible to do some really clever things with the resulting logs.

Try finding all messages where the user’s name starts with “N”, or where the wait time is greater than one hour in a traditional log file if you’re skeptical here. In some trivial cases a regular expression might handle this cleanly, but it quickly becomes complex and cumbersome when more data is involved. Using Serilog it can be as simple as startswith(name, "N") or wait > 60.

Preserving the message template itself ("Hi, {name}...") also has some interesting and very useful results: the message template becomes a kind of “message type” that can be used to find – or exclude – messages of a specific type. Again very hard, often impossible with traditional loggers.

What’s going on in JavaScript?

In JavaScript as elsewhere, there are already some sophisticated logging frameworks, with varied levels of structured data support. A fairly new project called node-bunyan interests me the most, with its clever DTRACE integration and support for serializing objects instead of messages, but even here the new hybrid-style API and DSL provided by Serilog is missing. With node-bunyan it’s possible to log an object and/or a message, but messages are still formatted out in a lossy manner like the earlier loggers on .NET did.

Whether it grows into a standalone project in its own right, or encourages more established frameworks to take a look at this new style of logging, bringing Serilog’s API to JavaScript seems like a fun and worthwhile thing to try.

serilog.js

Tinkering away over a month of so I’ve sketched out what I think serilog.js should look like.

serilog.js-example

Yes, serilog.js terminal output is type-aware, just like syntax highlighting in an IDE! It’s useful when dealing with structured events to see the difference between a number 5 (magenta for numbers) and a string "5" (cyan for strings). In itself this feature is a nice trimming, but it’s not just a party-trick; below the surface, the event is carrying all of this information.

Getting started – installing from NPM

All the code is currently published to NPM. At the command line:

npm install serilog

Then in your node application:

var serilog = require('serilog');
var terminal = require('serilog/src/serilog-terminal-sink');
var file = require('serilog/src/serilog-file-sink');

Serilog “sinks” are destinations for log events. This example uses the terminal sink, which writes to the system terminal/console. At the moment, the sinks are all zipped up directly in the serilog package, but they need to be split out.

Tour of the API

At present, with very little of the back end ‘sinks’ built, serilog.js is all about the API.

Configuring the pipeline

One of the first things to notice is that the logger is a pipeline. Thinking of log events as structured data leads naturally to an API more like what you’d find in event processing frameworks than the typical logging library.

var log = serilog.configuration()
  .writeTo(terminal())
  .minimumLevel('WARNING')
  .writeTo(file({path: 'log.txt'}))
  .createLogger();

Each element is applied in turn:

  • serilog.configuration() kicks things off by creating a new configuration object
  • .writeTo(terminal()) emits all events to the terminal sink
  • .minimumLevel('WARNING') filters out any that are below the WARNING level (we’ll cover levels below)
  • .writeTo(file({path: 'log.txt'}) emits the events that reach it to a log file, and
  • .createLogger() takes all of these pipeline stages and bundles them up into an efficient logger object

The result is log, which we’ll use below. (As in Serilog for .NET, there’s no “global” logger – multiple loggers can be created and are completely independent, unless you link them together with something like .writeTo(log2)).

You might be wondering – why not Node.js streams or RxJS etc.? Simple really – generic APIs are great for glueing together solutions, but for something that’s used extensively in a codebase, maintaining complete control over dependencies, performance and semantics is a good idea long-term, and that’s the path taken here.

Logging levels

serilog.js has four simple logging levels, listed here in order of importance:

log.trace('This is a lot of detail');
log.information('Something of note happened');
log.warning('Look out, wheels are coming off!');
log.error('Something's broken');

If log.information() looks like a bit of a mouthful, don’t worry: the log object itself is a function that can be called as a synonym for information:

log('This is an information-level event');

The default level for a new logger is INFORMATION; using .minimumLevel('TRACE') at the start of the pipeline will bump this up.

Message templates

The syntax of message templates is carried over directly from Serilog. Event properties are named by including them in braces, and the values are provided as positional arguments:

log('{a} + {b} = {c}', 1, 2, 3);
// -> {properties: {a: 1, b: 2, c: 3}}

I’ve left the message/template/timestamp out of the example output (// -> ...) to keep the focus on the structured part of the event, but these other parts of the event exist too.

If you pass an object to the log, e.g.:

var mail = {to: 'nblumhardt', subject: 'Is the post finished?'};
log('Sending {mail}', mail);
// -> {properties: {mail: '[Object] object'}}

You might be surprised that it’s stored using toString(). We haven’t missed a great opportunity to serialize here – the problem is that it is very easy to serialize too much data by mistake this way.

If you know that what you’re passing into a log method is a nice little chunk of data to work with later, prefix the property name with @ to say “I know what I’m doing – serialize this!”:

var mail = {to: 'nblumhardt', subject: 'Is the post finished?'};
log('Sending {@mail}', mail);
// -> {properties: {mail: {to: 'nblumhardt', subject: 'Is the post finished?'}}}

serilog.js can handle structured storage of objects and arrays nicely, and these can support some really cool processing scenarios – just don’t forget the @ (if you watch the events roll by on the terminal, you’ll notice the difference too – objects are colored green, while strings render in cyan).

Context and enrichment

The strength of structured logs is in correlation; this is one of the great challenges when instrumenting distributed apps, and probably the #1 reason that interest in structured logs has picked up again in recent times.

When searching a large number of events produced across many machines, it’s vital to have properties to filter on so that a single location or thread of interaction can be followed.

In a simple case, you might want to instrument all events with the name of the machine producing them. This can be done when configuring the logger:

var log = serilog.configuration()
  .enrich({machine: env.HOSTNAME})
  ...

Events created by the logger will all carry the specified properties:

log('Hello, {name}', 'Nick');
// -> {properties: {name: 'Nick', machine: 'NBLUMHARDT-RMBP'}}

More often though, it’s desirable to tag events produced within a part of the application, or in a transaction. For this the using() method is provided on the logger itself:

var smtpLog = log.using({subsystem: 'SMTP'});
smtpLog('About to send mail to {name}', 'Nick');
// -> { properties: {subsystem: 'SMTP', name: 'Nick'}}
Filtering

A final part of the API to check out is filtering. While most filtering will happen server-side wherever you collect your logs, it can be useful to apply some filters in the logging pipeline if you have a component producing a lot of noise, or if you need to avoid passing events with sensitive data to logs.

This fits in as you’d expect:

var log = serilog.configuration()
  .filter(function(evt){
    return evt.messageTemplate.raw !== 'Making {noise}' &&
      evt.properties.subsystem !== 'SMTP';
  })
  .writeTo(terminal())
Building serilog.js

If you want to tinker with the project, you can grab the source from GitHub. The build process uses gulp to drive some mocha tests. Just type:

npm install
gulp

…and you’re away!

The browser

serilog.js also runs in the browser (though version support will be patchy and is completely untested!). There are some notes on the GitHub site linked above on how to do this, and a test.html page in the repository that sets some of it up.

The catch

Serilog in .NET is out there in production use today. It’s being extended and refined by a core group of contributors and takes what time I have available to coordinate.

I’ll be using serilog.js in a few small JavaScript projects and extending it for my own needs, and I’ll be building a sink that writes to Seq at some point. But, I don’t have the capacity to really drive this project beyond the prototype that it is right now.

If you’re interested in JavaScript, event processing, application monitoring and/or API design, serilog.js needs you. Kick the tyres, check out the code, send a pull request! It’s a project “up for grabs” in the largest sense. Can you help change the face of JavaScript logging?

Seq 1.4 preview is out!

The features planned for Seq 1.4 are starting to come together. There’s a new preview available to download on the Seq website (direct download).

While 1.4 is still a little way off “done”, many of the improvements we’ve made are usable today; here’s a run through what you can expect from the new version.

What’s Seq? Seq is the fastest way for .NET teams to improve visibility using structured application logs. If you’re using Serilog, you’ll find Seq is the perfect partner – trivially easy to set up, and built for structured log events so querying them is simple from “day 1” of a project. You already use CI and automated deployment: Seq is the next piece of the puzzle, closing the loop to show your team what’s happening in development, testing and production.

Charts

New in 1.4:

Counters-Downloads

Charts show the count of matching events in a time period. You can view the last day (by hour), last week (by day), last month (by day) or last year (by week). It’s a simple feature that I’m sure we’ll expand on, but already a great way to keep a “finger on the pulse”.

To add a chart, filter the “events” view to show just what you want to chart, then drop down the “refresh” button and select “Add to dash”. By default, queries are shown on the dash as counters; to toggle between the counter and chart display, there’s a little icon in the top left that appears on hover.

HowToChart

(If you’re already using counters with Seq 1.3, you’ll need to recreate them before historical data will be indexed by the 1.4 preview.)

Quick(er) switching between views

A much-requested improvement: when no view is selected, the first 10 views will be shown in a list (saving a few clicks when switching between views).

ViewSwitching

Extended text operators

A bunch of work has gone into better string comparison functions in this version. Seq queries now support:

  • Contains(text, pattern)
  • StartsWith(text, pattern) and EndsWith(text, pattern)
  • IndexOf(text, pattern), and
  • Length(text)

In each of these, “pattern” can be case-insensitive, case-sensitive, or a regular expression. There’s some more information and examples in the documentation.

Date functions and @Timestamp

Events in Seq now carry a queryable @Timestamp property, and we’ve added a DateTime() function that can be used with it. Check out the documentation for some examples here.

Keep the feedback coming!

Download the preview, try it out, and let us know what you think! You know where to find me :) – and there’s also a Seq discussion group we’d love you to join, and a Seq Twitter account if you’d like to keep in the loop.

Serilog gets a mention at NDC Oslo 2014

It was really cool to see this session from NDC about Elasticsearch/Logstash/Kibana featuring Serilog from just after 16:00.

Monitoring your app with Logstash and Elasticsearch from NDC Conferences on Vimeo.

It’s fun watching Serilog presented by people who obviously “get it”. And, compared with the alternatives shown earlier in the session, it’s great to see just how easy Serilog is for generating quality structured logs.

(If you’re not using Serilog and interested in the demo code, it’s on GitHub, but please check out this tiny PR if it hasn’t made it through yet.)

One year of Serilog

I just pushed the first build of Serilog 1.3 to NuGet. This one includes a more flexible JsonFormatter and support for width specifiers in format strings. Not huge changes, but both called for obsoleting some existing methods, and with 1.2 at the venerable age of “.53″ it was time to rev and get back to the nice simple version “1.3.1”.

It occurred to me that we’ve now hit the one year mark in the Serilog project. Version 0.1 was announced on March 29th, 2013 actually!

If there was any doubt back then of the viability of “yet another logger” in the .NET ecosystem, it’s been well and truly dispelled in my mind. Sixteen of us have now contributed to the project, and what’s most exciting to me is that the developers extending, using and discussing Serilog are some of the best and brightest I know. It’s the kind of momentum that makes me confident we’ll see more use and growth in the next year.

For those who follow it, the mention of structured logging as a technology to “trial” in the January 2014 ThoughtWorks Technology Radar is also a good indicator that Serilog is aligned with the facilities .NET developers will come to expect and use widely.

Our 1.0 came only six months ago, so these are still early days, but there are now 38 packages tagged “serilog” on NuGet, and despite the more-than-occasional glitches in the counter ;) the core Serilog package has over 7,400 downloads.

Visits to http://serilog.net are also looking healthy:

Serilog-One-Year

April’s been a visibly good month, with a lot of interest stirred up by a discussion on .NET Rocks! where I did my best to “get the word out” about how Serilog has totally transformed my thinking about logging.

If you’re using Serilog it would be great to hear more about how it has worked for you, and where you think the project should be aiming next – get in touch here or on our discussion list!

XML configuration for Serilog

Some issues hang on! Serilog’s #3 — XML configuration support — was still grimacing at us from the backlog alongside issues with three digits, until it was unceremoniously closed a few minutes ago.

If you’ve spent any time with Serilog you’ll have noticed the absence of built-in XML configuration support. Something about the project just wanted – and wants – to be a code-centric thing. I guess it is part of lifting the status of logging a bit – ORMs for example moved on from XML configuration to code-first/fluent configuration many moons ago. Instrumenting your app with an event stream is an important thing that deserves a modern configuration API.

XML’s not at all bad though. File paths change, servers have different addresses in different environments, logging levels go up and down. XML’s a great fit for this and we’ve known that all along with Serilog; we’ve just chosen to keep it in its place and write code like:

var logFile = ConfigurationManager.AppSettings["LogFilePath"];

var logger = new LoggerConfiguration()
  .WriteTo.File(logFile)
  .CreateLogger();

Over time this gets boring and repetitive I admit. Thats why we’ve now got the Serilog.Extras.AppSettings package.

XML configuration with a twist

As I said – we like XML configuration in its place, but that’s a pretty small place. When I open up App.config or Web.config in a modern .NET project the amount of gunk in there can be pretty confronting.

Most XML config implementations provide their own <configurationSection>s, and those need to be declared, and then the XML syntax used within them generally gets copied from a long list of arcana like the one in the log4net documentation that I’ve visited for years on end.

Serilog’s “app settings” support is different; we use a technique I first saw suggested for Autofac module configuration several years ago.

This revolves around the <appSettings> collection, an element that’s present in almost every .NET configuration file. Here’s an example to illustrate what we’re doing:

<appSettings>
  <add key="serilog:minimum-level" value="Verbose" />

Instead of our own custom <serilog> element, we cheat a bit and just look for <appSettings> elements that match a certain pattern (their names start with serilog:). Simple idea, but effective: I’m going to write this whole article without checking the documentation or sample code even once! Remembering a setting name or pattern is much easier for me than remembering a whole pile of custom XML syntax.

Going a bit further, here’s the configuation for two sinks, the console and a log file:

  <add key="serilog:write-to:ColoredConsole" />
  <add key="serilog:write-to:File.path" value="C:\Logs\myapp.txt" />

(Notice you don’t actually have to provide a value for an app setting? I didn’t until recently, but works nicely!)

Where do the magic names ColoredConsole and File.path come from? They’re just encodings of the following method calls:

  .WiteTo.ColoredConsole()
  .WriteTo.File(path: @"C:\Logs\myapp.txt")

The name of the path parameter obviously wouldn’t be specified in normal usage, but you can see how the XML and code-based configuration match up 1:1.

The extension methods are looked up dynamically using reflection, so you can configure your own sinks this way. You can include multiple parameters for the same sink using multiple keys – for both features check out the wiki page linked below.

Enabling the package

Two steps: 1. Install the Serilog.Extas.AppSettings package from NuGet; 2. add a call to ReadAppSettings() when you’re configuring your logger:

var logger = new LoggerConfiguration()
  .ReadAppSettings()
  ... // Other configuration here, then
  .CreateLogger()

You can mix and match app settings with code-driven config, but for each sink you need to use one or the other.

There’s much more detailed documentation here, on the wiki.

But isn’t this super-limited?

I’m happy to trade configuation power for something I can actually remember how to use. Someday we might have a more sophisiticated XML configuration option than this, but unless you can write the PR – I’m served pretty well by this one and not in a hurry.

But this is totally not my style!

That’s fine; it’s an ‘extra’, meaning we ship it separately from Serilog’s core and you’re free to install it if you like it and ignore it otherwise.

Still, I’ll be happy to hear if you do find it useful!

What’s to love about Seq 1.2?

The latest version of Seq is now ready to download from the site. It’s the best one yet, with a much smoother navigation experience and a bunch of small refinements that you’ll appreciate if you spend a lot of time with Seq each day.

Navigation

Digging into a lump of log data involves a lot of filtering – changing, adding and removing little search expressions to get a different cut of the events.

In Seq 1.0 we were at a bit of an impasse as to how this tied in with the browser’s history stack, so (I’m a bit ashamed to say!) we left it alone.

With the new version we’ve nailed this, I think.

First, filters typed into the filter box are included in browser history, so searching for “Alice”, then searching for “Bob”, then pressing back will again leave you at “Alice”. Simple win!

Bob

Furthermore, if you change views, this also is included in history. If you’re searching for an error message in Production and then switch to look for the same message in QA, pressing back will land you at Production again.
The final piece of the puzzle is the query, which remains as a saveable scratch area for filters that stay active regardless of browser navigation.

Viewport

In Seq 1.0 the initial result set returned for each query was 30 events regardless of your monitor size, and you had to click to see each additional page of 30 events.

No more in 1.2! The new version makes better use of the available space by filling the browser window with events.
Once you’ve requested more events with the “Older” button, Seq will start “infinitely scrolling” so events appear as you go further down the page.

The new version is ready and waiting at http://getseq.net.