Skip to content

Commit 1b8b9f5

Browse files
authored
Merge pull request #197 from skomis-mm/configureOptions
Allow RequestLoggingOptions configuration outside of Configure(IApplicationBuilder)
2 parents a658f0b + 63e73f6 commit 1b8b9f5

File tree

7 files changed

+161
-27
lines changed

7 files changed

+161
-27
lines changed

samples/InlineInitializationSample/Startup.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,22 @@
33
using Microsoft.Extensions.DependencyInjection;
44
using Microsoft.Extensions.Hosting;
55
using Serilog;
6+
using Serilog.AspNetCore;
67

78
namespace InlineInitializationSample
89
{
910
public class Startup
1011
{
1112
public void ConfigureServices(IServiceCollection services)
1213
{
14+
services.Configure<RequestLoggingOptions>(o =>
15+
{
16+
o.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
17+
{
18+
diagnosticContext.Set("RemoteIpAddress", httpContext.Connection.RemoteIpAddress.MapToIPv4());
19+
};
20+
});
21+
1322
services.AddControllersWithViews();
1423
}
1524

src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 Serilog Contributors
1+
// Copyright 2019-2020 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -21,10 +21,20 @@
2121
namespace Serilog.AspNetCore
2222
{
2323
/// <summary>
24-
/// Contains options for the <see cref="Serilog.AspNetCore.RequestLoggingMiddleware"/>.
24+
/// Contains options for the <see cref="RequestLoggingMiddleware"/>.
2525
/// </summary>
2626
public class RequestLoggingOptions
2727
{
28+
const string DefaultRequestCompletionMessageTemplate =
29+
"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
30+
31+
static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) =>
32+
ex != null
33+
? LogEventLevel.Error
34+
: ctx.Response.StatusCode > 499
35+
? LogEventLevel.Error
36+
: LogEventLevel.Information;
37+
2838
/// <summary>
2939
/// Gets or sets the message template. The default value is
3040
/// <c>"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"</c>. The
@@ -52,13 +62,19 @@ public class RequestLoggingOptions
5262
/// </summary>
5363
public Action<IDiagnosticContext, HttpContext> EnrichDiagnosticContext { get; set; }
5464

55-
5665
/// <summary>
5766
/// The logger through which request completion events will be logged. The default is to use the
5867
/// static <see cref="Log"/> class.
5968
/// </summary>
6069
public ILogger Logger { get; set; }
6170

62-
internal RequestLoggingOptions() { }
71+
/// <summary>
72+
/// Constructor
73+
/// </summary>
74+
public RequestLoggingOptions()
75+
{
76+
GetLevel = DefaultGetLevel;
77+
MessageTemplate = DefaultRequestCompletionMessageTemplate;
78+
}
6379
}
6480
}

src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2019 Serilog Contributors
1+
// Copyright 2019-2020 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -13,10 +13,12 @@
1313
// limitations under the License.
1414

1515
using System;
16+
1617
using Microsoft.AspNetCore.Builder;
17-
using Microsoft.AspNetCore.Http;
18+
using Microsoft.Extensions.DependencyInjection;
19+
using Microsoft.Extensions.Options;
20+
1821
using Serilog.AspNetCore;
19-
using Serilog.Events;
2022

2123
namespace Serilog
2224
{
@@ -25,16 +27,6 @@ namespace Serilog
2527
/// </summary>
2628
public static class SerilogApplicationBuilderExtensions
2729
{
28-
const string DefaultRequestCompletionMessageTemplate =
29-
"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
30-
31-
static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) =>
32-
ex != null
33-
? LogEventLevel.Error
34-
: ctx.Response.StatusCode > 499
35-
? LogEventLevel.Error
36-
: LogEventLevel.Information;
37-
3830
/// <summary>
3931
/// Adds middleware for streamlined request logging. Instead of writing HTTP request information
4032
/// like method, path, timing, status code and exception details
@@ -70,12 +62,9 @@ public static IApplicationBuilder UseSerilogRequestLogging(
7062
Action<RequestLoggingOptions> configureOptions = null)
7163
{
7264
if (app == null) throw new ArgumentNullException(nameof(app));
73-
74-
var opts = new RequestLoggingOptions
75-
{
76-
GetLevel = DefaultGetLevel,
77-
MessageTemplate = DefaultRequestCompletionMessageTemplate
78-
};
65+
66+
var opts = app.ApplicationServices.GetService<IOptions<RequestLoggingOptions>>()?.Value ?? new RequestLoggingOptions();
67+
7968
configureOptions?.Invoke(opts);
8069

8170
if (opts.MessageTemplate == null)

test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@
1414
</ItemGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
18-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
17+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2" PrivateAssets="All" />
1919
<PackageReference Include="xunit" Version="2.4.1" />
2020
</ItemGroup>
2121

22+
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
23+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.5" />
24+
</ItemGroup>
25+
26+
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
27+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.1.3" />
28+
</ItemGroup>
29+
2230
</Project>
Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,103 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
48
using Xunit;
59

10+
using Microsoft.Extensions.DependencyInjection;
11+
using Microsoft.AspNetCore.Hosting;
12+
using Microsoft.AspNetCore.Mvc.Testing;
13+
using Microsoft.AspNetCore.Builder;
14+
15+
using Serilog.Filters;
16+
using Serilog.AspNetCore.Tests.Support;
17+
618
namespace Serilog.AspNetCore.Tests
719
{
8-
public class SerilogWebHostBuilderExtensionsTests
20+
public class SerilogWebHostBuilderExtensionsTests : IClassFixture<SerilogWebApplicationFactory>
921
{
22+
SerilogWebApplicationFactory _web;
23+
24+
public SerilogWebHostBuilderExtensionsTests(SerilogWebApplicationFactory web)
25+
{
26+
_web = web;
27+
}
28+
29+
[Theory]
30+
[InlineData(true)]
31+
[InlineData(false)]
32+
public async Task DisposeShouldBeHandled(bool dispose)
33+
{
34+
var logger = new DisposeTrackingLogger();
35+
using (var web = Setup(logger, dispose))
36+
{
37+
await web.CreateClient().GetAsync("/");
38+
}
39+
40+
Assert.Equal(dispose, logger.IsDisposed);
41+
}
42+
1043
[Fact]
11-
public void Todo()
44+
public async Task RequestLoggingMiddlewareShouldEnrich()
1245
{
46+
var (sink, web) = Setup(options =>
47+
{
48+
options.EnrichDiagnosticContext += (diagnosticContext, httpContext) =>
49+
{
50+
diagnosticContext.Set("SomeInteger", 42);
51+
};
52+
});
53+
54+
await web.CreateClient().GetAsync("/resource");
55+
56+
Assert.NotEmpty(sink.Writes);
57+
58+
var completionEvent = sink.Writes.Where(logEvent => Matching.FromSource<RequestLoggingMiddleware>()(logEvent)).FirstOrDefault();
59+
60+
Assert.Equal(42, completionEvent.Properties["SomeInteger"].LiteralValue());
61+
Assert.Equal("string", completionEvent.Properties["SomeString"].LiteralValue());
62+
Assert.Equal("/resource", completionEvent.Properties["RequestPath"].LiteralValue());
63+
Assert.Equal(200, completionEvent.Properties["StatusCode"].LiteralValue());
64+
Assert.Equal("GET", completionEvent.Properties["RequestMethod"].LiteralValue());
65+
Assert.True(completionEvent.Properties.ContainsKey("Elapsed"));
66+
}
67+
68+
WebApplicationFactory<TestStartup> Setup(ILogger logger, bool dispose, Action<RequestLoggingOptions> configureOptions = null)
69+
{
70+
var web = _web.WithWebHostBuilder(
71+
builder => builder
72+
.ConfigureServices(sc => sc.Configure<RequestLoggingOptions>(options =>
73+
{
74+
options.Logger = logger;
75+
options.EnrichDiagnosticContext += (diagnosticContext, httpContext) =>
76+
{
77+
diagnosticContext.Set("SomeString", "string");
78+
};
79+
}))
80+
.Configure(app =>
81+
{
82+
app.UseSerilogRequestLogging(configureOptions);
83+
app.Run(_ => Task.CompletedTask); // 200 OK
84+
})
85+
.UseSerilog(logger, dispose));
86+
87+
return web;
88+
}
89+
90+
(SerilogSink, WebApplicationFactory<TestStartup>) Setup(Action<RequestLoggingOptions> configureOptions = null)
91+
{
92+
var sink = new SerilogSink();
93+
var logger = new LoggerConfiguration()
94+
.Enrich.FromLogContext()
95+
.WriteTo.Sink(sink)
96+
.CreateLogger();
97+
98+
var web = Setup(logger, true, configureOptions);
1399

100+
return (sink, web);
14101
}
15102
}
16103
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Serilog.Events;
2+
3+
namespace Serilog.AspNetCore.Tests.Support
4+
{
5+
public static class Extensions
6+
{
7+
public static object LiteralValue(this LogEventPropertyValue @this)
8+
{
9+
return ((ScalarValue)@this).Value;
10+
}
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.AspNetCore.Hosting;
2+
using Microsoft.AspNetCore.Mvc.Testing;
3+
4+
namespace Serilog.AspNetCore.Tests.Support
5+
{
6+
public class SerilogWebApplicationFactory : WebApplicationFactory<TestStartup>
7+
{
8+
protected override IWebHostBuilder CreateWebHostBuilder() => new WebHostBuilder().UseStartup<TestStartup>();
9+
protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot(".");
10+
}
11+
12+
public class TestStartup { }
13+
}

0 commit comments

Comments
 (0)