diff --git a/.gitignore b/.gitignore index 940794e..a52a718 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ dlldata.c project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c diff --git a/appveyor.yml b/appveyor.yml index 9ca24f7..ceb50d8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2017 -configuration: Release +image: Visual Studio 2019 test: off build_script: - ps: ./Build.ps1 diff --git a/global.json b/global.json index 0a37afd..e3bbb00 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { "sdk": { - "version": "2.2.105" + "allowPrerelease": false, + "version": "3.1.100", + "rollForward": "latestFeature" } } diff --git a/samples/SimpleServiceSample/PrintTimeService.cs b/samples/SimpleServiceSample/PrintTimeService.cs index 513cace..f88e137 100644 --- a/samples/SimpleServiceSample/PrintTimeService.cs +++ b/samples/SimpleServiceSample/PrintTimeService.cs @@ -6,39 +6,22 @@ namespace SimpleServiceSample { - public class PrintTimeService : IHostedService, IDisposable + public class PrintTimeService : BackgroundService { private readonly ILogger _logger; - private Timer _timer; public PrintTimeService(ILogger logger) { _logger = logger; } - public Task StartAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - _timer = new Timer(DoWork, null, TimeSpan.Zero, - TimeSpan.FromSeconds(5)); - - return Task.CompletedTask; - } - - private void DoWork(object state) - { - _logger.LogInformation($"The current time is: {DateTimeOffset.UtcNow}"); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _timer?.Change(Timeout.Infinite, 0); - - return Task.CompletedTask; - } - - public void Dispose() - { - _timer?.Dispose(); + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("The current time is: {CurrentTime}", DateTimeOffset.UtcNow); + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } } } } diff --git a/samples/SimpleServiceSample/Program.cs b/samples/SimpleServiceSample/Program.cs index 4ff6e7e..8fce743 100644 --- a/samples/SimpleServiceSample/Program.cs +++ b/samples/SimpleServiceSample/Program.cs @@ -30,9 +30,7 @@ public static int Main(string[] args) try { Log.Information("Getting the motors running..."); - - BuildHost(args).Run(); - + CreateHostBuilder(args).Build().Run(); return 0; } catch (Exception ex) @@ -46,11 +44,9 @@ public static int Main(string[] args) } } - public static IHost BuildHost(string[] args) => - new HostBuilder() - .ConfigureHostConfiguration(BuildConfiguration) - .ConfigureServices(services => services.AddSingleton()) - .UseSerilog() - .Build(); + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices(services => services.AddHostedService()) + .UseSerilog(); } } diff --git a/samples/SimpleServiceSample/Properties/launchSettings.json b/samples/SimpleServiceSample/Properties/launchSettings.json new file mode 100644 index 0000000..af4a8bf --- /dev/null +++ b/samples/SimpleServiceSample/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "SimpleServiceSample": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/samples/SimpleServiceSample/SimpleServiceSample.csproj b/samples/SimpleServiceSample/SimpleServiceSample.csproj index 36bbcf2..9d323e4 100644 --- a/samples/SimpleServiceSample/SimpleServiceSample.csproj +++ b/samples/SimpleServiceSample/SimpleServiceSample.csproj @@ -1,22 +1,15 @@ - + + - netcoreapp2.1 - Exe + netcoreapp3.1 - - - - - + - - - - + diff --git a/serilog-extensions-hosting.sln.DotSettings b/serilog-extensions-hosting.sln.DotSettings index 87d9b7b..de0032b 100644 --- a/serilog-extensions-hosting.sln.DotSettings +++ b/serilog-extensions-hosting.sln.DotSettings @@ -2,4 +2,6 @@ True True True + True + True True \ No newline at end of file diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/NullEnricher.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/NullEnricher.cs new file mode 100644 index 0000000..af13dfc --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/NullEnricher.cs @@ -0,0 +1,28 @@ +// Copyright 2020 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Extensions.Hosting +{ + // Does nothing, but makes it easy to create an `ILogger` from a Serilog `Logger` + // that will not dispose the underlying pipeline when disposed itself. + class NullEnricher : ILogEventEnricher + { + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + } + } +} \ No newline at end of file diff --git a/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj b/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj index 1c84fd6..6b5b367 100644 --- a/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj +++ b/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj @@ -2,7 +2,7 @@ Serilog support for .NET Core logging in hosted services - 3.0.0 + 3.1.0 Microsoft;Serilog Contributors netstandard2.0 true diff --git a/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs b/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs index 306eadd..26091a2 100644 --- a/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs +++ b/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2020 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ // limitations under the License. using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.DependencyInjection; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; @@ -26,16 +26,30 @@ namespace Serilog /// public static class SerilogHostBuilderExtensions { + // Used internally to pass information through the container. We need to do this because if `logger` is the + // root logger, registering it as a singleton may lead to disposal along with the container by MEDI. This isn't + // always desirable, i.e. we may be handed a logger and `dispose: false`, so wrapping it keeps us in control + // of when the logger is disposed. + class RegisteredLogger + { + public RegisteredLogger(ILogger logger) + { + Logger = logger; + } + + public ILogger Logger { get; } + } + /// /// Sets Serilog as the logging provider. /// /// The host builder to configure. /// The Serilog logger; if not supplied, the static will be used. /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. + /// logger is not specified but is true, the method will be + /// called on the static class instead. /// A registered in the Serilog pipeline using the - /// WriteTo.Providers() configuration method, enabling other s to receive events. By + /// WriteTo.Providers() configuration method, enabling other s to receive events. By /// default, only Serilog sinks will receive events. /// The host builder. public static IHostBuilder UseSerilog( @@ -71,14 +85,15 @@ public static IHostBuilder UseSerilog( 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 host builder to configure. - /// The delegate for configuring the that will be used to construct a . - /// Indicates whether to preserve the value of . + /// 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. @@ -91,35 +106,80 @@ public static IHostBuilder UseSerilog( { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + return UseSerilog( + builder, + (hostBuilderContext, services, loggerConfiguration) => + configureLogger(hostBuilderContext, 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 host 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 host builder. + public static IHostBuilder UseSerilog( + this IHostBuilder 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)); builder.ConfigureServices((context, collection) => { - var loggerConfiguration = new LoggerConfiguration(); - LoggerProviderCollection loggerProviders = null; if (writeToProviders) { loggerProviders = new LoggerProviderCollection(); - loggerConfiguration.WriteTo.Providers(loggerProviders); } - - configureLogger(context, loggerConfiguration); - var logger = loggerConfiguration.CreateLogger(); - ILogger registeredLogger = null; - if (preserveStaticLogger) + collection.AddSingleton(services => { - 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 loggerConfiguration = new LoggerConfiguration(); + + if (loggerProviders != null) + loggerConfiguration.WriteTo.Providers(loggerProviders); + + configureLogger(context, services, loggerConfiguration); + var logger = loggerConfiguration.CreateLogger(); + + return new RegisteredLogger(logger); + }); + collection.AddSingleton(services => + { + // How can we register the logger, here, but not have MEDI dispose it? + // Using the `NullEnricher` hack to prevent disposal. + var logger = services.GetRequiredService().Logger; + return logger.ForContext(new NullEnricher()); + }); + collection.AddSingleton(services => { + var logger = services.GetRequiredService().Logger; + + 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, true, loggerProviders); if (writeToProviders) @@ -130,9 +190,11 @@ public static IHostBuilder UseSerilog( return factory; }); - - ConfigureServices(collection, logger); + + // Null is passed here because we've already (lazily) registered `ILogger` + ConfigureServices(collection, null); }); + return builder; }