diff --git a/Build.ps1 b/Build.ps1 index 4349906..7c5a85f 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,21 +1,51 @@ +echo "build: Build started" + Push-Location $PSScriptRoot -if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse } +if(Test-Path .\artifacts) { + echo "build: Cleaning .\artifacts" + Remove-Item .\artifacts -Force -Recurse +} -& dotnet restore +& dotnet restore --no-cache -$revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; +$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; +$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] -Push-Location src/Serilog.Extensions.Logging +echo "build: Version suffix is $suffix" -& dotnet pack -c Release -o ..\..\.\artifacts --version-suffix=$revision -if($LASTEXITCODE -ne 0) { exit 1 } +foreach ($src in ls src/*) { + Push-Location $src -Pop-Location -Push-Location test/Serilog.Extensions.Logging.Tests + echo "build: Packaging project in $src" -& dotnet test -c Release -if($LASTEXITCODE -ne 0) { exit 2 } + & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix + if($LASTEXITCODE -ne 0) { exit 1 } + + Pop-Location +} + +foreach ($test in ls test/*.PerformanceTests) { + Push-Location $test + + echo "build: Building performance test project in $test" + + & dotnet build -c Release + if($LASTEXITCODE -ne 0) { exit 2 } + + Pop-Location +} + +foreach ($test in ls test/*.Tests) { + Push-Location $test + + echo "build: Testing project in $test" + + & dotnet test -c Release + if($LASTEXITCODE -ne 0) { exit 3 } + + Pop-Location +} -Pop-Location Pop-Location diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..c8e55a8 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,6 @@ +1.1.0 + * #41 - Provide a `dispose` flag to instruct provider to take ownership of the Serilog logger + +1.0.0 + * Initial version + diff --git a/README.md b/README.md index f5d495e..ebaa3f6 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ This package routes ASP.NET log messages through Serilog, so you can get informa **First**, install the _Serilog.Extensions.Logging_ [NuGet package](https://www.nuget.org/packages/Serilog.Extensions.Logging) into your web or console app. You will need a way to view the log messages - _Serilog.Sinks.Literate_ writes these to the console. ```powershell -Install-Package Serilog.Extensions.Logging -Pre -DependencyVersion Highest -Install-Package Serilog.Sinks.Literate -Pre +Install-Package Serilog.Extensions.Logging -DependencyVersion Highest +Install-Package Serilog.Sinks.Literate ``` **Next**, in your application's `Startup` method, configure Serilog first: @@ -38,9 +38,13 @@ call `AddSerilog()` on the provided `loggerFactory`. ```csharp public void Configure(IApplicationBuilder app, IHostingEnvironment env, - ILoggerFactory loggerfactory) + ILoggerFactory loggerfactory, + IApplicationLifetime appLifetime) { loggerfactory.AddSerilog(); + + // Ensure any buffered events are sent at shutdown + appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); ``` That's it! With the level bumped up a little you should see log output like: diff --git a/appveyor.yml b/appveyor.yml index 6a39959..19d0d28 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,5 @@ version: '{build}' +skip_tags: true image: Visual Studio 2015 configuration: Release install: @@ -18,5 +19,11 @@ deploy: secure: nvZ/z+pMS91b3kG4DgfES5AcmwwGoBYQxr9kp4XiJHj25SAlgdIxFx++1N0lFH2x skip_symbols: true on: - branch: /^(dev|master)$/ - + branch: /^(master|dev)$/ +- provider: GitHub + auth_token: + secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX + artifact: /Serilog.*\.nupkg/ + tag: v$(appveyor_build_version) + on: + branch: master diff --git a/samples/WebSample/Controllers/HomeController.cs b/samples/WebSample/Controllers/HomeController.cs index 0cb5e17..2880e9f 100644 --- a/samples/WebSample/Controllers/HomeController.cs +++ b/samples/WebSample/Controllers/HomeController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Serilog; namespace WebSample.Controllers { @@ -10,6 +11,8 @@ public class HomeController : Controller { public IActionResult Index() { + Log.Information("Hello from the Index!"); + return View(); } @@ -17,6 +20,8 @@ public IActionResult About() { ViewData["Message"] = "Your application description page."; + Log.Information("This is a handler for {Path}", Request.Path); + return View(); } diff --git a/samples/WebSample/Startup.cs b/samples/WebSample/Startup.cs index 9d72903..df76f4a 100644 --- a/samples/WebSample/Startup.cs +++ b/samples/WebSample/Startup.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Serilog; namespace WebSample { @@ -14,12 +15,16 @@ public class Startup { public Startup(IHostingEnvironment env) { - var builder = new ConfigurationBuilder() + Configuration = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); + .AddEnvironmentVariables() + .Build(); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(Configuration) + .CreateLogger(); } public IConfigurationRoot Configuration { get; } @@ -34,8 +39,8 @@ 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, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - loggerFactory.AddDebug(); + // Specifying dispose: true closes and flushes the Serilog `Log` class when the app shuts down. + loggerFactory.AddSerilog(dispose: true); if (env.IsDevelopment()) { diff --git a/samples/WebSample/appsettings.json b/samples/WebSample/appsettings.json index fa8ce71..02557b3 100644 --- a/samples/WebSample/appsettings.json +++ b/samples/WebSample/appsettings.json @@ -1,10 +1,18 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { + "Serilog": { + "MinimumLevel": { "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } + "Override": { + "System": "Information", + "Microsoft": "Information" + } + }, + "Enrich": [ + "FromLogContext" + ], + "WriteTo": [ + "Trace", + "LiterateConsole" + ] } } diff --git a/samples/WebSample/project.json b/samples/WebSample/project.json index dd01a7f..c9468c7 100644 --- a/samples/WebSample/project.json +++ b/samples/WebSample/project.json @@ -19,7 +19,11 @@ "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0" + "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", + "Serilog.Extensions.Logging": { "target": "project" }, + "Serilog.Settings.Configuration": "2.1.0-dev-00028", + "Serilog.Sinks.Trace": "2.0.0", + "Serilog.Sinks.Literate": "2.0.0" }, "tools": { diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 3f72760..72b010c 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -21,11 +21,20 @@ class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher // May be null; if it is, Log.Logger will be lazily used readonly ILogger _logger; + readonly Action _dispose; - public SerilogLoggerProvider(ILogger logger = null) + public SerilogLoggerProvider(ILogger logger = null, bool dispose = false) { if (logger != null) _logger = logger.ForContext(new[] { this }); + + if (dispose) + { + if (logger != null) + _dispose = () => (logger as IDisposable)?.Dispose(); + else + _dispose = Log.CloseAndFlush; + } } public FrameworkLogger CreateLogger(string name) @@ -77,6 +86,9 @@ public SerilogLoggerScope CurrentScope } #endif - public void Dispose() { } + public void Dispose() + { + _dispose?.Invoke(); + } } } diff --git a/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs b/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs index a275721..c2aa06d 100644 --- a/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs +++ b/src/Serilog.Extensions.Logging/SerilogLoggerFactoryExtensions.cs @@ -17,14 +17,18 @@ public static class SerilogLoggerFactoryExtensions /// /// The logger factory 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. /// The logger factory. public static ILoggerFactory AddSerilog( this ILoggerFactory factory, - ILogger logger = null) + ILogger logger = null, + bool dispose = false) { if (factory == null) throw new ArgumentNullException(nameof(factory)); - factory.AddProvider(new SerilogLoggerProvider(logger)); + factory.AddProvider(new SerilogLoggerProvider(logger, dispose)); return factory; } diff --git a/src/Serilog.Extensions.Logging/project.json b/src/Serilog.Extensions.Logging/project.json index 95a0db4..000eb27 100644 --- a/src/Serilog.Extensions.Logging/project.json +++ b/src/Serilog.Extensions.Logging/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.1.0-*", "description": "Serilog provider for Microsoft.Extensions.Logging", "authors": [ "Microsoft", "Serilog Contributors" ], "packOptions": { diff --git a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs index 41ceba5..c50a5da 100644 --- a/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs +++ b/test/Serilog.Extensions.Logging.Tests/SerilogLoggerTests.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using Serilog.Debugging; +using Serilog.Framework.Logging.Tests.Support; using Xunit; namespace Serilog.Extensions.Logging.Test @@ -269,6 +270,24 @@ public void CarriesEventIdIfNonzero() Assert.Equal(42, id.Value); } + [Fact] + public void WhenDisposeIsFalseProvidedLoggerIsNotDisposed() + { + var logger = new DisposeTrackingLogger(); + var provider = new SerilogLoggerProvider(logger, false); + provider.Dispose(); + Assert.False(logger.IsDisposed); + } + + [Fact] + public void WhenDisposeIsTrueProvidedLoggerIsDisposed() + { + var logger = new DisposeTrackingLogger(); + var provider = new SerilogLoggerProvider(logger, true); + provider.Dispose(); + Assert.True(logger.IsDisposed); + } + private class FoodScope : IEnumerable> { readonly string _name; diff --git a/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs new file mode 100644 index 0000000..a967fef --- /dev/null +++ b/test/Serilog.Extensions.Logging.Tests/Support/DisposeTrackingLogger.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Framework.Logging.Tests.Support +{ + public class DisposeTrackingLogger : ILogger, IDisposable + { + public bool IsDisposed { get; set; } + + public ILogger ForContext(ILogEventEnricher enricher) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(IEnumerable enrichers) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext() + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(Type source) + { + return new LoggerConfiguration().CreateLogger(); + } + + public void Write(LogEvent logEvent) + { + } + + public void Write(LogEventLevel level, string messageTemplate) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1, T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public bool IsEnabled(LogEventLevel level) + { + return false; + } + + public void Verbose(string messageTemplate) + { + } + + public void Verbose(string messageTemplate, T propertyValue) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Verbose(string messageTemplate, params object[] propertyValues) + { + } + + public void Verbose(Exception exception, string messageTemplate) + { + } + + public void Verbose(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Debug(string messageTemplate) + { + } + + public void Debug(string messageTemplate, T propertyValue) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Debug(string messageTemplate, params object[] propertyValues) + { + } + + public void Debug(Exception exception, string messageTemplate) + { + } + + public void Debug(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Information(string messageTemplate) + { + } + + public void Information(string messageTemplate, T propertyValue) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Information(string messageTemplate, params object[] propertyValues) + { + } + + public void Information(Exception exception, string messageTemplate) + { + } + + public void Information(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Information(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Warning(string messageTemplate) + { + } + + public void Warning(string messageTemplate, T propertyValue) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Warning(string messageTemplate, params object[] propertyValues) + { + } + + public void Warning(Exception exception, string messageTemplate) + { + } + + public void Warning(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Error(string messageTemplate) + { + } + + public void Error(string messageTemplate, T propertyValue) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Error(string messageTemplate, params object[] propertyValues) + { + } + + public void Error(Exception exception, string messageTemplate) + { + } + + public void Error(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Error(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Fatal(string messageTemplate) + { + } + + public void Fatal(string messageTemplate, T propertyValue) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Fatal(string messageTemplate, params object[] propertyValues) + { + } + + public void Fatal(Exception exception, string messageTemplate) + { + } + + public void Fatal(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, + out IEnumerable boundProperties) + { + parsedTemplate = null; + boundProperties = null; + return false; + } + + public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) + { + property = null; + return false; + } + + public void Dispose() + { + IsDisposed = true; + } + } +}