Skip to content

Commit

Permalink
Merge pull request #20 from nblumhardt/supply-service-provider
Browse files Browse the repository at this point in the history
Supply services when configuring Serilog inline
  • Loading branch information
nblumhardt authored May 11, 2020
2 parents 6a8e484 + df44782 commit 2baf34f
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 29 deletions.
3 changes: 1 addition & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
version: '{build}'
skip_tags: true
image: Visual Studio 2017
configuration: Release
image: Visual Studio 2019
test: off
build_script:
- ps: ./Build.ps1
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "2.2.105"
"version": "3.1.201"
}
}
2 changes: 2 additions & 0 deletions serilog-extensions-hosting.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=benaadams/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=destructure/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Enricher/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MEDI/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
28 changes: 28 additions & 0 deletions src/Serilog.Extensions.Hosting/Extensions/Hosting/NullEnricher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 Serilog.Core;
using Serilog.Events;

namespace Serilog.Extensions.Hosting
{
// Does nothing, but makes it easy to create an `ILogger` from a Serilog `Logger`
// that will not dispose the underlying pipeline when disposed itself.
class NullEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>Serilog support for .NET Core logging in hosted services</Description>
<VersionPrefix>3.0.1</VersionPrefix>
<VersionPrefix>3.1.0</VersionPrefix>
<Authors>Microsoft;Serilog Contributors</Authors>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand Down
112 changes: 87 additions & 25 deletions src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 Serilog Contributors
// 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.
Expand All @@ -13,9 +13,9 @@
// limitations under the License.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Serilog.Extensions.Hosting;
using Serilog.Extensions.Logging;

Expand All @@ -26,16 +26,30 @@ namespace Serilog
/// </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="Log.CloseAndFlush()"/> method will be
/// called on the static <see cref="Log"/> class instead.</param>
/// 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="ILoggerProvider"/>s to receive events. By
/// <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(
Expand Down Expand Up @@ -71,14 +85,15 @@ public static IHostBuilder UseSerilog(
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="LoggerConfiguration" /> that will be used to construct a <see cref="Microsoft.Extensions.Logging.Logger" />.</param>
/// <param name="preserveStaticLogger">Indicates whether to preserve the value of <see cref="Log.Logger"/>.</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>
Expand All @@ -91,35 +106,80 @@ public static IHostBuilder UseSerilog(
{
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>
/// <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));

builder.ConfigureServices((context, collection) =>
{
var loggerConfiguration = new LoggerConfiguration();

LoggerProviderCollection loggerProviders = null;
if (writeToProviders)
{
loggerProviders = new LoggerProviderCollection();
loggerConfiguration.WriteTo.Providers(loggerProviders);
}

configureLogger(context, loggerConfiguration);
var logger = loggerConfiguration.CreateLogger();

ILogger registeredLogger = null;
if (preserveStaticLogger)
collection.AddSingleton(services =>
{
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 loggerConfiguration = new LoggerConfiguration();

if (loggerProviders != null)
loggerConfiguration.WriteTo.Providers(loggerProviders);

configureLogger(context, services, loggerConfiguration);
var 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, true, loggerProviders);

if (writeToProviders)
Expand All @@ -130,9 +190,11 @@ public static IHostBuilder UseSerilog(

return factory;
});

ConfigureServices(collection, logger);

// Null is passed here because we've already (lazily) registered `ILogger`
ConfigureServices(collection, null);
});

return builder;
}

Expand Down

0 comments on commit 2baf34f

Please sign in to comment.