Skip to content

Commit

Permalink
Merge pull request #132 from nblumhardt/add-provider
Browse files Browse the repository at this point in the history
SerilogLoggerFactory and AddProvider() infrastructure
  • Loading branch information
nblumhardt authored May 21, 2019
2 parents 31d7194 + d2155ce commit 79127bd
Show file tree
Hide file tree
Showing 17 changed files with 481 additions and 69 deletions.
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;
});

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

0 comments on commit 79127bd

Please # to comment.