diff --git a/src/Serilog.Extensions.Hosting/SerilogLoggingBuilderExtensions.cs b/src/Serilog.Extensions.Hosting/SerilogLoggingBuilderExtensions.cs index cf8bf04..4efc041 100644 --- a/src/Serilog.Extensions.Hosting/SerilogLoggingBuilderExtensions.cs +++ b/src/Serilog.Extensions.Hosting/SerilogLoggingBuilderExtensions.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; using System; using static Serilog.SerilogHostBuilderExtensions; @@ -21,7 +24,7 @@ public static class SerilogLoggingBuilderExtensions /// A registered in the Serilog pipeline using the /// WriteTo.Providers() configuration method, enabling other s to receive events. By /// default, only Serilog sinks will receive events. - /// The host builder. + /// The logging builder. public static ILoggingBuilder AddSerilog( this ILoggingBuilder builder, Serilog.ILogger logger = null, @@ -61,5 +64,180 @@ public static ILoggingBuilder AddSerilog( return builder; } + + /// Sets Serilog as the logging provider. + /// + /// A is supplied so that configuration and hosting information can be used. + /// The logger will be shut down when application services are disposed. + /// + /// The logging builder to configure. + /// The delegate for configuring the that will be used to construct a . + /// Indicates whether to preserve the value of . + /// By default, Serilog does not write events to s registered through + /// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify + /// true to write events to all providers. + /// If the static is a bootstrap logger (see + /// LoggerConfigurationExtensions.CreateBootstrapLogger()), and is + /// not specified, the the bootstrap logger will be reconfigured through the supplied delegate, rather than being + /// replaced entirely or ignored. + /// The logging builder. + public static ILoggingBuilder AddSerilogFactory( + this ILoggingBuilder builder, + Action configureLogger, + bool preserveStaticLogger = false, + bool writeToProviders = false) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + + // This check is eager; replacing the bootstrap logger after calling this method is not supported. +#if !NO_RELOADABLE_LOGGER + var reloadable = Log.Logger as ReloadableLogger; + var useReload = reloadable != null && !preserveStaticLogger; +#else + const bool useReload = false; +#endif + var services = builder.Services; + + LoggerProviderCollection loggerProviders = null; + if (writeToProviders) + { + loggerProviders = new LoggerProviderCollection(); + } + + services.AddSingleton(sp => + { + Serilog.ILogger logger; +#if !NO_RELOADABLE_LOGGER + if (useReload) + { + reloadable!.Reload(cfg => + { + if (loggerProviders != null) + cfg.WriteTo.Providers(loggerProviders); + + configureLogger(sp, cfg); + return cfg; + }); + + logger = reloadable.Freeze(); + } + else +#endif + { + var loggerConfiguration = new LoggerConfiguration(); + + if (loggerProviders != null) + loggerConfiguration.WriteTo.Providers(loggerProviders); + + configureLogger(sp, loggerConfiguration); + logger = loggerConfiguration.CreateLogger(); + } + + return new RegisteredLogger(logger); + }); + + services.AddSingleton(sp => + { + // How can we register the logger, here, but not have MEDI dispose it? + // Using the `NullEnricher` hack to prevent disposal. + var logger = sp.GetRequiredService().Logger; + return logger.ForContext(new NullEnricher()); + }); + + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService().Logger; + + Serilog.ILogger registeredLogger = null; + if (preserveStaticLogger) + { + registeredLogger = logger; + } + else + { + // Passing a `null` logger to `SerilogLoggerFactory` results in disposal via + // `Log.CloseAndFlush()`, which additionally replaces the static logger with a no-op. + Log.Logger = logger; + } + + var factory = new SerilogLoggerFactory(registeredLogger, !useReload, loggerProviders); + + if (writeToProviders) + { + foreach (var provider in sp.GetServices()) + factory.AddProvider(provider); + } + + return factory; + }); + + ConfigureDiagnosticContext(services, preserveStaticLogger); + + return builder; + } + + /// Sets Serilog as the logging provider. + /// + /// A is supplied so that configuration and hosting information can be used. + /// The logger will be shut down when application services are disposed. + /// + /// The logging builder to configure. + /// The delegate for configuring the that will be used to construct a . + /// Indicates whether to preserve the value of . + /// By default, Serilog does not write events to s registered through + /// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify + /// true to write events to all providers. + /// The logging builder. + public static ILoggingBuilder AddSerilog( + this ILoggingBuilder builder, + Action configureLogger, + bool preserveStaticLogger = false, + bool writeToProviders = false) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + + return AddSerilogFactory( + builder, + (sp, loggerConfiguration) => + { + var configuration = sp.GetRequiredService(); + configureLogger(configuration, loggerConfiguration); + }, + preserveStaticLogger: preserveStaticLogger, + writeToProviders: writeToProviders); + } + + /// Sets Serilog as the logging provider. + /// + /// A is supplied so that configuration and hosting information can be used. + /// The logger will be shut down when application services are disposed. + /// + /// The logging builder to configure. + /// The delegate for configuring the that will be used to construct a . + /// Indicates whether to preserve the value of . + /// By default, Serilog does not write events to s registered through + /// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify + /// true to write events to all providers. + /// The logging builder. + public static ILoggingBuilder AddSerilog( + this ILoggingBuilder builder, + Action configureLogger, + bool preserveStaticLogger = false, + bool writeToProviders = false) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + + return AddSerilogFactory( + builder, + (sp, loggerConfiguration) => + { + configureLogger(loggerConfiguration); + }, + preserveStaticLogger: preserveStaticLogger, + writeToProviders: writeToProviders); + } } } \ No newline at end of file diff --git a/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj b/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj index daf9720..77dba55 100644 --- a/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj +++ b/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj @@ -14,6 +14,18 @@ $(DefineConstants);NO_RELOADABLE_LOGGER + + + + + + + + PreserveNewest + true + PreserveNewest + + @@ -29,6 +41,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Serilog.Extensions.Hosting.Tests/SerilogLoggingBuilderExtensionsTests.cs b/test/Serilog.Extensions.Hosting.Tests/SerilogLoggingBuilderExtensionsTests.cs index 9f7bbb6..1f71a95 100644 --- a/test/Serilog.Extensions.Hosting.Tests/SerilogLoggingBuilderExtensionsTests.cs +++ b/test/Serilog.Extensions.Hosting.Tests/SerilogLoggingBuilderExtensionsTests.cs @@ -10,20 +10,35 @@ namespace Serilog.Extensions.Hosting.Tests; public class SerilogLoggingBuilderExtensionsTests { [Fact] - public async Task SerilogLoggingBuilderExtensions_AddSerilog_SuccessAsync() + public async Task LoggingBuilderExtensions_AddSerilog_SuccessAsync() { // Arrange var builder = WebApplication.CreateBuilder(); - var logger = new LoggerConfiguration() - .WriteTo.InMemory() - .CreateLogger(); - builder.Logging.AddSerilog(logger); + builder.Logging.AddSerilog(logger => logger.WriteTo.InMemory()); builder.WebHost.UseTestServer(); var app = builder.Build(); // Act - var message = "Hello World!"; - app.Logger.LogInformation("Hello World!"); + var message = "Logging in memory"; + app.Logger.LogInformation(message); + await app.StartAsync(); + + // Assert + InMemorySink.Instance.Should().HaveMessage(message); + } + + [Fact] + public async Task LoggingBuilderExtensions_AddSerilogWithAppsettings_SuccessAsync() + { + // Arrange + var builder = WebApplication.CreateBuilder(); + builder.Logging.AddSerilog((appConfig, loggerConfig) => loggerConfig.ReadFrom.Configuration(appConfig)); + builder.WebHost.UseTestServer(); + var app = builder.Build(); + + // Act + var message = "Logging in memory with appsettings.json"; + app.Logger.LogInformation(message); await app.StartAsync(); // Assert diff --git a/test/Serilog.Extensions.Hosting.Tests/appsettings.json b/test/Serilog.Extensions.Hosting.Tests/appsettings.json new file mode 100644 index 0000000..ba074bc --- /dev/null +++ b/test/Serilog.Extensions.Hosting.Tests/appsettings.json @@ -0,0 +1,16 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "WriteTo": [ + { + "Name": "InMemory" + } + ] + } +} \ No newline at end of file