Using Serilog Logging for LaunchDarkly
Overview
Background
I was recently trying to get LaunchDarkly to write its logs using our standard
logging configuration - which is Serilog on
top of Microsoft.Extensions.Logging
. According to the LaunchDarkly docs, this was pretty straight-forward but it turned out to be trickier than I thought.
The solution involved getting services from an IServiceCollection
before the standard IServiceProvider
is built with the builder.Build()
call in ASP.NET applications.
tl;dr: Just wanna see the working code? It's here: https://github.com/dahlsailrunner/launch-darkly-logging
The Documentation
The issue was in the code being suggested:
1using LaunchDarkly.Logging;
2using LaunchDarkly.Sdk.Server;
3
4var config = Configuration.Builder("sdk-key-123abc")
5 .Logging(Logs.CoreLogging)
6 .Build();
All of the above looks simple enough - but what is Logs.CoreLogging
? There are two options for this argument:
ILogAdapter
: Defined by LaunchDarkly as a hook into things like Microsoft.Extensions.LoggingIComponentConfigurer<LoggingConfiguration>
: Also defined by LaunchDarkly for lower-level control
The ILogAdapter
was the route to pursue - but how to get one of those?
There's another NuGet package (LaunchDarkly.Logging.Microsoft
) which defined a static method that can
return an ILogAdapter
if you provide an instance of the standard ILoggerFactory
interface.
We're getting close now!!
With the config
object from the above snippet of code, we need to register a singleton instance for
LaunchDarkly into the IServiceCollection
as we're starting the application:
1builder.Services.AddSingleton<ILdClient>(_ => new LdClient(ldConfig));
The Problem
During application startup, you have code something like this:
1var builder = WebApplication.CreateBuilder(args);
2// register services with builder.Services.AddXXXXX() calls
3
4builder.Services.AddSingleton<ILdClient>(_ => new LdClient(ldConfig));
5
6var app = builder.Build(); // builds IServiceProvider from IServiceCollection (builder.Services)
We need to be able to use an ILoggerFactory
instance to create the ldConfig
variable above - but we
won't be building the IServiceProvider
until later.
The Solution
We can use an intermediate building of an IServiceProvider
to do what we need. A simplified version of the
code above looks like this:
1var builder = WebApplication.CreateBuilder(args);
2// register services with builder.Services.AddXXXXX() calls
3
4var sp = builder.Services.BuildServiceProvider();
5var loggerFactory = sp.GetService<ILoggerFactory>();
6// .. use loggerFactory when building ldConfig variable
7builder.Services.AddSingleton<ILdClient>(_ => new LdClient(ldConfig));
8
9var app = builder.Build(); // builds IServiceProvider from IServiceCollection (builder.Services)
The above code will work fine -- GREAT! -- but there's also a caution here. Calling BuildServiceProvider
will create instances of singletons that you've registered - and then the app = builder.Build()
line
will do that again. So definitely avoid registering singleton services before that added call.
The Result
With the above code in place, I started getting great logs from the standard logging pipeline I had set up with Serilog.
Here's a sample entry shown in Seq:
And the configuration of filtering by level was just as simple as it should be:
1{
2 "Serilog": {
3 "MinimumLevel": {
4 "Default": "Debug",
5 "Override": {
6 "Microsoft.AspNetCore": "Warning",
7 "System": "Warning",
8 "LaunchDarkly": "Debug"
9 }
10 }
11 }
12}
The Github repo listed above has a fully working minimal API that shows all of this in action. It's here: https://github.com/dahlsailrunner/launch-darkly-logging
Happy coding!