15
15
using System ;
16
16
using System . Collections . Generic ;
17
17
using System . Diagnostics ;
18
+ using System . Linq ;
18
19
using System . Threading . Tasks ;
19
20
using Microsoft . AspNetCore . Http ;
20
21
using Microsoft . AspNetCore . Http . Features ;
@@ -26,18 +27,19 @@ namespace Serilog.AspNetCore
26
27
{
27
28
class RequestLoggingMiddleware
28
29
{
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
-
34
30
readonly RequestDelegate _next ;
35
31
readonly DiagnosticContext _diagnosticContext ;
32
+ readonly MessageTemplate _messageTemplate ;
33
+ readonly int _messageTemplatePlaceholderCount ;
36
34
37
- public RequestLoggingMiddleware ( RequestDelegate next , DiagnosticContext diagnosticContext )
35
+ public RequestLoggingMiddleware ( RequestDelegate next , DiagnosticContext diagnosticContext , RequestLoggingOptions options )
38
36
{
37
+ if ( options == null ) throw new ArgumentNullException ( nameof ( options ) ) ;
39
38
_next = next ?? throw new ArgumentNullException ( nameof ( next ) ) ;
40
39
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException ( nameof ( diagnosticContext ) ) ;
40
+
41
+ _messageTemplate = new MessageTemplateParser ( ) . Parse ( options . MessageTemplate ) ;
42
+ _messageTemplatePlaceholderCount = _messageTemplate . Tokens . OfType < PropertyToken > ( ) . Count ( ) ;
41
43
}
42
44
43
45
// ReSharper disable once UnusedMember.Global
@@ -46,19 +48,28 @@ public async Task Invoke(HttpContext httpContext)
46
48
if ( httpContext == null ) throw new ArgumentNullException ( nameof ( httpContext ) ) ;
47
49
48
50
var start = Stopwatch . GetTimestamp ( ) ;
51
+
49
52
var collector = _diagnosticContext . BeginCollection ( ) ;
50
53
try
51
54
{
52
55
await _next ( httpContext ) ;
53
56
var elapsedMs = GetElapsedMilliseconds ( start , Stopwatch . GetTimestamp ( ) ) ;
54
- var statusCode = httpContext . Response ? . StatusCode ;
57
+ var statusCode = httpContext . Response . StatusCode ;
55
58
LogCompletion ( httpContext , collector , statusCode , elapsedMs , null ) ;
56
59
}
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
+ }
59
70
}
60
71
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 )
62
73
{
63
74
var level = statusCode > 499 ? LogEventLevel . Error : LogEventLevel . Information ;
64
75
@@ -67,14 +78,14 @@ static bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector co
67
78
if ( ! collector . TryComplete ( out var properties ) )
68
79
properties = new List < LogEventProperty > ( ) ;
69
80
70
- properties . Capacity = properties . Count + MessageTemplatePlaceholderCount ;
81
+ properties . Capacity = properties . Count + _messageTemplatePlaceholderCount ;
71
82
72
83
// Last-in (rightly) wins...
73
84
properties . Add ( new LogEventProperty ( "RequestMethod" , new ScalarValue ( httpContext . Request . Method ) ) ) ;
74
85
properties . Add ( new LogEventProperty ( "RequestPath" , new ScalarValue ( GetPath ( httpContext ) ) ) ) ;
75
86
properties . Add ( new LogEventProperty ( "StatusCode" , new ScalarValue ( statusCode ) ) ) ;
76
87
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 ) ;
78
89
Log . Write ( evt ) ;
79
90
80
91
return false ;
0 commit comments