diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..04d41db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Report a bug and help us to improve Serilog +title: '' +labels: bug +assignees: '' + +--- + +The Serilog maintainers want you to have a great experience using Serilog, and will happily track down and resolve bugs. We all have limited time, though, so please think through all of the factors that might be involved and include as much useful information as possible 😊. + +ℹ If the problem is caused by a sink or other related package, please try to track down the correct repository for that package and create the report there: this tracker is for the **Serilog.AspNetCore** package only. + +**Description** +What's going wrong? + +**Reproduction** +Please provide code samples showing how you're configuring and calling Serilog to produce the behavior. + +**Expected behavior** +A concise description of what you expected to happen. + +**Relevant package, tooling and runtime versions** +What Serilog version are you using, on what platform? + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..272c322 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an improvement to Serilog +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/usage-help.md b/.github/ISSUE_TEMPLATE/usage-help.md new file mode 100644 index 0000000..4e4db70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/usage-help.md @@ -0,0 +1,16 @@ +--- +name: Usage help +about: Get help with using Serilog +title: '' +labels: invalid +assignees: '' + +--- + +Hi! 👋 + +The Serilog community wants you to have a great experience using Serilog. Unfortunately, only a handful of maintainers actively follow this repository, and our time is short, so we cannot answer usage questions posted here. + +Fortunately, a much larger group of people (including some of us) also watch and answer questions on the [`serilog` tag on Stack Overflow](https://stackoverflow.com/questions/tagged/serilog). + +Please head over to Stack Overflow, ask your question, and tag it with `serilog`. Thanks! ❤ diff --git a/README.md b/README.md index 860fe95..c1a3538 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ public class Program try { Log.Information("Starting web host"); - CreateWebHostBuilder(args).Build().Run(); + CreateHostBuilder(args).Build().Run(); return 0; } catch (Exception ex) @@ -46,21 +46,22 @@ public class Program } ``` -**Then**, add `UseSerilog()` to the web host builder in `BuildWebHost()`. +**Then**, add `UseSerilog()` to the Generic Host in `CreateHostBuilder()`. ```csharp - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseSerilog(); // <-- Add this line; + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog() // <-- Add this line + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); } ``` **Finally**, clean up by removing the remaining configuration for the default logger: - * Remove calls to `AddLogging()` * Remove the `"Logging"` section from _appsettings.json_ files (this can be replaced with [Serilog configuration](https://github.com/serilog/serilog-settings-configuration) as shown in [the _EarlyInitializationSample_ project](https://github.com/serilog/serilog-aspnetcore/blob/dev/samples/EarlyInitializationSample/Program.cs), if required) - * Remove `ILoggerFactory` parameters and any `Add*()` calls on the logger factory in _Startup.cs_ * Remove `UseApplicationInsights()` (this can be replaced with the [Serilog AI sink](https://github.com/serilog/serilog-sinks-applicationinsights), if required) That's it! With the level bumped up a little you will see log output resembling: @@ -191,13 +192,13 @@ app.UseSerilogRequestLogging(options => You can alternatively configure Serilog inline, in `BuildWebHost()`, using a delegate as shown below: ```csharp - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration + .UseSerilog((hostingContext, services, loggerConfiguration) => loggerConfiguration .ReadFrom.Configuration(hostingContext.Configuration) .Enrich.FromLogContext() .WriteTo.Console()) ``` -This has the advantage of making the `hostingContext`'s `Configuration` object available for [configuration of the logger](https://github.com/serilog/serilog-settings-configuration), but at the expense of losing `Exception`s raised earlier in program startup. +This has the advantage of making a service provider and the `hostingContext`'s `Configuration` object available for [configuration of the logger](https://github.com/serilog/serilog-settings-configuration), but at the expense of losing `Exception`s raised earlier in program startup. If this method is used, `Log.Logger` is assigned implicitly, and closed when the app is shut down. diff --git a/appveyor.yml b/appveyor.yml index ef82a65..39b0037 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: '{build}' skip_tags: true -image: Visual Studio 2017 +image: Visual Studio 2019 install: - ps: ./Setup.ps1 build_script: @@ -10,9 +10,9 @@ artifacts: - path: artifacts/Serilog.*.nupkg deploy: - provider: NuGet - api_key: - secure: gvYNwOSxj9Bq4erOm7alpWzHlEmNLLUV99VYKV1WF9qTJeDkvYpswoNHi9sAlZH4 skip_symbols: true + api_key: + secure: jZtLAmD4ALF9x1BZR1DiV3KhKlliWbzRUAw73xfMZaBsvz19123qLz2zw1+hPdcn on: branch: /^(master|dev)$/ - provider: GitHub diff --git a/global.json b/global.json index 2223a05..80992af 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { "sdk": { - "version": "3.0.100" + "allowPrerelease": false, + "version": "3.1.201", + "rollForward": "latestFeature" } -} \ No newline at end of file +} diff --git a/samples/EarlyInitializationSample/EarlyInitializationSample.csproj b/samples/EarlyInitializationSample/EarlyInitializationSample.csproj index 0b71549..2c67adb 100644 --- a/samples/EarlyInitializationSample/EarlyInitializationSample.csproj +++ b/samples/EarlyInitializationSample/EarlyInitializationSample.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + netcoreapp3.1 diff --git a/samples/InlineInitializationSample/InlineInitializationSample.csproj b/samples/InlineInitializationSample/InlineInitializationSample.csproj index 0b71549..2c67adb 100644 --- a/samples/InlineInitializationSample/InlineInitializationSample.csproj +++ b/samples/InlineInitializationSample/InlineInitializationSample.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + netcoreapp3.1 diff --git a/samples/InlineInitializationSample/Program.cs b/samples/InlineInitializationSample/Program.cs index 9cf2e42..b2c964c 100644 --- a/samples/InlineInitializationSample/Program.cs +++ b/samples/InlineInitializationSample/Program.cs @@ -13,11 +13,8 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }) - .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) + .UseSerilog((hostingContext, services, loggerConfiguration) => loggerConfiguration .ReadFrom.Configuration(hostingContext.Configuration) .Enrich.FromLogContext() .WriteTo.Debug() diff --git a/samples/InlineInitializationSample/Startup.cs b/samples/InlineInitializationSample/Startup.cs index 82f3bcd..c8dc31e 100644 --- a/samples/InlineInitializationSample/Startup.cs +++ b/samples/InlineInitializationSample/Startup.cs @@ -1,10 +1,9 @@ -using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; -using Serilog.Events; +using Serilog.AspNetCore; namespace InlineInitializationSample { @@ -12,6 +11,14 @@ public class Startup { public void ConfigureServices(IServiceCollection services) { + services.Configure(o => + { + o.EnrichDiagnosticContext = (diagnosticContext, httpContext) => + { + diagnosticContext.Set("RemoteIpAddress", httpContext.Connection.RemoteIpAddress.MapToIPv4()); + }; + }); + services.AddControllersWithViews(); } diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs index e6c17f1..dee6a5d 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs @@ -24,6 +24,7 @@ namespace Serilog.AspNetCore { + // ReSharper disable once ClassNeverInstantiated.Global class RequestLoggingMiddleware { readonly RequestDelegate _next; @@ -31,6 +32,7 @@ class RequestLoggingMiddleware readonly MessageTemplate _messageTemplate; readonly Action _enrichDiagnosticContext; readonly Func _getLevel; + readonly ILogger _logger; static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options) @@ -42,6 +44,7 @@ public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnost _getLevel = options.GetLevel; _enrichDiagnosticContext = options.EnrichDiagnosticContext; _messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate); + _logger = options.Logger?.ForContext(); } // ReSharper disable once UnusedMember.Global @@ -73,7 +76,7 @@ public async Task Invoke(HttpContext httpContext) bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex) { - var logger = Log.ForContext(); + var logger = _logger ?? Log.ForContext(); var level = _getLevel(httpContext, elapsedMs, ex); if (!logger.IsEnabled(level)) return false; @@ -106,7 +109,18 @@ static double GetElapsedMilliseconds(long start, long stop) static string GetPath(HttpContext httpContext) { - return httpContext.Features.Get()?.RawTarget ?? httpContext.Request.Path.ToString(); + /* + In some cases, like when running integration tests with WebApplicationFactory + the RawTarget returns an empty string instead of null, in that case we can't use + ?? as fallback. + */ + var requestPath = httpContext.Features.Get()?.RawTarget; + if (string.IsNullOrEmpty(requestPath)) + { + requestPath = httpContext.Request.Path.ToString(); + } + + return requestPath; } } } diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs index 327e468..8d86582 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019-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. @@ -16,13 +16,25 @@ using Serilog.Events; using System; +// ReSharper disable UnusedAutoPropertyAccessor.Global + namespace Serilog.AspNetCore { /// - /// Contains options for the . + /// Contains options for the . /// public class RequestLoggingOptions { + const string DefaultRequestCompletionMessageTemplate = + "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; + + static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) => + ex != null + ? LogEventLevel.Error + : ctx.Response.StatusCode > 499 + ? LogEventLevel.Error + : LogEventLevel.Information; + /// /// Gets or sets the message template. The default value is /// "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms". The @@ -50,6 +62,19 @@ public class RequestLoggingOptions /// public Action EnrichDiagnosticContext { get; set; } - internal RequestLoggingOptions() { } + /// + /// The logger through which request completion events will be logged. The default is to use the + /// static class. + /// + public ILogger Logger { get; set; } + + /// + /// Constructor + /// + public RequestLoggingOptions() + { + GetLevel = DefaultGetLevel; + MessageTemplate = DefaultRequestCompletionMessageTemplate; + } } } diff --git a/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj index 5c5c3f7..31f5836 100644 --- a/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj +++ b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj @@ -2,15 +2,16 @@ Serilog support for ASP.NET Core logging - 3.2.0 + 3.4.0 Microsoft;Serilog Contributors - netstandard2.0 + netstandard2.0;netcoreapp3.1 true true ../../assets/Serilog.snk true true serilog;aspnet;aspnetcore + serilog-extension-nuget.png https://serilog.net/images/serilog-extension-nuget.png https://github.com/serilog/serilog-aspnetcore Apache-2.0 @@ -21,16 +22,30 @@ - - + + + + + + - + - - - - + + + + + + + + + + + + + + diff --git a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs index 838fd94..b163fc7 100644 --- a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs +++ b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2019 Serilog Contributors +// Copyright 2019-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,10 +13,12 @@ // limitations under the License. using System; + using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + using Serilog.AspNetCore; -using Serilog.Events; namespace Serilog { @@ -25,16 +27,6 @@ namespace Serilog /// public static class SerilogApplicationBuilderExtensions { - const string DefaultRequestCompletionMessageTemplate = - "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; - - static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) => - ex != null - ? LogEventLevel.Error - : ctx.Response.StatusCode > 499 - ? LogEventLevel.Error - : LogEventLevel.Information; - /// /// Adds middleware for streamlined request logging. Instead of writing HTTP request information /// like method, path, timing, status code and exception details @@ -70,12 +62,9 @@ public static IApplicationBuilder UseSerilogRequestLogging( Action configureOptions = null) { if (app == null) throw new ArgumentNullException(nameof(app)); - - var opts = new RequestLoggingOptions - { - GetLevel = DefaultGetLevel, - MessageTemplate = DefaultRequestCompletionMessageTemplate - }; + + var opts = app.ApplicationServices.GetService>()?.Value ?? new RequestLoggingOptions(); + configureOptions?.Invoke(opts); if (opts.MessageTemplate == null) diff --git a/src/Serilog.AspNetCore/images/serilog-extension-nuget.png b/src/Serilog.AspNetCore/images/serilog-extension-nuget.png new file mode 100644 index 0000000..1dfe430 Binary files /dev/null and b/src/Serilog.AspNetCore/images/serilog-extension-nuget.png differ diff --git a/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj index 83b1006..99eef06 100644 --- a/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj +++ b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp2.1;netcoreapp3.1 Serilog.AspNetCore.Tests ../../assets/Serilog.snk true @@ -14,9 +14,17 @@ - - + + + + + + + + + + diff --git a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs index 6aa9b1f..e95368d 100644 --- a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs +++ b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs @@ -1,16 +1,103 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Linq; +using System.Threading.Tasks; + using Xunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.Builder; + +using Serilog.Filters; +using Serilog.AspNetCore.Tests.Support; + namespace Serilog.AspNetCore.Tests { - public class SerilogWebHostBuilderExtensionsTests + public class SerilogWebHostBuilderExtensionsTests : IClassFixture { + SerilogWebApplicationFactory _web; + + public SerilogWebHostBuilderExtensionsTests(SerilogWebApplicationFactory web) + { + _web = web; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task DisposeShouldBeHandled(bool dispose) + { + var logger = new DisposeTrackingLogger(); + using (var web = Setup(logger, dispose)) + { + await web.CreateClient().GetAsync("/"); + } + + Assert.Equal(dispose, logger.IsDisposed); + } + [Fact] - public void Todo() + public async Task RequestLoggingMiddlewareShouldEnrich() { + var (sink, web) = Setup(options => + { + options.EnrichDiagnosticContext += (diagnosticContext, httpContext) => + { + diagnosticContext.Set("SomeInteger", 42); + }; + }); + + await web.CreateClient().GetAsync("/resource"); + + Assert.NotEmpty(sink.Writes); + + var completionEvent = sink.Writes.Where(logEvent => Matching.FromSource()(logEvent)).FirstOrDefault(); + + Assert.Equal(42, completionEvent.Properties["SomeInteger"].LiteralValue()); + Assert.Equal("string", completionEvent.Properties["SomeString"].LiteralValue()); + Assert.Equal("/resource", completionEvent.Properties["RequestPath"].LiteralValue()); + Assert.Equal(200, completionEvent.Properties["StatusCode"].LiteralValue()); + Assert.Equal("GET", completionEvent.Properties["RequestMethod"].LiteralValue()); + Assert.True(completionEvent.Properties.ContainsKey("Elapsed")); + } + + WebApplicationFactory Setup(ILogger logger, bool dispose, Action configureOptions = null) + { + var web = _web.WithWebHostBuilder( + builder => builder + .ConfigureServices(sc => sc.Configure(options => + { + options.Logger = logger; + options.EnrichDiagnosticContext += (diagnosticContext, httpContext) => + { + diagnosticContext.Set("SomeString", "string"); + }; + })) + .Configure(app => + { + app.UseSerilogRequestLogging(configureOptions); + app.Run(_ => Task.CompletedTask); // 200 OK + }) + .UseSerilog(logger, dispose)); + + return web; + } + + (SerilogSink, WebApplicationFactory) Setup(Action configureOptions = null) + { + var sink = new SerilogSink(); + var logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Sink(sink) + .CreateLogger(); + + var web = Setup(logger, true, configureOptions); + return (sink, web); } } } \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Support/Extensions.cs b/test/Serilog.AspNetCore.Tests/Support/Extensions.cs new file mode 100644 index 0000000..54e6a1f --- /dev/null +++ b/test/Serilog.AspNetCore.Tests/Support/Extensions.cs @@ -0,0 +1,12 @@ +using Serilog.Events; + +namespace Serilog.AspNetCore.Tests.Support +{ + public static class Extensions + { + public static object LiteralValue(this LogEventPropertyValue @this) + { + return ((ScalarValue)@this).Value; + } + } +} diff --git a/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs b/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs new file mode 100644 index 0000000..92e5d09 --- /dev/null +++ b/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace Serilog.AspNetCore.Tests.Support +{ + public class SerilogWebApplicationFactory : WebApplicationFactory + { + protected override IWebHostBuilder CreateWebHostBuilder() => new WebHostBuilder().UseStartup(); + protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot("."); + } + + public class TestStartup { } +}