diff --git a/.gitignore b/.gitignore index a52a718..0173fad 100644 --- a/.gitignore +++ b/.gitignore @@ -285,3 +285,4 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs +/samples/WebApplicationSample/logs/ diff --git a/samples/WebApplicationSample/Program.cs b/samples/WebApplicationSample/Program.cs new file mode 100644 index 0000000..3fcae13 --- /dev/null +++ b/samples/WebApplicationSample/Program.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace WebApplicationSample +{ + public static class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); + + Log.Information("Starting up!"); + + try + { + CreateHostBuilder(args).Build().Run(); + + Log.Information("Stopped cleanly"); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "An unhandled exception occured during bootstrapping"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog((context, services, configuration) => configuration + .WriteTo.Console() + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services)) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} \ No newline at end of file diff --git a/samples/WebApplicationSample/Properties/launchSettings.json b/samples/WebApplicationSample/Properties/launchSettings.json new file mode 100644 index 0000000..932948b --- /dev/null +++ b/samples/WebApplicationSample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15670", + "sslPort": 44322 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApplicationSample": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/WebApplicationSample/Startup.cs b/samples/WebApplicationSample/Startup.cs new file mode 100644 index 0000000..4fe346d --- /dev/null +++ b/samples/WebApplicationSample/Startup.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace WebApplicationSample +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGet("/", async context => + { + Log.Information("Saying hello"); + await context.Response.WriteAsync("Hello World!"); + }); + }); + } + } +} \ No newline at end of file diff --git a/samples/WebApplicationSample/WebApplicationSample.csproj b/samples/WebApplicationSample/WebApplicationSample.csproj new file mode 100644 index 0000000..9f85c41 --- /dev/null +++ b/samples/WebApplicationSample/WebApplicationSample.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/samples/WebApplicationSample/appsettings.Development.json b/samples/WebApplicationSample/appsettings.Development.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/samples/WebApplicationSample/appsettings.Development.json @@ -0,0 +1,2 @@ +{ +} diff --git a/samples/WebApplicationSample/appsettings.json b/samples/WebApplicationSample/appsettings.json new file mode 100644 index 0000000..633356d --- /dev/null +++ b/samples/WebApplicationSample/appsettings.json @@ -0,0 +1,18 @@ +{ + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "WriteTo": [ + { + "Name": "File", + "Args": { "path": "./logs/log-.txt", "rollingInterval": "Day" } + } + ] + }, + "AllowedHosts": "*" +} diff --git a/serilog-extensions-hosting.sln b/serilog-extensions-hosting.sln index e5c0bf6..2208752 100644 --- a/serilog-extensions-hosting.sln +++ b/serilog-extensions-hosting.sln @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Hosting. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleServiceSample", "samples\SimpleServiceSample\SimpleServiceSample.csproj", "{E5A82756-4619-4E6B-8B26-6D83E00E99F0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplicationSample", "samples\WebApplicationSample\WebApplicationSample.csproj", "{1ACDCA67-F404-45AB-9348-98E55E03CB8C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {E5A82756-4619-4E6B-8B26-6D83E00E99F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5A82756-4619-4E6B-8B26-6D83E00E99F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5A82756-4619-4E6B-8B26-6D83E00E99F0}.Release|Any CPU.Build.0 = Release|Any CPU + {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +56,7 @@ Global {0549D23F-986B-4FB2-BACE-16FD7A7BC9EF} = {A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0} {AD51759B-CD58-473F-9620-0B0E56A123A1} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} {E5A82756-4619-4E6B-8B26-6D83E00E99F0} = {F2407211-6043-439C-8E06-3641634332E7} + {1ACDCA67-F404-45AB-9348-98E55E03CB8C} = {F2407211-6043-439C-8E06-3641634332E7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10} diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/CachingReloadableLogger.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/CachingReloadableLogger.cs new file mode 100644 index 0000000..9a47560 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/CachingReloadableLogger.cs @@ -0,0 +1,519 @@ +// 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 System; +using System.Collections.Generic; +using System.Threading; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Extensions.Hosting +{ + class CachingReloadableLogger : ILogger, IReloadableLogger + { + readonly ReloadableLogger _reloadableLogger; + readonly Func _configure; + readonly IReloadableLogger _parent; + + ILogger _root, _cached; + bool _frozen; + + public CachingReloadableLogger(ReloadableLogger reloadableLogger, ILogger root, IReloadableLogger parent, Func configure) + { + _reloadableLogger = reloadableLogger; + _parent = parent; + _configure = configure; + _root = root; + _cached = null; + _frozen = false; + } + + public ILogger ReloadLogger() + { + return _configure(_parent.ReloadLogger()); + } + + public ILogger ForContext(ILogEventEnricher enricher) + { + if (enricher == null) return this; + + if (_frozen) + return _cached.ForContext(enricher); + + if (_reloadableLogger.CreateChild( + _root, + this, + _cached, + p => p.ForContext(enricher), + out var child, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return child; + } + + public ILogger ForContext(IEnumerable enrichers) + { + if (enrichers == null) return this; + + if (_frozen) + return _cached.ForContext(enrichers); + + + if (_reloadableLogger.CreateChild( + _root, + this, + _cached, + p => p.ForContext(enrichers), + out var child, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return child; + } + + public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + { + if (propertyName == null) return this; + + if (_frozen) + return _cached.ForContext(propertyName, value, destructureObjects); + + // There's a trade-off, here. Changes to destructuring configuration won't be picked up, but, + // it's better to not extend the lifetime of `value` or pass it between threads unexpectedly. + var eager = ReloadLogger(); + if (!eager.BindProperty(propertyName, value, destructureObjects, out var property)) + return this; + + var enricher = new FixedPropertyEnricher(property); + + if (_reloadableLogger.CreateChild( + _root, + this, + _cached, + p => p.ForContext(enricher), + out var child, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return child; + } + + public ILogger ForContext() + { + if (_frozen) + return _cached.ForContext(); + + + if (_reloadableLogger.CreateChild( + _root, + this, + _cached, + p => p.ForContext(), + out var child, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return child; + } + + public ILogger ForContext(Type source) + { + if (_frozen) + return _cached.ForContext(source); + + if (_reloadableLogger.CreateChild( + _root, + this, + _cached, + p => p.ForContext(source), + out var child, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return child; + } + + void Update(ILogger newRoot, ILogger newCached, bool frozen) + { + _root = newRoot; + _cached = newCached; + _frozen = frozen; + + // https://github.com/dotnet/runtime/issues/20500#issuecomment-284774431 + // Publish `_cached` and `_frozen`. This is useful here because it means that once the logger is frozen - which + // we always expect - reads don't require any synchronization/interlocked instructions. + Interlocked.MemoryBarrierProcessWide(); + } + + public void Write(LogEvent logEvent) + { + if (_frozen) + { + _cached.Write(logEvent); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + logEvent, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, string messageTemplate) + { + if (_frozen) + { + _cached.Write(level, messageTemplate); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + messageTemplate, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + if (_frozen) + { + _cached.Write(level, messageTemplate, propertyValue); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + messageTemplate, + propertyValue, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (_frozen) + { + _cached.Write(level, messageTemplate, propertyValue0, propertyValue1); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + messageTemplate, + propertyValue0, + propertyValue1, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + if (_frozen) + { + _cached.Write(level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + messageTemplate, + propertyValue0, + propertyValue1, + propertyValue2, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + { + if (_frozen) + { + _cached.Write(level, messageTemplate, propertyValues); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + messageTemplate, + propertyValues, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate) + { + if (_frozen) + { + _cached.Write(level, exception, messageTemplate); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + exception, + messageTemplate, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + { + if (_frozen) + { + _cached.Write(level, exception, messageTemplate, propertyValue); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + exception, + messageTemplate, + propertyValue, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1) + { + if (_frozen) + { + _cached.Write(level, exception, messageTemplate, propertyValue0, propertyValue1); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + exception, + messageTemplate, + propertyValue0, + propertyValue1, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1, T2 propertyValue2) + { + if (_frozen) + { + _cached.Write(level, exception, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + exception, + messageTemplate, + propertyValue0, + propertyValue1, + propertyValue2, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + { + if (_frozen) + { + _cached.Write(level, exception, messageTemplate, propertyValues); + return; + } + + if (_reloadableLogger.InvokeWrite( + _root, + _cached, + this, + level, + exception, + messageTemplate, + propertyValues, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + } + + public bool IsEnabled(LogEventLevel level) + { + if (_frozen) + { + return _cached.IsEnabled(level); + } + + if (_reloadableLogger.InvokeIsEnabled( + _root, + _cached, + this, + level, + out var isEnabled, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return isEnabled; + } + + public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, + out IEnumerable boundProperties) + { + if (_frozen) + { + return _cached.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + } + + if (_reloadableLogger.InvokeBindMessageTemplate( + _root, + _cached, + this, + messageTemplate, + propertyValues, + out parsedTemplate, + out boundProperties, + out var canBind, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return canBind; + } + + public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) + { + if (_frozen) + { + return _cached.BindProperty(propertyName, value, destructureObjects, out property); + } + + if (_reloadableLogger.InvokeBindProperty( + _root, + _cached, + this, + propertyName, + value, + destructureObjects, + out property, + out var canBind, + out var newRoot, + out var newCached, + out var frozen)) + { + Update(newRoot, newCached, frozen); + } + + return canBind; + } + } +} \ No newline at end of file diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/FixedPropertyEnricher.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/FixedPropertyEnricher.cs new file mode 100644 index 0000000..3543173 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/FixedPropertyEnricher.cs @@ -0,0 +1,34 @@ +// 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 +{ + class FixedPropertyEnricher : ILogEventEnricher + { + readonly LogEventProperty _property; + + public FixedPropertyEnricher(LogEventProperty property) + { + _property = property; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + logEvent.AddPropertyIfAbsent(_property); + } + } +} diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/IReloadableLogger.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/IReloadableLogger.cs new file mode 100644 index 0000000..07b4cf9 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/IReloadableLogger.cs @@ -0,0 +1,21 @@ +// 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. + +namespace Serilog.Extensions.Hosting +{ + interface IReloadableLogger + { + ILogger ReloadLogger(); + } +} \ No newline at end of file diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/InjectedLoggerSettings.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/InjectedLoggerSettings.cs new file mode 100644 index 0000000..97e37ea --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/InjectedLoggerSettings.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Serilog.Configuration; +using Serilog.Core; + +namespace Serilog.Extensions.Hosting +{ + class InjectedLoggerSettings : ILoggerSettings + { + readonly IServiceProvider _services; + + public InjectedLoggerSettings(IServiceProvider services) + { + _services = services ?? throw new ArgumentNullException(nameof(services)); + } + + public void Configure(LoggerConfiguration loggerConfiguration) + { + var levelSwitch = _services.GetService(); + if (levelSwitch != null) + loggerConfiguration.MinimumLevel.ControlledBy(levelSwitch); + + foreach (var settings in _services.GetServices()) + loggerConfiguration.ReadFrom.Settings(settings); + + foreach (var policy in _services.GetServices()) + loggerConfiguration.Destructure.With(policy); + + foreach (var enricher in _services.GetServices()) + loggerConfiguration.Enrich.With(enricher); + + foreach (var filter in _services.GetServices()) + loggerConfiguration.Filter.With(filter); + + foreach (var sink in _services.GetServices()) + loggerConfiguration.WriteTo.Sink(sink); + } + } +} diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/ReloadableLogger.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/ReloadableLogger.cs new file mode 100644 index 0000000..454b707 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/ReloadableLogger.cs @@ -0,0 +1,669 @@ +// 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 System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using Serilog.Core; +using Serilog.Events; + +// ReSharper disable MemberCanBePrivate.Global + +namespace Serilog.Extensions.Hosting +{ + /// + /// A Serilog that can be reconfigured without invalidating existing + /// instances derived from it. + /// + public sealed class ReloadableLogger : ILogger, IReloadableLogger, IDisposable + { + readonly object _sync = new object(); + Logger _logger; + + // One-way; if the value is `true` it can never again be made `false`, allowing "double-checked" reads. If + // `true`, `_logger` is final and a memory barrier ensures the final value is seen by all threads. + bool _frozen; + + // Unsure whether this should be exposed; currently going for minimal API surface. + internal ReloadableLogger(Logger initial) + { + _logger = initial ?? throw new ArgumentNullException(nameof(initial)); + } + + ILogger IReloadableLogger.ReloadLogger() + { + return _logger; + } + + /// + /// Reload the logger using the supplied configuration delegate. + /// + /// A callback in which the logger is reconfigured. + /// is null. + public void Reload(Func configure) + { + if (configure == null) throw new ArgumentNullException(nameof(configure)); + + lock (_sync) + { + _logger.Dispose(); + _logger = configure(new LoggerConfiguration()).CreateLogger(); + } + } + + /// + /// Freeze the logger, so that no further reconfiguration is possible. Once the logger is frozen, logging through + /// new contextual loggers will have no additional cost, and logging directly through this logger will not require + /// any synchronization. + /// + /// The configured with the final settings. + /// The logger is already frozen. + public Logger Freeze() + { + lock (_sync) + { + if (_frozen) + throw new InvalidOperationException("The logger is already frozen."); + + _frozen = true; + + // https://github.com/dotnet/runtime/issues/20500#issuecomment-284774431 + // Publish `_logger` and `_frozen`. This is useful here because it means that once the logger is frozen - which + // we always expect - reads don't require any synchronization/interlocked instructions. + Interlocked.MemoryBarrierProcessWide(); + + return _logger; + } + } + + /// + public void Dispose() + { + lock (_sync) + _logger.Dispose(); + } + + /// + public ILogger ForContext(ILogEventEnricher enricher) + { + if (enricher == null) return this; + + if (_frozen) + return _logger.ForContext(enricher); + + lock (_sync) + return new CachingReloadableLogger(this, _logger, this, p => p.ForContext(enricher)); + } + + /// + public ILogger ForContext(IEnumerable enrichers) + { + if (enrichers == null) return this; + + if (_frozen) + return _logger.ForContext(enrichers); + + lock (_sync) + return new CachingReloadableLogger(this, _logger, this, p => p.ForContext(enrichers)); + } + + /// + public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + { + if (propertyName == null) return this; + + if (_frozen) + return _logger.ForContext(propertyName, value, destructureObjects); + + lock (_sync) + return new CachingReloadableLogger(this, _logger, this, p => p.ForContext(propertyName, value, destructureObjects)); + } + + /// + public ILogger ForContext() + { + if (_frozen) + return _logger.ForContext(); + + lock (_sync) + return new CachingReloadableLogger(this, _logger, this, p => p.ForContext()); + } + + /// + public ILogger ForContext(Type source) + { + if (source == null) return this; + + if (_frozen) + return _logger.ForContext(source); + + lock (_sync) + return new CachingReloadableLogger(this, _logger, this, p => p.ForContext(source)); + } + + /// + public void Write(LogEvent logEvent) + { + if (_frozen) + { + _logger.Write(logEvent); + return; + } + + lock (_sync) + { + _logger.Write(logEvent); + } + } + + /// + public void Write(LogEventLevel level, string messageTemplate) + { + if (_frozen) + { + _logger.Write(level, messageTemplate); + return; + } + + lock (_sync) + { + _logger.Write(level, messageTemplate); + } + } + + /// + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + if (_frozen) + { + _logger.Write(level, messageTemplate, propertyValue); + return; + } + + lock (_sync) + { + _logger.Write(level, messageTemplate, propertyValue); + } + } + + /// + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (_frozen) + { + _logger.Write(level, messageTemplate, propertyValue0, propertyValue1); + return; + } + + lock (_sync) + { + _logger.Write(level, messageTemplate, propertyValue0, propertyValue1); + } + } + + /// + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + if (_frozen) + { + _logger.Write(level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return; + } + + lock (_sync) + { + _logger.Write(level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + } + + /// + public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + { + if (_frozen) + { + _logger.Write(level, messageTemplate, propertyValues); + return; + } + + lock (_sync) + { + _logger.Write(level, messageTemplate, propertyValues); + } + } + + /// + public void Write(LogEventLevel level, Exception exception, string messageTemplate) + { + if (_frozen) + { + _logger.Write(level, exception, messageTemplate); + return; + } + + lock (_sync) + { + _logger.Write(level, exception, messageTemplate); + } + } + + /// + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + { + if (_frozen) + { + _logger.Write(level, exception, messageTemplate, propertyValue); + return; + } + + lock (_sync) + { + _logger.Write(level, exception, messageTemplate, propertyValue); + } + } + + /// + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (_frozen) + { + _logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1); + return; + } + + lock (_sync) + { + _logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1); + } + } + + /// + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + if (_frozen) + { + _logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return; + } + + lock (_sync) + { + _logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + } + + /// + public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + { + if (_frozen) + { + _logger.Write(level, exception, messageTemplate, propertyValues); + return; + } + + lock (_sync) + { + _logger.Write(level, exception, messageTemplate, propertyValues); + } + } + + /// + public bool IsEnabled(LogEventLevel level) + { + if (_frozen) + { + return _logger.IsEnabled(level); + } + + lock (_sync) + { + return _logger.IsEnabled(level); + } + } + + /// + public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, + out IEnumerable boundProperties) + { + if (_frozen) + { + return _logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + } + + lock (_sync) + { + return _logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + } + } + + /// + public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) + { + if (_frozen) + { + return _logger.BindProperty(propertyName, value, destructureObjects, out property); + } + + lock (_sync) + { + return _logger.BindProperty(propertyName, value, destructureObjects, out property); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + (ILogger, bool) UpdateForCaller(ILogger root, ILogger cached, IReloadableLogger caller, out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (cached != null && root == _logger) + { + newRoot = default; + newCached = default; + frozen = _frozen; + return (cached, frozen); // If we're frozen, then the caller hasn't observed this yet and should update. + } + + newRoot = _logger; + newCached = caller.ReloadLogger(); + frozen = false; + return (newCached, true); + } + + internal bool InvokeIsEnabled(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, out bool isEnabled, out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + isEnabled = logger.IsEnabled(level); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + isEnabled = logger.IsEnabled(level); + return update; + } + } + + internal bool InvokeBindMessageTemplate(ILogger root, ILogger cached, IReloadableLogger caller, string messageTemplate, + object[] propertyValues, out MessageTemplate parsedTemplate, out IEnumerable boundProperties, + out bool canBind, out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + canBind = logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + canBind = logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties); + return update; + } + } + + internal bool InvokeBindProperty(ILogger root, ILogger cached, IReloadableLogger caller, string propertyName, + object propertyValue, bool destructureObjects, out LogEventProperty property, + out bool canBind, out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + canBind = logger.BindProperty(propertyName, propertyValue, destructureObjects, out property); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + canBind = logger.BindProperty(propertyName, propertyValue, destructureObjects, out property); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEvent logEvent, out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(logEvent); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(logEvent); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, string messageTemplate, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, string messageTemplate, + T propertyValue, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, string messageTemplate, + T0 propertyValue0, T1 propertyValue1, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue0, propertyValue1); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue0, propertyValue1); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, string messageTemplate, + T0 propertyValue0, T1 propertyValue1, T2 propertyValue2, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, string messageTemplate, + object[] propertyValues, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValues); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, messageTemplate, propertyValues); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, Exception exception, string messageTemplate, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, Exception exception, string messageTemplate, + T propertyValue, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, Exception exception, string messageTemplate, + T0 propertyValue0, T1 propertyValue1, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, Exception exception, string messageTemplate, + T0 propertyValue0, T1 propertyValue1, T2 propertyValue2, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + return update; + } + } + + internal bool InvokeWrite(ILogger root, ILogger cached, IReloadableLogger caller, LogEventLevel level, Exception exception, string messageTemplate, + object[] propertyValues, + out ILogger newRoot, out ILogger newCached, out bool frozen) + { + if (_frozen) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValues); + return update; + } + + lock (_sync) + { + var (logger, update) = UpdateForCaller(root, cached, caller, out newRoot, out newCached, out frozen); + logger.Write(level, exception, messageTemplate, propertyValues); + return update; + } + } + + internal bool CreateChild( + ILogger root, + IReloadableLogger parent, + ILogger cachedParent, + Func configureChild, + out ILogger child, + out ILogger newRoot, + out ILogger newCached, + out bool frozen) + { + if (_frozen) + { + var (logger, _) = UpdateForCaller(root, cachedParent, parent, out newRoot, out newCached, out frozen); + child = configureChild(logger); + return true; // Always an update, since the caller has not observed that the reloadable logger is frozen. + } + + // No synchronization, here - a lot of loggers are created and thrown away again without ever being used, + // so we just return a lazy wrapper. + child = new CachingReloadableLogger(this, root, parent, configureChild); + newRoot = default; + newCached = default; + frozen = default; + return false; + } + } +} diff --git a/src/Serilog.Extensions.Hosting/LoggerConfigurationExtensions.cs b/src/Serilog.Extensions.Hosting/LoggerConfigurationExtensions.cs new file mode 100644 index 0000000..2a78262 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/LoggerConfigurationExtensions.cs @@ -0,0 +1,40 @@ +// 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 Microsoft.Extensions.Hosting; +using Serilog.Extensions.Hosting; +using System; + +namespace Serilog +{ + /// + /// Extends . + /// + public static class LoggerConfigurationExtensions + { + /// + /// Create a for use during host bootstrapping. The + /// + /// configuration overload will detect when is set to a instance, and + /// reconfigure/freeze it so that s created during host bootstrapping continue to work once + /// logger configuration (with access to host services) is completed. + /// + /// + /// + public static ReloadableLogger CreateBootstrapLogger(this LoggerConfiguration loggerConfiguration) + { + return new ReloadableLogger(loggerConfiguration.CreateLogger()); + } + } +} diff --git a/src/Serilog.Extensions.Hosting/LoggerSettingsConfigurationExtensions.cs b/src/Serilog.Extensions.Hosting/LoggerSettingsConfigurationExtensions.cs new file mode 100644 index 0000000..4e9c62c --- /dev/null +++ b/src/Serilog.Extensions.Hosting/LoggerSettingsConfigurationExtensions.cs @@ -0,0 +1,43 @@ +// 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 System; +using Serilog.Configuration; +using Serilog.Core; +using Serilog.Extensions.Hosting; + +namespace Serilog +{ + /// + /// Extends with methods for consuming host services. + /// + public static class LoggerSettingsConfigurationExtensions + { + /// + /// Configure the logger using components from the . If present, the logger will + /// receive implementations/instances of , , + /// , , , and + /// . + /// + /// The `ReadFrom` configuration object. + /// A from which services will be requested. + /// A to support method chaining. + public static LoggerConfiguration Services( + this LoggerSettingsConfiguration loggerSettingsConfiguration, + IServiceProvider services) + { + return loggerSettingsConfiguration.Settings(new InjectedLoggerSettings(services)); + } + } +} \ 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 f8bddc8..e424cd7 100644 --- a/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj +++ b/src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj @@ -2,9 +2,9 @@ Serilog support for .NET Core logging in hosted services - 3.1.1 + 4.0.0 Microsoft;Serilog Contributors - netstandard2.0 + netstandard2.1 true true Serilog.Extensions.Hosting @@ -23,11 +23,11 @@ - + - - - + + + diff --git a/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs b/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs index 26091a2..0863769 100644 --- a/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs +++ b/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.Logging; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; +// ReSharper disable MemberCanBePrivate.Global namespace Serilog { @@ -85,7 +86,6 @@ public static IHostBuilder UseSerilog( return builder; } - /// Sets Serilog as the logging provider. /// /// A is supplied so that configuration and hosting information can be used. @@ -125,6 +125,10 @@ public static IHostBuilder UseSerilog( /// 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 (created using + /// ), and is + /// not specified, the the bootstrap logger will be reconfigured through the supplied delegate, rather than being + /// replaced entirely or ignored. /// The host builder. public static IHostBuilder UseSerilog( this IHostBuilder builder, @@ -135,6 +139,10 @@ public static IHostBuilder UseSerilog( 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. + var reloadable = Log.Logger as ReloadableLogger; + var useReload = reloadable != null && !preserveStaticLogger; + builder.ConfigureServices((context, collection) => { LoggerProviderCollection loggerProviders = null; @@ -145,13 +153,30 @@ public static IHostBuilder UseSerilog( collection.AddSingleton(services => { - var loggerConfiguration = new LoggerConfiguration(); + ILogger logger; + if (useReload) + { + reloadable!.Reload(cfg => + { + if (loggerProviders != null) + cfg.WriteTo.Providers(loggerProviders); + + configureLogger(context, services, cfg); + return cfg; + }); + + logger = reloadable.Freeze(); + } + else + { + var loggerConfiguration = new LoggerConfiguration(); - if (loggerProviders != null) - loggerConfiguration.WriteTo.Providers(loggerProviders); - - configureLogger(context, services, loggerConfiguration); - var logger = loggerConfiguration.CreateLogger(); + if (loggerProviders != null) + loggerConfiguration.WriteTo.Providers(loggerProviders); + + configureLogger(context, services, loggerConfiguration); + logger = loggerConfiguration.CreateLogger(); + } return new RegisteredLogger(logger); }); @@ -180,7 +205,7 @@ public static IHostBuilder UseSerilog( Log.Logger = logger; } - var factory = new SerilogLoggerFactory(registeredLogger, true, loggerProviders); + var factory = new SerilogLoggerFactory(registeredLogger, !useReload, loggerProviders); if (writeToProviders) { diff --git a/test/Serilog.Extensions.Hosting.Tests/LoggerSettingsConfigurationExtensionsTests.cs b/test/Serilog.Extensions.Hosting.Tests/LoggerSettingsConfigurationExtensionsTests.cs new file mode 100644 index 0000000..c4e0ac1 --- /dev/null +++ b/test/Serilog.Extensions.Hosting.Tests/LoggerSettingsConfigurationExtensionsTests.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; +using Serilog.Core; +using Serilog.Extensions.Hosting.Tests.Support; +using Xunit; + +namespace Serilog.Extensions.Hosting.Tests +{ + public class LoggerSettingsConfigurationExtensionsTests + { + [Fact] + public void SinksAreInjectedFromTheServiceProvider() + { + var sink = new SerilogSink(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(sink); + using var services = serviceCollection.BuildServiceProvider(); + + using var logger = new LoggerConfiguration() + .ReadFrom.Services(services) + .CreateLogger(); + + logger.Information("Hello, world!"); + + var evt = Assert.Single(sink.Writes); + Assert.Equal("Hello, world!", evt!.MessageTemplate.Text); + } + } +} \ No newline at end of file diff --git a/test/Serilog.Extensions.Hosting.Tests/ReloadableLoggerTests.cs b/test/Serilog.Extensions.Hosting.Tests/ReloadableLoggerTests.cs new file mode 100644 index 0000000..e14f4aa --- /dev/null +++ b/test/Serilog.Extensions.Hosting.Tests/ReloadableLoggerTests.cs @@ -0,0 +1,22 @@ +using Xunit; + +namespace Serilog.Extensions.Hosting.Tests +{ + public class ReloadableLoggerTests + { + [Fact] + public void AFrozenLoggerYieldsSerilogLoggers() + { + var logger = new ReloadableLogger(new LoggerConfiguration().CreateLogger()); + var contextual = logger.ForContext(); + + var nested = contextual.ForContext("test", "test"); + Assert.IsNotType(nested); + + logger.Freeze(); + + nested = contextual.ForContext("test", "test"); + Assert.IsType(nested); + } + } +} 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 bb9c750..9214cd1 100644 --- a/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj +++ b/test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj @@ -1,12 +1,13 @@  - netcoreapp2.2 + netcoreapp3.1 Serilog.Extensions.Hosting.Tests ../../assets/Serilog.snk true true true + latest @@ -14,6 +15,7 @@ + all