Skip to content

Commit 206c627

Browse files
committed
UseSerilogRequestLogging()
1 parent 4c826df commit 206c627

File tree

12 files changed

+195
-6
lines changed

12 files changed

+195
-6
lines changed

samples/EarlyInitializationSample/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ public static int Main(string[] args)
2121
Log.Logger = new LoggerConfiguration()
2222
.ReadFrom.Configuration(Configuration)
2323
.Enrich.FromLogContext()
24-
.WriteTo.Console()
24+
.WriteTo.Console(
25+
// {Properties:j} added:
26+
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} " +
27+
"{Properties:j}{NewLine}{Exception}")
2528
.CreateLogger();
2629

2730
try

samples/EarlyInitializationSample/Startup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Mvc;
99
using Microsoft.Extensions.Configuration;
1010
using Microsoft.Extensions.DependencyInjection;
11+
using Serilog;
1112

1213
namespace EarlyInitializationSample
1314
{
@@ -46,6 +47,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
4647
app.UseExceptionHandler("/Home/Error");
4748
}
4849

50+
// Write streamlined request completion events, instead of the more verbose ones from the framework.
51+
// To use the default framework request logging instead, remove this line and set the "Microsoft"
52+
// level in appsettings.json to "Information".
53+
app.UseSerilogRequestLogging();
54+
4955
app.UseStaticFiles();
5056
app.UseCookiePolicy();
5157

samples/EarlyInitializationSample/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"MinimumLevel": {
44
"Default": "Information",
55
"Override": {
6-
"Microsoft": "Information",
6+
"Microsoft": "Warning",
77
"System": "Warning"
88
}
99
}

samples/InlineInitializationSample/Controllers/HomeController.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Threading;
34
using Microsoft.AspNetCore.Mvc;
45
using InlineInitializationSample.Models;
56
using Microsoft.Extensions.Logging;
7+
using Serilog;
68

79
namespace InlineInitializationSample.Controllers
810
{
911
public class HomeController : Controller
1012
{
13+
static int _callCount = 0;
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

samples/InlineInitializationSample/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
1717
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
1818
.ReadFrom.Configuration(hostingContext.Configuration)
1919
.Enrich.FromLogContext()
20-
.WriteTo.Console());
20+
.WriteTo.Console(
21+
// {Properties:j} added:
22+
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} " +
23+
"{Properties:j}{NewLine}{Exception}"));
2124
}
2225
}

samples/InlineInitializationSample/Startup.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Mvc;
99
using Microsoft.Extensions.Configuration;
1010
using Microsoft.Extensions.DependencyInjection;
11+
using Serilog;
1112

1213
namespace InlineInitializationSample
1314
{
@@ -46,6 +47,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
4647
app.UseExceptionHandler("/Home/Error");
4748
}
4849

50+
// Write streamlined request completion events, instead of the more verbose ones from the framework.
51+
// To use the default framework request logging instead, remove this line and set the "Microsoft"
52+
// level in appsettings.json to "Information".
53+
app.UseSerilogRequestLogging();
54+
4955
app.UseStaticFiles();
5056
app.UseCookiePolicy();
5157

samples/InlineInitializationSample/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"MinimumLevel": {
44
"Default": "Information",
55
"Override": {
6-
"Microsoft": "Information",
6+
"Microsoft": "Warning",
77
"System": "Warning"
88
}
99
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
using System.Collections.Generic;
17+
using System.Diagnostics;
18+
using System.Threading.Tasks;
19+
using Microsoft.AspNetCore.Http;
20+
using Microsoft.AspNetCore.Http.Features;
21+
using Serilog.Events;
22+
using Serilog.Extensions.Hosting;
23+
using Serilog.Parsing;
24+
25+
namespace Serilog.AspNetCore
26+
{
27+
class RequestLoggingMiddleware
28+
{
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+
readonly RequestDelegate _next;
35+
readonly DiagnosticContext _diagnosticContext;
36+
37+
public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext)
38+
{
39+
_next = next ?? throw new ArgumentNullException(nameof(next));
40+
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));
41+
}
42+
43+
// ReSharper disable once UnusedMember.Global
44+
public async Task Invoke(HttpContext httpContext)
45+
{
46+
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
47+
48+
var start = Stopwatch.GetTimestamp();
49+
var collector = _diagnosticContext.BeginCollection();
50+
try
51+
{
52+
await _next(httpContext);
53+
var elapsedMs = GetElapsedMilliseconds(start, Stopwatch.GetTimestamp());
54+
var statusCode = httpContext.Response?.StatusCode;
55+
LogCompletion(httpContext, collector, statusCode, elapsedMs, null);
56+
}
57+
// Never caught, because `LogCompletion()` returns false.
58+
catch (Exception ex) when (LogCompletion(httpContext, collector, 500, GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()), ex)) { }
59+
}
60+
61+
static bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int? statusCode, double elapsedMs, Exception ex)
62+
{
63+
var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information;
64+
65+
if (!Log.IsEnabled(level)) return false;
66+
67+
if (!collector.TryComplete(out var properties))
68+
properties = new List<LogEventProperty>();
69+
70+
properties.Capacity = properties.Count + MessageTemplatePlaceholderCount;
71+
72+
// Last-in (rightly) wins...
73+
properties.Add(new LogEventProperty("RequestMethod", new ScalarValue(httpContext.Request.Method)));
74+
properties.Add(new LogEventProperty("RequestPath", new ScalarValue(GetPath(httpContext))));
75+
properties.Add(new LogEventProperty("StatusCode", new ScalarValue(statusCode)));
76+
properties.Add(new LogEventProperty("Elapsed", new ScalarValue(elapsedMs)));
77+
var evt = new LogEvent(DateTimeOffset.Now, level, ex, MessageTemplate, properties);
78+
Log.Write(evt);
79+
80+
return false;
81+
}
82+
83+
static double GetElapsedMilliseconds(long start, long stop)
84+
{
85+
return (stop - start) * 1000 / (double)Stopwatch.Frequency;
86+
}
87+
88+
static string GetPath(HttpContext httpContext)
89+
{
90+
return httpContext.Features.Get<IHttpRequestFeature>()?.RawTarget ?? httpContext.Request.Path.ToString();
91+
}
92+
}
93+
}

src/Serilog.AspNetCore/AspNetCore/SerilogLoggerFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using System;
16+
using System.ComponentModel;
1617
using Microsoft.Extensions.Logging;
1718
using Serilog.Debugging;
1819
using Serilog.Extensions.Logging;
@@ -23,6 +24,7 @@ namespace Serilog.AspNetCore
2324
/// Implements <see cref="ILoggerFactory"/> so that we can inject Serilog Logger.
2425
/// </summary>
2526
[Obsolete("Replaced with Serilog.Extensions.Logging.SerilogLoggerFactory")]
27+
[EditorBrowsable(EditorBrowsableState.Never)]
2628
public class SerilogLoggerFactory : ILoggerFactory
2729
{
2830
private readonly SerilogLoggerProvider _provider;

src/Serilog.AspNetCore/Serilog.AspNetCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0" />
2929
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
3030
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
31+
<ProjectReference Include="..\..\..\serilog-extensions-hosting\src\Serilog.Extensions.Hosting\Serilog.Extensions.Hosting.csproj" />
3132
</ItemGroup>
3233

3334
</Project>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2017-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+
using Microsoft.AspNetCore.Builder;
17+
using Serilog.AspNetCore;
18+
19+
namespace Serilog
20+
{
21+
/// <summary>
22+
/// Extends <see cref="IApplicationBuilder"/> with methods for configuring Serilog features.
23+
/// </summary>
24+
public static class SerilogApplicationBuilderExtensions
25+
{
26+
/// <summary>
27+
/// Adds middleware for streamlined request logging. Instead of writing HTTP request information
28+
/// like method, path, timing, status code and exception details
29+
/// in several events, this middleware collects information during the request (including from
30+
/// <see cref="IDiagnosticContext"/>), and writes a single event at request completion. Add this
31+
/// in <c>Startup.cs</c> before any handlers whose activities should be logged.
32+
/// </summary>
33+
/// <param name="app">The application builder.</param>
34+
/// <returns>The application builder.</returns>
35+
public static IApplicationBuilder UseSerilogRequestLogging(this IApplicationBuilder app)
36+
{
37+
if (app == null) throw new ArgumentNullException(nameof(app));
38+
return app.UseMiddleware<RequestLoggingMiddleware>();
39+
}
40+
}
41+
}

src/Serilog.AspNetCore/SerilogWebHostBuilderExtensions.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 Serilog Contributors
1+
// Copyright 2017-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.
@@ -17,6 +17,7 @@
1717
using Microsoft.Extensions.Logging;
1818
using Serilog.Extensions.Logging;
1919
using Microsoft.Extensions.DependencyInjection;
20+
using Serilog.Extensions.Hosting;
2021

2122
namespace Serilog
2223
{
@@ -63,6 +64,8 @@ public static IWebHostBuilder UseSerilog(
6364
{
6465
collection.AddSingleton<ILoggerFactory>(services => new SerilogLoggerFactory(logger, dispose));
6566
}
67+
68+
ConfigureServices(collection, logger);
6669
});
6770

6871
return builder;
@@ -127,8 +130,30 @@ public static IWebHostBuilder UseSerilog(
127130

128131
return factory;
129132
});
133+
134+
ConfigureServices(collection, logger);
130135
});
131136
return builder;
132137
}
138+
139+
static void ConfigureServices(IServiceCollection collection, ILogger logger)
140+
{
141+
if (collection == null) throw new ArgumentNullException(nameof(collection));
142+
143+
if (logger != null)
144+
{
145+
// This won't (and shouldn't) take ownership of the logger.
146+
collection.AddSingleton(logger);
147+
}
148+
149+
// Registered to provide two services...
150+
var diagnosticContext = new DiagnosticContext(logger);
151+
152+
// Consumed by e.g. middleware
153+
collection.AddSingleton(diagnosticContext);
154+
155+
// Consumed by user code
156+
collection.AddSingleton<IDiagnosticContext>(diagnosticContext);
157+
}
133158
}
134159
}

0 commit comments

Comments
 (0)