Serilog is a great tool to write structured log messages. To fully profit from the structured part you need to write your log messages in a certain way. Otherwise you lose most of the great features and could use any other traditional logging framework. Luckily there are only a few simple things you should keep in mind and I will show you today what those are.
This post is part of the Improve Your Log Messages series. You can find the other parts here:
- Part 1: The Missed Opportunities of Log Files
- Part 2: Structured Logging with Serilog
- Part 3: RavenDB as a Sink for Serilog
- Part 4: Seq as a Sink for Serilog
- Part 5: How to Influence the Output of Serilog
- Part 6: Monitor your Application with Seq
- Part 7: Debugging Serilog
- Part 8: Elasticsearch as a Sink for Serilog
- Part 9: Monitor your Applications with Kibana
- Part 10: Closing the Feedback Loop from Log Messages to Knowledge
- Part 11: How To Analyse IIS Log Files
- Part 12: Using Logstash to Analyse IIS Log Files with Kibana
- Part 13: Analysing Apache Log Files with Logstash and Kibana
- Part 14: How to Analyse SharePoint Log Files
More than String.Format()
Serilog can write more to a sink than just a plain string. Therefore you never should use a +
to stitch the parts of your log message together. You better use placeholders as you would do in a call to String.Format():
1 |
Log.Information("This is order {orderId} for customer {customerId}", order.Id, customer.Id); |
This code will result in a message like this one:
As you can see, the placeholders for the concatenation are used to identify the parameters. This context helps you to identify the number 123 as an orderId
and makes it distinguishable from a possible personId
with the same value.
Should you however build your string with placeholders like {0}
and {1}
as you do it normally with String.Format(), you end up with a message like this one:
While your message is the same, you lost all of the context when you use numbers as placeholders. The time spend to give those placeholders a good name is saved multiple times when you need to search your log messages.
Influence the Serialization
If you need to influence how a DateTime-object or a number is serialised, you can use the formatting you know from String.Format(). This code will result in 3 different representations of the same DateTime-object:
1 2 3 |
var date = new DateTime(2014, 9, 1, 20, 30, 10); Log.Information("This is {original}, just the year {year:yyyy} or full {full:F}", date, date, date); |
The log message generated from this code looks like this:
You still have the full DateTime object assigned to the parameters, but your message is in the required format. This seems a bit odd at first, but when you work with dates you soon will be thankful of this behaviour.
Objects as Parameters
Serilog will call the ToString() method when an object is used as a parameter. If you haven’t overwritten this method with a custom one it will return the fully qualified name:
1 |
Log.Information("This is order {orderId} for customer {customer}", order.Id, customer); |
The simplest way to get a better log entry is to overwrite the ToString() method:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Customer { public string Id { get; set; } public string Name { get; set; } public string Country { get; set; } public string EMail { get; set; } public override string ToString() { return String.Format("{0} - {1}", Id, Name); } } |
This little change will change your log message in something far more useful:
You can get more ideas on how to format your messages in the documentation of Serilog.
@ and $
With the modifiers @
and $
in front of the parameter for the log messages we can modify the output even further. The @
operator will serialize the whole object and you immediately see the benefit it brings for your log messages.
The $ in front of the parameter will force the ToString() method for serialization. Since this is often the default behaviour it’s not so clear why this is helpful. Some types like enumerations however format their entries as an array. Using $
and @
on a type like enumerable will show you the differences nicely:
1 2 3 4 5 6 7 8 |
List<Customer> customers = new List<Customer>(); customers.Add(new Customer() { Id = "LM", Name = "Lanng Maxine" }); customers.Add(new Customer() { Id = "HI", Name = "Humphrey Ima" }); var enumeration = customers.AsEnumerable<Customer>(); Log.Information("Customers: {Default} or {$ToString} or {@Object}", enumeration, enumeration, enumeration); |
Next
With this additional information we are now able to modify the log messages created with Serilog. And meaningful messages are what we need to look deeper into the powerful features of Seq. Next week I will show you how the dashboard and views can help you to get the most out of Seq.