/
SerilogHostBuilderExtensions.cs
246 lines (214 loc) · 11.6 KB
/
SerilogHostBuilderExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// Copyright 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog.Extensions.Hosting;
using Serilog.Extensions.Logging;
// ReSharper disable MemberCanBePrivate.Global
namespace Serilog
{
/// <summary>
/// Extends <see cref="IHostBuilder"/> with Serilog configuration methods.
/// </summary>
public static class SerilogHostBuilderExtensions
{
// Used internally to pass information through the container. We need to do this because if `logger` is the
// root logger, registering it as a singleton may lead to disposal along with the container by MEDI. This isn't
// always desirable, i.e. we may be handed a logger and `dispose: false`, so wrapping it keeps us in control
// of when the logger is disposed.
class RegisteredLogger
{
public RegisteredLogger(ILogger logger)
{
Logger = logger;
}
public ILogger Logger { get; }
}
/// <summary>
/// Sets Serilog as the logging provider.
/// </summary>
/// <param name="builder">The host builder to configure.</param>
/// <param name="logger">The Serilog logger; if not supplied, the static <see cref="Serilog.Log"/> will be used.</param>
/// <param name="dispose">When <c>true</c>, dispose <paramref name="logger"/> when the framework disposes the provider. If the
/// logger is not specified but <paramref name="dispose"/> is <c>true</c>, the <see cref="Serilog.Log.CloseAndFlush()"/> method will be
/// called on the static <see cref="Serilog.Log"/> class instead.</param>
/// <param name="providers">A <see cref="LoggerProviderCollection"/> registered in the Serilog pipeline using the
/// <c>WriteTo.Providers()</c> configuration method, enabling other <see cref="Microsoft.Extensions.Logging.ILoggerProvider"/>s to receive events. By
/// default, only Serilog sinks will receive events.</param>
/// <returns>The host builder.</returns>
public static IHostBuilder UseSerilog(
this IHostBuilder builder,
ILogger logger = null,
bool dispose = false,
LoggerProviderCollection providers = null)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
builder.ConfigureServices((_, collection) =>
{
if (providers != null)
{
collection.AddSingleton<ILoggerFactory>(services =>
{
var factory = new SerilogLoggerFactory(logger, dispose, providers);
foreach (var provider in services.GetServices<ILoggerProvider>())
factory.AddProvider(provider);
return factory;
});
}
else
{
collection.AddSingleton<ILoggerFactory>(services => new SerilogLoggerFactory(logger, dispose));
}
ConfigureServices(collection, logger);
});
return builder;
}
/// <summary>Sets Serilog as the logging provider.</summary>
/// <remarks>
/// A <see cref="HostBuilderContext"/> is supplied so that configuration and hosting information can be used.
/// The logger will be shut down when application services are disposed.
/// </remarks>
/// <param name="builder">The host builder to configure.</param>
/// <param name="configureLogger">The delegate for configuring the <see cref="Serilog.LoggerConfiguration" /> that will be used to construct a <see cref="Serilog.Core.Logger" />.</param>
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Serilog.Log.Logger"/>.</param>
/// <param name="writeToProviders">By default, Serilog does not write events to <see cref="ILoggerProvider"/>s registered through
/// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify
/// <c>true</c> to write events to all providers.</param>
/// <returns>The host builder.</returns>
public static IHostBuilder UseSerilog(
this IHostBuilder builder,
Action<HostBuilderContext, LoggerConfiguration> configureLogger,
bool preserveStaticLogger = false,
bool writeToProviders = false)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger));
return UseSerilog(
builder,
(hostBuilderContext, services, loggerConfiguration) =>
configureLogger(hostBuilderContext, loggerConfiguration),
preserveStaticLogger: preserveStaticLogger,
writeToProviders: writeToProviders);
}
/// <summary>Sets Serilog as the logging provider.</summary>
/// <remarks>
/// A <see cref="HostBuilderContext"/> is supplied so that configuration and hosting information can be used.
/// The logger will be shut down when application services are disposed.
/// </remarks>
/// <param name="builder">The host builder to configure.</param>
/// <param name="configureLogger">The delegate for configuring the <see cref="Serilog.LoggerConfiguration" /> that will be used to construct a <see cref="Serilog.Core.Logger" />.</param>
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Serilog.Log.Logger"/>.</param>
/// <param name="writeToProviders">By default, Serilog does not write events to <see cref="ILoggerProvider"/>s registered through
/// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify
/// <c>true</c> to write events to all providers.</param>
/// <remarks>If the static <see cref="Log.Logger"/> is a bootstrap logger (created using
/// <see cref="LoggerConfigurationExtensions.CreateBootstrapLogger"/>), and <paramref name="preserveStaticLogger"/> is
/// not specified, the the bootstrap logger will be reconfigured through the supplied delegate, rather than being
/// replaced entirely or ignored.</remarks>
/// <returns>The host builder.</returns>
public static IHostBuilder UseSerilog(
this IHostBuilder builder,
Action<HostBuilderContext, IServiceProvider, LoggerConfiguration> configureLogger,
bool preserveStaticLogger = false,
bool writeToProviders = false)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger));
// This check is eager; replacing the bootstrap logger after calling this method is not supported.
var reloadable = Log.Logger as ReloadableLogger;
var useReload = reloadable != null && !preserveStaticLogger;
builder.ConfigureServices((context, collection) =>
{
LoggerProviderCollection loggerProviders = null;
if (writeToProviders)
{
loggerProviders = new LoggerProviderCollection();
}
collection.AddSingleton(services =>
{
ILogger logger;
if (useReload)
{
reloadable!.Reload(cfg =>
{
if (loggerProviders != null)
cfg.WriteTo.Providers(loggerProviders);
configureLogger(context, services, cfg);
return cfg;
});
logger = reloadable.Freeze();
}
else
{
var loggerConfiguration = new LoggerConfiguration();
if (loggerProviders != null)
loggerConfiguration.WriteTo.Providers(loggerProviders);
configureLogger(context, services, loggerConfiguration);
logger = loggerConfiguration.CreateLogger();
}
return new RegisteredLogger(logger);
});
collection.AddSingleton(services =>
{
// How can we register the logger, here, but not have MEDI dispose it?
// Using the `NullEnricher` hack to prevent disposal.
var logger = services.GetRequiredService<RegisteredLogger>().Logger;
return logger.ForContext(new NullEnricher());
});
collection.AddSingleton<ILoggerFactory>(services =>
{
var logger = services.GetRequiredService<RegisteredLogger>().Logger;
ILogger registeredLogger = null;
if (preserveStaticLogger)
{
registeredLogger = logger;
}
else
{
// Passing a `null` logger to `SerilogLoggerFactory` results in disposal via
// `Log.CloseAndFlush()`, which additionally replaces the static logger with a no-op.
Log.Logger = logger;
}
var factory = new SerilogLoggerFactory(registeredLogger, !useReload, loggerProviders);
if (writeToProviders)
{
foreach (var provider in services.GetServices<ILoggerProvider>())
factory.AddProvider(provider);
}
return factory;
});
// Null is passed here because we've already (lazily) registered `ILogger`
ConfigureServices(collection, null);
});
return builder;
}
static void ConfigureServices(IServiceCollection collection, ILogger logger)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
if (logger != null)
{
// This won't (and shouldn't) take ownership of the logger.
collection.AddSingleton(logger);
}
// Registered to provide two services...
var diagnosticContext = new DiagnosticContext(logger);
// Consumed by e.g. middleware
collection.AddSingleton(diagnosticContext);
// Consumed by user code
collection.AddSingleton<IDiagnosticContext>(diagnosticContext);
}
}
}