I have a class BackgroundTask
that I have set up to log events when things happen, such as when the task completes. For example, for the Success
case, I call Log.Verbose("{Task} completed successfully", this);
My default ToString()
includes the progress, but for a completed task we know it's 100% so I would like to ignore it. I know that with numeric types you can pass in a custom format specifier string (like "{IntProperty:p5}"
, which would render my int as a percentage with 5 decimal places), and I would like to be able to do the same, such as Log.Information("{Task:Name}", this)
, which would pass in "Name"
into my ToString()
method.
I've tried adding lots of different methods like adding ToString()
's, (taking in nothing, a string, a string and IFormatProvider
), implementing IFormattable
, forcing stringification etc and nothing seems to work. I can see that Serilog is correctly parsing my format string: (PropertyBinder.cs
, ConstructNamedProperties()
line 111)
This calls ConstructProperty()
which just ignores the Format
property of the token, which explains why it's being ignored, but I was wondering if there was a way that would work that I haven't thought of.
PS Yes I'm aware I have several options I could do, but I'd rather not do these:
Log.Information("{Task}", new {Name = this.Name, Id = this.Id});
ToString()
with my own format string - Same as (1), this destroys the original, and means it won't be stored with all it's information. E.g. Log.Information("{Task}", this.ToString("Custom Format"));
ToStringFormat
before passing it into Serilog - This seems bad practice and just adds extra clutter, not to mention the concurrency issues. E.g. this.Format = "Custom FOrmat"; Log.Information("{Task}", this);
This is due to the split between capturing and formatting in the Serilog pipeline.
In order for format strings like :X
to be processed when rendering to a sink, the original object implementing IFormattable
needs to be available.
But, because sinks often process events asynchronously, Serilog can't be sure that any given logged object is thread-safe, and so any unknown types are captured at the time/site of logging using ToString()
.
To get around this, you need to tell Serilog that your Point
class is an (essentially immutable) value type with:
.Destructure.AsScalar(typeof(Point))
when the logger is configured. You can then implement IFormattable
on Point
and use {Point:X}
etc. in templates.