From 63e73f61dd9ce81452a6f5f21fc678cb9027a6cc Mon Sep 17 00:00:00 2001 From: Sergey Komisarchik Date: Sat, 13 Jun 2020 12:57:01 +0300 Subject: [PATCH] configure RequestLoggingOptions --- samples/InlineInitializationSample/Startup.cs | 9 ++ .../AspNetCore/RequestLoggingOptions.cs | 24 ++++- .../SerilogApplicationBuilderExtensions.cs | 27 ++---- .../Serilog.AspNetCore.Tests.csproj | 12 ++- .../SerilogWebHostBuilderExtensionsTests.cs | 91 ++++++++++++++++++- .../Support/Extensions.cs | 12 +++ .../Support/SerilogWebApplicationFactory.cs | 13 +++ 7 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 test/Serilog.AspNetCore.Tests/Support/Extensions.cs create mode 100644 test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs diff --git a/samples/InlineInitializationSample/Startup.cs b/samples/InlineInitializationSample/Startup.cs index 2b71adf..c8dc31e 100644 --- a/samples/InlineInitializationSample/Startup.cs +++ b/samples/InlineInitializationSample/Startup.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; +using Serilog.AspNetCore; namespace InlineInitializationSample { @@ -10,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/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs index 6a69f55..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. @@ -21,10 +21,20 @@ 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 @@ -52,13 +62,19 @@ public class RequestLoggingOptions /// public Action EnrichDiagnosticContext { get; set; } - /// /// The logger through which request completion events will be logged. The default is to use the /// static class. /// public ILogger Logger { get; set; } - internal RequestLoggingOptions() { } + /// + /// Constructor + /// + public RequestLoggingOptions() + { + GetLevel = DefaultGetLevel; + MessageTemplate = DefaultRequestCompletionMessageTemplate; + } } } 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/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj index d6e81ac..99eef06 100644 --- a/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj +++ b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj @@ -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 { } +}