-
Notifications
You must be signed in to change notification settings - Fork 209
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
Add an overload to UseSerilog() for inline initialization during program startup #1
Conversation
Thanks Fabian! The PR looks great. I need to give some thought to how the logger could be exposed - I don't think it's a huge deal, but it is a bit hard to offer the full Serilog experience if there's no way for the program to get access to a Serilog logger. Guessing there has to be a solution, but registering Also wondering whether we'd allow Will give it a little more thought... Thanks for kicking this off! |
You're right, that was totally an oversight on my side. 😞 I wonder, wouldn't adding the That way, it could be injected in the Startup class constructor or in any of the calls to Startup class methods, and be accessible when configuring an custom DI provider. Let's say I'm using Autofac together with https://github.com/nblumhardt/autofac-serilog-integration, I could simply do something like this: public class Startup
{
private IConfiguration Configuration { get; }
private Serilog.ILogger Logger { get; }
public Startup(IHostingEnvironment environment, IConfiguration configuration, Serilog.ILogger logger)
{
Configuration = configuration;
Logger = logger;
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterLogger(Logger);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
} As for the case where one wants to use Serilog without a custom DI provider, then maybe a Besides having some kind of // I'm assuming that SerilogLoggerFactory exposes a property named Logger of type Serilog.ILogger.
public static Serilog.ILogger GetSerilogLogger(this ILoggerFactory loggerFactory) => loggerFactory is SerilogLoggerFactory serilogLoggerFactory ? serilogLoggerFactory.Logger : null;
public static Serilog.ILogger GetSerilogLogger<T>(this ILoggerFactory loggerFactory) => loggerFactory is SerilogLoggerFactory serilogLoggerFactory ? serilogLoggerFactory.Logger.ForContext<T>() : null; The advantage is that it does not pollute the service collection with a raw |
Thanks for the follow-up and suggestions. I need some more time to think through this one. Registering a Serilog-based Registering the Serilog Trade-offs everywhere! :-) :-) |
I ended up adding something very similar to this pull request my application, except that I set the result of The problem with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some thoughts/comments.
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace SimpleWebSampleV2.Controllers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although it's useful having the sample as documentation, I'm wary of adding samples for all of the various configuration options (it'll become a bit unwieldy to maintain). Perhaps we remove this, but look to add a few explanatory lines in the README instead?
{ | ||
if (builder == null) throw new ArgumentNullException(nameof(builder)); | ||
if (configureSerilog == null) throw new ArgumentNullException(nameof(configureSerilog)); | ||
builder.ConfigureServices |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a little spaced-out to my eyes. Can we put the method name and params on the same line? E.g.:
builder.ConfigureServices( (context, collection) =>
{
var loggerConfiguration = new LoggerConfiguration();
configureSerilog(context, loggerConfiguration);
collection.AddSingleton<ILoggerFactory>(new SerilogLoggerFactory(loggerConfiguration.CreateLogger(), true));
});
/// <param name="builder">The web host builder to configure.</param> | ||
/// <param name="configureSerilog">The delegate for configuring the <see cref="LoggerConfiguration" /> that will be used to construct a <see cref="Logger" />.</param> | ||
/// <returns>The web host builder.</returns> | ||
public static IWebHostBuilder UseSerilog(this IWebHostBuilder builder, Action<WebHostBuilderContext, LoggerConfiguration> configureSerilog) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the action applies to a logger configuration, can we call the parameter configureLogger
instead of configureSerilog
?
@@ -41,5 +41,25 @@ public static IWebHostBuilder UseSerilog(this IWebHostBuilder builder, Serilog.I | |||
collection.AddSingleton<ILoggerFactory>(new SerilogLoggerFactory(logger, dispose))); | |||
return builder; | |||
} | |||
|
|||
/// <summary>Sets Serilog as the logging provider.</summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May need to include a note on how this differs from the overload above, e.g. Sets Serilog as the logging provider. A <see cref="WebHostBuilderContext"/> is supplied so that configuration and hosting information can be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should possibly add The logger will be shut down when application services are disposed.
{ | ||
var loggerConfiguration = new LoggerConfiguration(); | ||
configureSerilog(context, loggerConfiguration); | ||
collection.AddSingleton<ILoggerFactory>(new SerilogLoggerFactory(loggerConfiguration.CreateLogger(), true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does AddSingleton
guarantee disposal of the logger factory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the created ILogger
should to be saved to the static Log.Logger
here, because at some point in the Startup.Configure()
function, the CloseAndFlush()
function needs to be setup for calling at application shutdown:
var applicationLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
applicationLifetime.ApplicationStopping.Register(() => Log.CloseAndFlush());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we call those "sensible defaults" and just start out by wiring all of that up automatically here? The overall perspective would then be: if you supply the logger, you configure everything to your liking; if we supply it (via this new overload), "just trust us", everything should be set up reasonably. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the documentation, it seems AddSingleton won't dispose the logger factory if we create the object ourselves. 😟
I think that using the overload with the factory method should do the trick, however.
Regarding the call to CloseAndFlush()
method, should I try to wire it in the call to ConfigureServices()
, then ?
Hi again @GoldenCrystal - I've had some time to think about this and feel like we should move ahead. Is that your opinion also? To expose the logger, we've got a few options, but one that might work well is to expose a parameter If we do this, it's possible for packages like The only wrinkle this would introduce is that we'd need to switch from disposing the logger directly to calling What do you think? |
@GoldenCrystal Hi Fabian - just checking in, are you still keen on these changes? No great rush, but if you need help to get it over the line or want to pass on the baton just let me know :-) Cheers! |
This is a very essential change for real-world logging scenarios, thank you |
@nblumhardt Sorry, I was out of town for a while. I will take a look at this in a few hours :) |
@nblumhardt I'm wondering about the [Flags]
enum LoggerRegistrationOptions
{
/// <summary>Do not expose the <see cref="ILogger" /> directly.</summary>
None = 0,
/// <summary>Register the <see cref="ILogger" /> as the global <see cref="Log.Logger" /> instance.</summary>
RegisterStatic = 1,
/// <summary>Registers the <see cref="ILogger" /> within ASP.NET dependency injection.</summary>
RegisterSingleton = 2,
/// <summary>Registers a generic <see cref="ILogger{T}" /> as singleton within ASP.NET dependency injection.</summary>
RegisterGenericSingleton = 4,
/// <summary>Registers the <see cref="ILogger" /> as well as <see cref="ILogger{T}" /> as singletons within ASP.NET dependency injection.</summary>
RegisterSingletons = 6,
} This seems to me like it would offer the most choices to developpers. (Perhaps even a bit too much ?) |
Hi @GoldenCrystal, thanks for the reply; I had second thoughts about the The I am not sure how to register |
Had a quick look at the singleton disposal problem; all we should need to do is switch to using the If we want to do it via |
…ng the construction of IWebHost.
Thanks Fabian! I made one small tweak. Merging now :-) |
As discussed here:
serilog/serilog-settings-configuration#66
I propose to add an overload to the
UseSerilog
extension method:The goal of this new method is to be able to initialize Serilog during the call to
IWebHostBuilder.Build()
, after configuration and environment have been built. (see at the end for an example)For that, the creation of the
LoggerConfiguration
instance and the call toLoggerConfiguration.CreateLogger()
are handled by the method itself, similarly to what is done byIWebHostBuilder.ConfigureAppConfiguration()
.Of course, as it is always the case whith choices, this one has its advantages and its drawbacks.
Drawbacks:
IWebHostBuilder.Build()
method means that it is initialized a bit later compared to when theILogger
instance is set up earlier.ILoggerFactory
has been added to the service collection. More specifically, logging can happen inside the callIWebHost.Build()
but cannot happen before.ILogger
is not exposed outside of ASP.NET Core DI. That behavior can easily be modified if desired.Advantages:
WebHostBuilder
.IHostingEnvironment
is already available: Access toEnvironmentName
,ContentRootPath
or other properties of the hosting environment is possible.IConfiguration
instance is available: This overload ofUseSerilog()
integrates more nicely withConfigureAppConfiguration
, although one can still decide to use onlyUseConfiguration
if desired.At the end of the day, this does not solve the chicken/egg problem of what to initialize first: Configuration, Logging, or DI… It is just an alternative way of setting things up.
A typical usage would be as below: