Yes, I want a bucket of additional properties added to a specific entry - not write shared properties to all entries within the same context.
But this is what contexts/scopes are for. Just wrap the logging line into scope in using
. For example using the build-in ILogger.BeginScope
:
var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(new JsonFormatter())
.CreateLogger();
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddSerilog(log));
var sp = services.BuildServiceProvider();
var logger = sp.GetRequiredService<ILogger<Program>>();
var scopeInfoDict = new Dictionary<string, object>
{
{"foo", 1}
};
int seconds = 100; // do some work
scopeInfoDict.Add("bar", 42); // enrich
// ...
using (logger.BeginScope(scopeInfoDict)) // use the "context" for specific entry
{
logger.LogInformation("Completed in {Seconds}", seconds);
}
logger.LogInformation("No contextual properties");
Which results in:
{"Timestamp":"2023-07-31T13:05:39.0692467+03:00","Level":"Information","MessageTemplate":"Completed in {Seconds}","Properties":{"Seconds":100,"SourceContext":"Program","foo":1,"bar":42}}
{"Timestamp":"2023-07-31T13:05:39.0945963+03:00","Level":"Information","MessageTemplate":"No contextual properties","Properties":{"SourceContext":"Program"}}
Or using Serilog's LogContext
:
// ...
using (LogContext.PushProperty("foo", 1))
using (LogContext.PushProperty("bar", 42))
{
logger.Information("Completed in {Seconds}", seconds);
}
logger.Information("No contextual properties");
UPD:
As you have suggested in the comments you can follow the approach with resolving the DiagnosticContext
as the RequestLoggingMiddleware
does, but in background job there would be no middleware so you will need to implement something similar.
Personally I would go with custom service for that, to be a little bit more decoupled from Serilog/ASP.NET Core Serilog specific infrastructure:
services.AddScoped<Dictionary<string, object>>(); // sample code, better use some custom type, potentially over concurrent collection.
services.AddLogging(builder => builder.AddSerilog(log));
var sp = services.BuildServiceProvider();
// create scope in worker per "iteration"
using (var serviceScope = sp.CreateScope())
{
var ssp = serviceScope.ServiceProvider;
var logger = ssp.GetRequiredService<ILogger<Program>>();
ssp.GetRequiredService<Dictionary<string, object>>()
.Add("foo", 1);
int seconds = 100; // do some work
ssp.GetRequiredService<Dictionary<string, object>>().Add("bar", 42); // enrich
// log with the scope info
using (logger.BeginScope(ssp.GetRequiredService<Dictionary<string, object>>())) // use the "context" for specific entry
{
logger.LogInformation("Completed in {Seconds}", seconds);
}
logger.LogInformation("No contextual properties");
}