Skip to content

Commit c59e75e

Browse files
committed
feat: add more extensions for ILoggingBuilder
1 parent 4d786ff commit c59e75e

File tree

4 files changed

+231
-9
lines changed

4 files changed

+231
-9
lines changed

src/Serilog.Extensions.Hosting/SerilogLoggingBuilderExtensions.cs

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Serilog;
4+
using Serilog.Extensions.Hosting;
25
using Serilog.Extensions.Logging;
36
using System;
47
using static Serilog.SerilogHostBuilderExtensions;
@@ -21,7 +24,7 @@ public static class SerilogLoggingBuilderExtensions
2124
/// <param name="providers">A <see cref="LoggerProviderCollection"/> registered in the Serilog pipeline using the
2225
/// <c>WriteTo.Providers()</c> configuration method, enabling other <see cref="Microsoft.Extensions.Logging.ILoggerProvider"/>s to receive events. By
2326
/// default, only Serilog sinks will receive events.</param>
24-
/// <returns>The host builder.</returns>
27+
/// <returns>The logging builder.</returns>
2528
public static ILoggingBuilder AddSerilog(
2629
this ILoggingBuilder builder,
2730
Serilog.ILogger logger = null,
@@ -61,5 +64,180 @@ public static ILoggingBuilder AddSerilog(
6164

6265
return builder;
6366
}
67+
68+
/// <summary>Sets Serilog as the logging provider.</summary>
69+
/// <remarks>
70+
/// A <see cref="ILoggingBuilder"/> is supplied so that configuration and hosting information can be used.
71+
/// The logger will be shut down when application services are disposed.
72+
/// </remarks>
73+
/// <param name="builder">The logging builder to configure.</param>
74+
/// <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>
75+
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Serilog.Log.Logger"/>.</param>
76+
/// <param name="writeToProviders">By default, Serilog does not write events to <see cref="ILoggerProvider"/>s registered through
77+
/// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify
78+
/// <c>true</c> to write events to all providers.</param>
79+
/// <remarks>If the static <see cref="Log.Logger"/> is a bootstrap logger (see
80+
/// <c>LoggerConfigurationExtensions.CreateBootstrapLogger()</c>), and <paramref name="preserveStaticLogger"/> is
81+
/// not specified, the the bootstrap logger will be reconfigured through the supplied delegate, rather than being
82+
/// replaced entirely or ignored.</remarks>
83+
/// <returns>The logging builder.</returns>
84+
public static ILoggingBuilder AddSerilogFactory(
85+
this ILoggingBuilder builder,
86+
Action<IServiceProvider, LoggerConfiguration> configureLogger,
87+
bool preserveStaticLogger = false,
88+
bool writeToProviders = false)
89+
{
90+
if (builder == null) throw new ArgumentNullException(nameof(builder));
91+
if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger));
92+
93+
// This check is eager; replacing the bootstrap logger after calling this method is not supported.
94+
#if !NO_RELOADABLE_LOGGER
95+
var reloadable = Log.Logger as ReloadableLogger;
96+
var useReload = reloadable != null && !preserveStaticLogger;
97+
#else
98+
const bool useReload = false;
99+
#endif
100+
var services = builder.Services;
101+
102+
LoggerProviderCollection loggerProviders = null;
103+
if (writeToProviders)
104+
{
105+
loggerProviders = new LoggerProviderCollection();
106+
}
107+
108+
services.AddSingleton(sp =>
109+
{
110+
Serilog.ILogger logger;
111+
#if !NO_RELOADABLE_LOGGER
112+
if (useReload)
113+
{
114+
reloadable!.Reload(cfg =>
115+
{
116+
if (loggerProviders != null)
117+
cfg.WriteTo.Providers(loggerProviders);
118+
119+
configureLogger(sp, cfg);
120+
return cfg;
121+
});
122+
123+
logger = reloadable.Freeze();
124+
}
125+
else
126+
#endif
127+
{
128+
var loggerConfiguration = new LoggerConfiguration();
129+
130+
if (loggerProviders != null)
131+
loggerConfiguration.WriteTo.Providers(loggerProviders);
132+
133+
configureLogger(sp, loggerConfiguration);
134+
logger = loggerConfiguration.CreateLogger();
135+
}
136+
137+
return new RegisteredLogger(logger);
138+
});
139+
140+
services.AddSingleton(sp =>
141+
{
142+
// How can we register the logger, here, but not have MEDI dispose it?
143+
// Using the `NullEnricher` hack to prevent disposal.
144+
var logger = sp.GetRequiredService<RegisteredLogger>().Logger;
145+
return logger.ForContext(new NullEnricher());
146+
});
147+
148+
services.AddSingleton<ILoggerFactory>(sp =>
149+
{
150+
var logger = sp.GetRequiredService<RegisteredLogger>().Logger;
151+
152+
Serilog.ILogger registeredLogger = null;
153+
if (preserveStaticLogger)
154+
{
155+
registeredLogger = logger;
156+
}
157+
else
158+
{
159+
// Passing a `null` logger to `SerilogLoggerFactory` results in disposal via
160+
// `Log.CloseAndFlush()`, which additionally replaces the static logger with a no-op.
161+
Log.Logger = logger;
162+
}
163+
164+
var factory = new SerilogLoggerFactory(registeredLogger, !useReload, loggerProviders);
165+
166+
if (writeToProviders)
167+
{
168+
foreach (var provider in sp.GetServices<ILoggerProvider>())
169+
factory.AddProvider(provider);
170+
}
171+
172+
return factory;
173+
});
174+
175+
ConfigureDiagnosticContext(services, preserveStaticLogger);
176+
177+
return builder;
178+
}
179+
180+
/// <summary>Sets Serilog as the logging provider.</summary>
181+
/// <remarks>
182+
/// A <see cref="ILoggingBuilder"/> is supplied so that configuration and hosting information can be used.
183+
/// The logger will be shut down when application services are disposed.
184+
/// </remarks>
185+
/// <param name="builder">The logging builder to configure.</param>
186+
/// <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>
187+
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Serilog.Log.Logger"/>.</param>
188+
/// <param name="writeToProviders">By default, Serilog does not write events to <see cref="ILoggerProvider"/>s registered through
189+
/// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify
190+
/// <c>true</c> to write events to all providers.</param>
191+
/// <returns>The logging builder.</returns>
192+
public static ILoggingBuilder AddSerilog(
193+
this ILoggingBuilder builder,
194+
Action<IConfiguration, LoggerConfiguration> configureLogger,
195+
bool preserveStaticLogger = false,
196+
bool writeToProviders = false)
197+
{
198+
if (builder == null) throw new ArgumentNullException(nameof(builder));
199+
if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger));
200+
201+
return AddSerilogFactory(
202+
builder,
203+
(sp, loggerConfiguration) =>
204+
{
205+
var configuration = sp.GetRequiredService<IConfiguration>();
206+
configureLogger(configuration, loggerConfiguration);
207+
},
208+
preserveStaticLogger: preserveStaticLogger,
209+
writeToProviders: writeToProviders);
210+
}
211+
212+
/// <summary>Sets Serilog as the logging provider.</summary>
213+
/// <remarks>
214+
/// A <see cref="ILoggingBuilder"/> is supplied so that configuration and hosting information can be used.
215+
/// The logger will be shut down when application services are disposed.
216+
/// </remarks>
217+
/// <param name="builder">The logging builder to configure.</param>
218+
/// <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>
219+
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Serilog.Log.Logger"/>.</param>
220+
/// <param name="writeToProviders">By default, Serilog does not write events to <see cref="ILoggerProvider"/>s registered through
221+
/// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify
222+
/// <c>true</c> to write events to all providers.</param>
223+
/// <returns>The logging builder.</returns>
224+
public static ILoggingBuilder AddSerilog(
225+
this ILoggingBuilder builder,
226+
Action<LoggerConfiguration> configureLogger,
227+
bool preserveStaticLogger = false,
228+
bool writeToProviders = false)
229+
{
230+
if (builder == null) throw new ArgumentNullException(nameof(builder));
231+
if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger));
232+
233+
return AddSerilogFactory(
234+
builder,
235+
(sp, loggerConfiguration) =>
236+
{
237+
configureLogger(loggerConfiguration);
238+
},
239+
preserveStaticLogger: preserveStaticLogger,
240+
writeToProviders: writeToProviders);
241+
}
64242
}
65243
}

test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414
<PropertyGroup Condition=" '$(TargetFramework)' == 'net4.8' ">
1515
<DefineConstants>$(DefineConstants);NO_RELOADABLE_LOGGER</DefineConstants>
1616
</PropertyGroup>
17+
18+
<ItemGroup>
19+
<None Remove="appsettings.json" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Content Include="appsettings.json">
24+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
25+
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
26+
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
27+
</Content>
28+
</ItemGroup>
1729

1830
<ItemGroup>
1931
<ProjectReference Include="..\..\src\Serilog.Extensions.Hosting\Serilog.Extensions.Hosting.csproj" />
@@ -29,6 +41,7 @@
2941
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
3042
<PackageReference Include="Serilog.Sinks.InMemory" Version="0.11.0" />
3143
<PackageReference Include="Serilog.Sinks.InMemory.Assertions" Version="0.11.0" />
44+
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
3245
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
3346
<PrivateAssets>all</PrivateAssets>
3447
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

test/Serilog.Extensions.Hosting.Tests/SerilogLoggingBuilderExtensionsTests.cs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,35 @@ namespace Serilog.Extensions.Hosting.Tests;
1010
public class SerilogLoggingBuilderExtensionsTests
1111
{
1212
[Fact]
13-
public async Task SerilogLoggingBuilderExtensions_AddSerilog_SuccessAsync()
13+
public async Task LoggingBuilderExtensions_AddSerilog_SuccessAsync()
1414
{
1515
// Arrange
1616
var builder = WebApplication.CreateBuilder();
17-
var logger = new LoggerConfiguration()
18-
.WriteTo.InMemory()
19-
.CreateLogger();
20-
builder.Logging.AddSerilog(logger);
17+
builder.Logging.AddSerilog(logger => logger.WriteTo.InMemory());
2118
builder.WebHost.UseTestServer();
2219
var app = builder.Build();
2320

2421
// Act
25-
var message = "Hello World!";
26-
app.Logger.LogInformation("Hello World!");
22+
var message = "Logging in memory";
23+
app.Logger.LogInformation(message);
24+
await app.StartAsync();
25+
26+
// Assert
27+
InMemorySink.Instance.Should().HaveMessage(message);
28+
}
29+
30+
[Fact]
31+
public async Task LoggingBuilderExtensions_AddSerilogWithAppsettings_SuccessAsync()
32+
{
33+
// Arrange
34+
var builder = WebApplication.CreateBuilder();
35+
builder.Logging.AddSerilog((appConfig, loggerConfig) => loggerConfig.ReadFrom.Configuration(appConfig));
36+
builder.WebHost.UseTestServer();
37+
var app = builder.Build();
38+
39+
// Act
40+
var message = "Logging in memory with appsettings.json";
41+
app.Logger.LogInformation(message);
2742
await app.StartAsync();
2843

2944
// Assert
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Serilog": {
3+
"MinimumLevel": {
4+
"Default": "Information",
5+
"Override": {
6+
"Microsoft": "Warning",
7+
"Microsoft.Hosting.Lifetime": "Information"
8+
}
9+
},
10+
"WriteTo": [
11+
{
12+
"Name": "InMemory"
13+
}
14+
]
15+
}
16+
}

0 commit comments

Comments
 (0)