Skip to content

Commit df5bf0d

Browse files
committed
Allow configuration of the completion message template
1 parent 206c627 commit df5bf0d

File tree

6 files changed

+80
-20
lines changed

6 files changed

+80
-20
lines changed

samples/EarlyInitializationSample/Controllers/HomeController.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Threading;
34
using Microsoft.AspNetCore.Mvc;
45
using EarlyInitializationSample.Models;
56
using Microsoft.Extensions.Logging;
7+
using Serilog;
68

79
namespace EarlyInitializationSample.Controllers
810
{
911
public class HomeController : Controller
1012
{
13+
static int _callCount;
14+
1115
readonly ILogger<HomeController> _logger;
16+
readonly IDiagnosticContext _diagnosticContext;
1217

13-
public HomeController(ILogger<HomeController> logger)
18+
public HomeController(ILogger<HomeController> logger, IDiagnosticContext diagnosticContext)
1419
{
1520
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
21+
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));
1622
}
1723

1824
public IActionResult Index()
1925
{
2026
_logger.LogInformation("Hello, world!");
27+
28+
_diagnosticContext.Add("IndexCallCount", Interlocked.Increment(ref _callCount));
29+
2130
return View();
2231
}
2332

2433
public IActionResult Privacy()
2534
{
26-
return View();
35+
throw new InvalidOperationException("Something went wrong.");
2736
}
2837

2938
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

samples/InlineInitializationSample/Controllers/HomeController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace InlineInitializationSample.Controllers
1010
{
1111
public class HomeController : Controller
1212
{
13-
static int _callCount = 0;
13+
static int _callCount;
1414

1515
readonly ILogger<HomeController> _logger;
1616
readonly IDiagnosticContext _diagnosticContext;
@@ -32,7 +32,7 @@ public IActionResult Index()
3232

3333
public IActionResult Privacy()
3434
{
35-
return View();
35+
throw new InvalidOperationException("Something went wrong.");
3636
}
3737

3838
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

src/Serilog.AspNetCore/AspNetCore/RequestCompletionMiddleware.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System;
1616
using System.Collections.Generic;
1717
using System.Diagnostics;
18+
using System.Linq;
1819
using System.Threading.Tasks;
1920
using Microsoft.AspNetCore.Http;
2021
using Microsoft.AspNetCore.Http.Features;
@@ -26,18 +27,19 @@ namespace Serilog.AspNetCore
2627
{
2728
class RequestLoggingMiddleware
2829
{
29-
// We may, at some future point, wish to allow customization of the template used in completion events.
30-
const int MessageTemplatePlaceholderCount = 4;
31-
static readonly MessageTemplate MessageTemplate =
32-
new MessageTemplateParser().Parse("HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms");
33-
3430
readonly RequestDelegate _next;
3531
readonly DiagnosticContext _diagnosticContext;
32+
readonly MessageTemplate _messageTemplate;
33+
readonly int _messageTemplatePlaceholderCount;
3634

37-
public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext)
35+
public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options)
3836
{
37+
if (options == null) throw new ArgumentNullException(nameof(options));
3938
_next = next ?? throw new ArgumentNullException(nameof(next));
4039
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));
40+
41+
_messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate);
42+
_messageTemplatePlaceholderCount = _messageTemplate.Tokens.OfType<PropertyToken>().Count();
4143
}
4244

4345
// ReSharper disable once UnusedMember.Global
@@ -46,19 +48,28 @@ public async Task Invoke(HttpContext httpContext)
4648
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
4749

4850
var start = Stopwatch.GetTimestamp();
51+
4952
var collector = _diagnosticContext.BeginCollection();
5053
try
5154
{
5255
await _next(httpContext);
5356
var elapsedMs = GetElapsedMilliseconds(start, Stopwatch.GetTimestamp());
54-
var statusCode = httpContext.Response?.StatusCode;
57+
var statusCode = httpContext.Response.StatusCode;
5558
LogCompletion(httpContext, collector, statusCode, elapsedMs, null);
5659
}
57-
// Never caught, because `LogCompletion()` returns false.
58-
catch (Exception ex) when (LogCompletion(httpContext, collector, 500, GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()), ex)) { }
60+
catch (Exception ex)
61+
// Never caught, because `LogCompletion()` returns false. This ensures e.g. the developer exception page is still
62+
// shown, although it does also mean we see a duplicate "unhandled exception" event from ASP.NET Core.
63+
when (LogCompletion(httpContext, collector, 500, GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()), ex))
64+
{
65+
}
66+
finally
67+
{
68+
collector.Dispose();
69+
}
5970
}
6071

61-
static bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int? statusCode, double elapsedMs, Exception ex)
72+
bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex)
6273
{
6374
var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information;
6475

@@ -67,14 +78,14 @@ static bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector co
6778
if (!collector.TryComplete(out var properties))
6879
properties = new List<LogEventProperty>();
6980

70-
properties.Capacity = properties.Count + MessageTemplatePlaceholderCount;
81+
properties.Capacity = properties.Count + _messageTemplatePlaceholderCount;
7182

7283
// Last-in (rightly) wins...
7384
properties.Add(new LogEventProperty("RequestMethod", new ScalarValue(httpContext.Request.Method)));
7485
properties.Add(new LogEventProperty("RequestPath", new ScalarValue(GetPath(httpContext))));
7586
properties.Add(new LogEventProperty("StatusCode", new ScalarValue(statusCode)));
7687
properties.Add(new LogEventProperty("Elapsed", new ScalarValue(elapsedMs)));
77-
var evt = new LogEvent(DateTimeOffset.Now, level, ex, MessageTemplate, properties);
88+
var evt = new LogEvent(DateTimeOffset.Now, level, ex, _messageTemplate, properties);
7889
Log.Write(evt);
7990

8091
return false;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2019 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
17+
namespace Serilog
18+
{
19+
class RequestLoggingOptions
20+
{
21+
public string MessageTemplate { get; }
22+
23+
public RequestLoggingOptions(string messageTemplate)
24+
{
25+
MessageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate));
26+
}
27+
}
28+
}

src/Serilog.AspNetCore/Serilog.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424

2525
<ItemGroup>
2626
<PackageReference Include="Serilog" Version="2.8.0" />
27+
<PackageReference Include="Serilog.Extensions.Hosting" Version="3.0.0-*" />
2728
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.0-*" />
2829
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0" />
2930
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
3031
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
31-
<ProjectReference Include="..\..\..\serilog-extensions-hosting\src\Serilog.Extensions.Hosting\Serilog.Extensions.Hosting.csproj" />
3232
</ItemGroup>
3333

3434
</Project>

src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017-2019 Serilog Contributors
1+
// Copyright 2019 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.
@@ -23,6 +23,9 @@ namespace Serilog
2323
/// </summary>
2424
public static class SerilogApplicationBuilderExtensions
2525
{
26+
const string DefaultRequestCompletionMessageTemplate =
27+
"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
28+
2629
/// <summary>
2730
/// Adds middleware for streamlined request logging. Instead of writing HTTP request information
2831
/// like method, path, timing, status code and exception details
@@ -31,11 +34,20 @@ public static class SerilogApplicationBuilderExtensions
3134
/// in <c>Startup.cs</c> before any handlers whose activities should be logged.
3235
/// </summary>
3336
/// <param name="app">The application builder.</param>
37+
/// <param name="messageTemplate">The message template to use when logging request completion
38+
/// events. The default is
39+
/// <c>"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"</c>. The
40+
/// template can contain any of the placeholders from the default template, names of properties
41+
/// added by ASP.NET Core, and names of properties added to the <see cref="IDiagnosticContext"/>.
42+
/// </param>
3443
/// <returns>The application builder.</returns>
35-
public static IApplicationBuilder UseSerilogRequestLogging(this IApplicationBuilder app)
44+
public static IApplicationBuilder UseSerilogRequestLogging(
45+
this IApplicationBuilder app,
46+
string messageTemplate = DefaultRequestCompletionMessageTemplate)
3647
{
3748
if (app == null) throw new ArgumentNullException(nameof(app));
38-
return app.UseMiddleware<RequestLoggingMiddleware>();
49+
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));
50+
return app.UseMiddleware<RequestLoggingMiddleware>(new RequestLoggingOptions(messageTemplate));
3951
}
4052
}
4153
}

0 commit comments

Comments
 (0)