-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support dependency injection into sinks, enrichers, filters, etc. #141
Support dependency injection into sinks, enrichers, filters, etc. #141
Comments
I'd much prefer to add something like Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.AddAspNetCoreDIEnricher()
.CreateLogger() then services.AddTransient<IEnricher, RequestEnricher>();
services.AddSingleton<IEnricher, EnvironmentEnricher>();
services.AddSingleton<ISink, ConsoleSink>(); I've no idea how to achieve it though. The thought pattern is that it's a model that a lot AspNetCore programmers will understand. It would make the cognitive load a lot less. |
@martinjt thanks for the note. I think supporting sinks and enrichers directly from DI is a worthwhile consideration, although it won't work for most existing Serilog sinks because they're not exposed as objects directly (so the above would be additive, in some way, I guess). It's tricky to achieve a cleaner syntax without some object linking the first and second parts of the configuration process ( |
Another note, it would be good to explore the intersection of this and |
Mmm I have some concerns about the API being a bit mind-twisting, but it's sort of an advanced scenario, so maybe that's just fine. Another approach that may or may not work ... var resolveContext = new ResolveContext();
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.ResolveLater(resolveContext, (cfg, ctx) =>
cfg
.Enrich.WithProperty("ResolvedLater", ctx.Resolve<string>("TheValueForResolveLater"))
.WriteTo.Sink(ctx.Resolve<MyCustomSink>())
)
.CreateLogger();
// and then later on ...
resolveContext.Set<MyCustomSink>(new MySinkCOnfiguredLater());
resolveContext.Set("TheValueForResolveLater", "The value to show in the logs");
// notify people that relied on this `resolveContext` that the values are now ready
resolveContext.Update(); ( oh my, I haven't written any C# in way too long, and my brain writes TypeScript U_U ... ) so somehow promoting some sort of This also means we can use multiple I guess it could then be built upon the existing DI features from AspNetCore or something else ? maybe that does not make much sense, so this is just a brainstorming exercise :) |
@tsimbalar I like that API 👍 - will give that option some more thought. Rather than build it directly on the container APIs, passing a |
Support for dependencies in enrichers, etc would be really appreciated. Can I ask if this is still considered feature in near future? |
@alesdvorakcz thanks for the nudge. Yes, it's still being considered - just a matter of someone finding the time to dig in deeply/write some code. Cheers! |
Over the last days I have been struggling to figure out the current most correct way of configuring the Application Insights sink in an ASP.NET Core 3.1 application, preferrably without deprecation warnings. Reading serilog-contrib/serilog-sinks-applicationinsights#121 and related brought me to this issue. I am all for a DI-friendly API design. However, I would be very reluctant to develop serilog's API design to contain any explicit notion of DI, and MS.DI in particular. Several third party DI containers are non-conforming to the MS.DI interfaces, such as https://github.com/simpleinjector/SimpleInjector, whose main author @dotnetjunkie is actively involved in many discussion around this topic. With that in mind, I am not a fan of the proposed The main problem is quite well summarized by @cmeeren:
Given Microsoft's design philosophy, where the composition root of the application is on the level of the IMHO a good approach would be to do all fail-safe/isolated logger setup directly after application startup (e.g. configuring a console sink for logging to stdout), and then being able to either add configuration to that same logger, or to derive from it (and update the static instance) in the composition root. A clean API for this might be to add an overload of It would be great if we can take this discussion forward. (@nblumhardt pardon the indirect nudge 😉) |
@ralphhendriks thanks for the nudge :-) ... Does the |
@nblumhardt Sorry for the delay, it took me a few days to think about this and also discuss some aspects with a colleague (:wave: @Red-F). I agree that the idea of the I would think that MS is facing these issues as well for the built-in logging capabilities in ASP.NET Core. I remember reading a comment in a doc that after the change to the |
Found that serilog/serilog-extensions-hosting#14 is also related. It would definitely be interesting to hear @davidfowl's opinion here. |
I'd like to share my two cents here. Currently, most if not all sinks are created via the recommended extension method approach. I believe this does not scale properly and is hard to adapt to DI. Instead, I propose we decouple the sink creation from the sink configuration. Have those extension methods just define the sink type and options (maybe use the idiomatic Then, once this is in place, we can have a simple activator-based factory and a DI based one that are installed automatically depending on which Serilog packages are referenced. Sinks that currently don't depend on DI will work fine with a simple activator approach, while more modern ones that rely on registered services can be built using I think this decoupling would lead into a clean syntax (basically keeping what exists today for configuring the sinks) without the need for decorator/wrapper "dependency-aware sinks" or those hard to read lambdas by default. |
Hi, i thought to this a bit and I finally came to the fact that the Serilog package itself should expose a way to register an abstraction which responsibility is to provide dependencies, let's say Finally it would lead to this kind of code in the Program.cs of an asp.net core app:
Please see this commit https://github.com/g7ed6e/serilog/commit/7fb606c62e9afc37d000c0f5917905f383f298de |
That line will create a different DI container than the one the application uses. You'll end up with 2 sets of singletons and nobody is disposing the container here either so your disposable objects don't get disposed. |
@g7ed6e The abstraction I am specifically worried about your remark
This would lead to a range of integration packages being added to the already quite substantial amount of Serilog(-related) NuGet-packages available. On top of the arguments put forward by @ploeh, this would overwhelm developers with too many possibilities and may even cause quite some confusion. (Think for example about a developer employing one of the conformist third party DI containers. Has they to cross-wire the logging configuration using your proposed I quite like @julealgon's observation that sink (enrichter, etc.) creation and configuration are two distinct things. However, going this route would probably mean a major and breaking API change to the core assembly, thus impacting all users of Serilog, not just those integrating with ASP.NET Core. I wonder what @nblumhardt thinks of this. I would consider @julealgon's suggestion in #169 as a first, and far more low-key step in the right direction for improving the integration with ASP.NET Core BTW. |
In case someone starts to read the blog post that @ralphhendriks linked to (Conforming Container), but doesn't make it all the way to the end of it, it can seem like it's all criticism and no constructive advice. I should have found a better way to structure that and subsequent articles. The constructive advice does exist, however. For a a library like Serilog, I'd surmise that DI-Friendly Library contains most of the relevant advice I could think of back then. I'm happy to assist with particulars, if I can. |
I've ended up with a proof-of-concept based on https://github.com/serilog/serilog-reload that adds an public static class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateReloadableLogger(); // <-- ⭐
Log.Information("Starting up");
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Unhandled exception");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, services, cfg) => cfg
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.WriteTo.Sink(new SinkWithDependencies(new Lazy<IHttpClientFactory>(
() => services.GetRequiredService<IHttpClientFactory>()))) // <-- ⭐
.WriteTo.Console())
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
} Hangs together without too much magic, and would support It might seem strange to bake in special handling for A couple more notes - I think we should Example code is in: https://github.com/nblumhardt/serilog-reload/tree/dev/example/WebExample - opinions, ideas, and PRs welcome. |
Assuming, we'd like to initialize Serilog in the first few lines of
Program.Main()
, and independently of ASP.NET Core/other hosting code, how can we support sinks and other pipeline elements that depend on the framework and components that are initialized later?For example, in #84 there's a discussion regarding the use of Application Insights configuration from dependency injection in a Serilog sink.
LateInitSink
in this gist demonstrates a possible approach:@khellang suggested using a name like
DependencyInjectedSink
instead ofLateInitSink
, so make the API more searchable/discoverable.We could, finalize/ship an implementation of this approach.
The need to also support enrichers, filters, and so-on, could be met by including a range of similar types for those cases.
As an alternative, or extension of this idea, we could create a wrapper API that mimics
LoggerConfiguration
and sets up the full range of extension points for DI (rough sketch):This API should be possible using some extension points already in 2.9.0; it would be nicer to support
deferred.WriteTo.File()
etc. but we'd need new APIs to enable it.Thoughts?
See also #130.
The text was updated successfully, but these errors were encountered: