Skip to content

SerilogLoggerFactory and AddProvider() infrastructure #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 21, 2019
31 changes: 24 additions & 7 deletions samples/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,41 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;

namespace Sample
{
public class Program
{
public static void Main(string[] args)
{
// Creating a `LoggerProviderCollection` lets Serilog optionally write
// events through other dynamically-added MEL ILoggerProviders.
var providers = new LoggerProviderCollection();

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.LiterateConsole()
.WriteTo.Console()
.WriteTo.Providers(providers)
.CreateLogger();

var services = new ServiceCollection()
.AddLogging(builder =>
{
builder.AddSerilog();
});
var services = new ServiceCollection();

services.AddSingleton(providers);
services.AddSingleton<ILoggerFactory>(sc =>
{
var providerCollection = sc.GetService<LoggerProviderCollection>();
var factory = new SerilogLoggerFactory(null, true, providerCollection);

foreach (var provider in sc.GetServices<ILoggerProvider>())
factory.AddProvider(provider);

return factory;
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This provider registration functionality would need to be wrapped up by UseSerilog() in the two alternative hosting projects.


services.AddLogging(l => l.AddConsole());

var serviceProvider = services.BuildServiceProvider();
// getting the logger using the class's name is conventional
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();

var startTime = DateTimeOffset.UtcNow;
Expand Down Expand Up @@ -57,6 +72,8 @@ public static void Main(string[] args)
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "RESULT", "START TIME", "END TIME", "DURATION(ms)");
logger.LogInformation("{Result,-10:l}{StartTime,15:l}{EndTime,15:l}{Duration,15:l}", "------", "----- ----", "--- ----", "------------");
logger.LogInformation("{Result,-10:l}{StartTime,15:mm:s tt}{EndTime,15:mm:s tt}{Duration,15}", "SUCCESS", startTime, endTime, (endTime - startTime).TotalMilliseconds);

serviceProvider.Dispose();
}
}
}
5 changes: 3 additions & 2 deletions samples/Sample/Sample.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<AssemblyName>Sample</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Sample</PackageId>
Expand All @@ -14,7 +14,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Literate" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions serilog-extensions-logging.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=destructure/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Destructurer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=enricher/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=enrichers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonscalar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
64 changes: 64 additions & 0 deletions src/Serilog.Extensions.Logging/Extensions/Logging/LevelMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2019 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 Microsoft.Extensions.Logging;
using Serilog.Events;

namespace Serilog.Extensions.Logging
{
static class LevelMapping
{
public static LogEventLevel ToSerilogLevel(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Critical:
return LogEventLevel.Fatal;
case LogLevel.Error:
return LogEventLevel.Error;
case LogLevel.Warning:
return LogEventLevel.Warning;
case LogLevel.Information:
return LogEventLevel.Information;
case LogLevel.Debug:
return LogEventLevel.Debug;
// ReSharper disable once RedundantCaseLabel
case LogLevel.Trace:
default:
return LogEventLevel.Verbose;
}
}

public static LogLevel ToExtensionsLevel(LogEventLevel logEventLevel)
{
switch (logEventLevel)
{
case LogEventLevel.Fatal:
return LogLevel.Critical;
case LogEventLevel.Error:
return LogLevel.Error;
case LogEventLevel.Warning:
return LogLevel.Warning;
case LogEventLevel.Information:
return LogLevel.Information;
case LogEventLevel.Debug:
return LogLevel.Debug;
// ReSharper disable once RedundantCaseLabel
case LogEventLevel.Verbose:
default:
return LogLevel.Trace;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2019 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 System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;

namespace Serilog.Extensions.Logging
{
/// <summary>
/// A dynamically-modifiable collection of <see cref="ILoggerProvider"/>s.
/// </summary>
public class LoggerProviderCollection : IDisposable
{
volatile ILoggerProvider[] _providers = new ILoggerProvider[0];

/// <summary>
/// Add <paramref name="provider"/> to the collection.
/// </summary>
/// <param name="provider">A logger provider.</param>
public void AddProvider(ILoggerProvider provider)
{
if (provider == null) throw new ArgumentNullException(nameof(provider));

var existing = _providers;
var added = existing.Concat(new[] {provider}).ToArray();

#pragma warning disable 420 // ref to a volatile field
while (Interlocked.CompareExchange(ref _providers, added, existing) != existing)
#pragma warning restore 420
{
existing = _providers;
added = existing.Concat(new[] { provider }).ToArray();
}
}

/// <summary>
/// Get the currently-active providers.
/// </summary>
/// <remarks>
/// If the collection has been disposed, we'll leave the individual
/// providers with the job of throwing <see cref="ObjectDisposedException"/>.
/// </remarks>
public IEnumerable<ILoggerProvider> Providers => _providers;

/// <inheritdoc cref="IDisposable"/>
public void Dispose()
{
foreach (var provider in _providers)
provider.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2019 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 Serilog.Core;
using Serilog.Events;

namespace Serilog.Extensions.Logging
{
class LoggerProviderCollectionSink : ILogEventSink, IDisposable
{
readonly LoggerProviderCollection _providers;

public LoggerProviderCollectionSink(LoggerProviderCollection providers)
{
_providers = providers ?? throw new ArgumentNullException(nameof(providers));
}

public void Emit(LogEvent logEvent)
{
string categoryName = null;

if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) &&
sourceContextProperty is ScalarValue sourceContextValue &&
sourceContextValue.Value is string sourceContext)
{
categoryName = sourceContext;
}

var level = LevelMapping.ToExtensionsLevel(logEvent.Level);
var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties);

foreach (var provider in _providers.Providers)
{
var logger = provider.CreateLogger(categoryName);

logger.Log(
level,
default,
slv,
logEvent.Exception,
(s, e) => s.ToString());
}
}

public void Dispose()
{
_providers.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2019 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.Events;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Serilog.Extensions.Logging
{
readonly struct SerilogLogValues : IReadOnlyList<KeyValuePair<string, object>>
{
// Note, this struct is only used in a very limited context internally, so we ignore
// the possibility of fields being null via the default struct initialization.

private readonly MessageTemplate _messageTemplate;
private readonly IReadOnlyDictionary<string, LogEventPropertyValue> _properties;
private readonly KeyValuePair<string, object>[] _values;

public SerilogLogValues(MessageTemplate messageTemplate, IReadOnlyDictionary<string, LogEventPropertyValue> properties)
{
_messageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate));

// The dictionary is needed for rendering through the message template
_properties = properties ?? throw new ArgumentNullException(nameof(properties));

// The array is needed because the IReadOnlyList<T> interface expects indexed access
_values = new KeyValuePair<string, object>[_properties.Count + 1];
var i = 0;
foreach (var p in properties)
{
_values[i] = new KeyValuePair<string, object>(p.Key, (p.Value is ScalarValue sv) ? sv.Value : p.Value);
++i;
}
_values[i] = new KeyValuePair<string, object>("{OriginalFormat}", _messageTemplate.Text);
}

public KeyValuePair<string, object> this[int index]
{
get => _values[index];
}

public int Count => _properties.Count + 1;

public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => ((IEnumerable<KeyValuePair<string, object>>)_values).GetEnumerator();

public override string ToString() => _messageTemplate.Render(_properties);

IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator();
}
}
25 changes: 2 additions & 23 deletions src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public SerilogLogger(

public bool IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(ConvertLevel(logLevel));
return _logger.IsEnabled(LevelMapping.ToSerilogLevel(logLevel));
}

public IDisposable BeginScope<TState>(TState state)
Expand All @@ -49,7 +49,7 @@ public IDisposable BeginScope<TState>(TState state)

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var level = ConvertLevel(logLevel);
var level = LevelMapping.ToSerilogLevel(logLevel);
if (!_logger.IsEnabled(level))
{
return;
Expand Down Expand Up @@ -133,27 +133,6 @@ static object AsLoggableValue<TState>(TState state, Func<TState, Exception, stri
return sobj;
}

static LogEventLevel ConvertLevel(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Critical:
return LogEventLevel.Fatal;
case LogLevel.Error:
return LogEventLevel.Error;
case LogLevel.Warning:
return LogEventLevel.Warning;
case LogLevel.Information:
return LogEventLevel.Information;
case LogLevel.Debug:
return LogEventLevel.Debug;
// ReSharper disable once RedundantCaseLabel
case LogLevel.Trace:
default:
return LogEventLevel.Verbose;
}
}

static LogEventProperty CreateEventIdProperty(EventId eventId)
{
var properties = new List<LogEventProperty>(2);
Expand Down
Loading