Skip to content

Instantly share code, notes, and snippets.

@Andreas-Hjortland
Created November 2, 2018 12:33
Show Gist options
  • Save Andreas-Hjortland/75e78e1c121bc1a35b1fab7cbdd6b773 to your computer and use it in GitHub Desktop.
Save Andreas-Hjortland/75e78e1c121bc1a35b1fab7cbdd6b773 to your computer and use it in GitHub Desktop.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
namespace DecoratingLogger
{
public delegate TScope OnLog<TScope>(ILogger logger, LogLevel logLevel, EventId eventId, dynamic state, Exception exception);
public delegate TScope OnSimpleLog<TScope>();
public static class DecoratingLoggerProviderExtensions
{
public static OnLog<TScope> ConvertOnLog<TScope>(this OnSimpleLog<TScope> decorator)
{
return (logger, logLevel, eventId, state, exception) => decorator();
}
#region LoggerProvider
/// <summary>
/// Decorate a LoggerProvider with a callback which is run on log
/// statements
/// </summary>
/// <param name="provider">The provider to decorate</param>
/// <param name="decorator">The decorator</param>
/// <returns>A new LoggerProvider which will wrap and decorate the input</returns>
public static DecoratingLoggerProvider<TScope> AddDecorator<TScope>(this ILoggerProvider provider, OnLog<TScope> decorator) => new DecoratingLoggerProvider<TScope>(provider, decorator);
public static DecoratingLoggerProvider<TScope> AddDecorator<TScope>(this ILoggerProvider provider, OnSimpleLog<TScope> decorator) => new DecoratingLoggerProvider<TScope>(provider, decorator.ConvertOnLog());
#endregion
#region LoggingBuilder
private static void ReplaceService<TScope>(ILoggingBuilder builder, ServiceDescriptor descriptor, IEnumerable<OnLog<TScope>> decorators)
{
if (descriptor.ImplementationFactory == null && descriptor.ImplementationInstance == null)
{
builder.Services.Add(new ServiceDescriptor(descriptor.ImplementationType, descriptor.ImplementationType, descriptor.Lifetime));
}
builder.Services.Add(
new ServiceDescriptor(typeof(ILoggerProvider),
serviceProvider =>
{
var impl = (ILoggerProvider)(
descriptor.ImplementationInstance ??
descriptor.ImplementationFactory?.Invoke(serviceProvider) ??
serviceProvider.GetRequiredService(descriptor.ImplementationType));
return decorators.Aggregate(
impl,
(provider, decorator) => provider.AddDecorator(decorator));
},
descriptor.Lifetime));
builder.Services.Remove(descriptor);
}
/// <summary>
/// Replaces all logger providers with a provider which decorates them
/// with a callback which is run on a log statement.
/// </summary>
/// <param name="builder"></param>
/// <param name="decorators">Callbacks to run on on a log statement</param>
/// <returns>The builder after we have replaced all the other ILoggerProviders with a decorated version</returns>
/// <remarks>
/// If you need to decorate some, but not all loggers, you can achieve
/// that by adding all loggers you want to decorate before
/// `AddDecorator`, followed by `AddDecorator` and the rest of the
/// undecorated loggers.
/// </remarks>
public static ILoggingBuilder AddDecorator<TScope>(this ILoggingBuilder builder, IEnumerable<OnLog<TScope>> decorators)
{
var loggerProviders = builder.Services
.Where(s => s.ServiceType.Equals(typeof(ILoggerProvider)))
.ToArray();
foreach(var descriptor in loggerProviders)
{
ReplaceService(builder, descriptor, decorators);
}
return builder;
}
public static ILoggingBuilder AddDecorator<TScope>(this ILoggingBuilder builder, params OnLog<TScope>[] decorators)
=> builder.AddDecorator((IEnumerable<OnLog<TScope>>)decorators);
public static ILoggingBuilder AddDecorator<TScope>(this ILoggingBuilder builder, params OnSimpleLog<TScope>[] decorators)
=> builder.AddDecorator(decorators.Select(ConvertOnLog));
#endregion
#region LoggerFactory
/// <summary>
/// Add a decorated loggerProvider to the loggerfactory.
/// </summary>
/// <param name="loggerFactory"></param>
/// <param name="baseProvider">The provider to decorate</param>
/// <param name="decorators">Callbacks to run on a log statement</param>
/// <returns>The logger factory in the input with the new decorated provider</returns>
public static ILoggerFactory AddDecorator<TScope>(this ILoggerFactory loggerFactory, ILoggerProvider baseProvider, IEnumerable<OnLog<TScope>> decorators)
{
loggerFactory.AddProvider(
decorators.Aggregate(
baseProvider,
(provider, decorator) => provider.AddDecorator(decorator)
)
);
return loggerFactory;
}
public static ILoggerFactory AddDecorator<TScope>(this ILoggerFactory loggerFactory, ILoggerProvider baseProvider, params OnLog<TScope>[] decorators)
=> loggerFactory.AddDecorator(baseProvider, (IEnumerable<OnLog<TScope>>)decorators);
public static ILoggerFactory AddDecorator<TScope>(this ILoggerFactory loggerFactory, ILoggerProvider baseProvider, params OnSimpleLog<TScope>[] decorators)
=> loggerFactory.AddDecorator(baseProvider, decorators.Select(ConvertOnLog));
#endregion
}
public class DecoratingLoggerProvider<TScope> : ILoggerProvider
{
private class DecoratingLogger : ILogger
{
private readonly ILogger _baseLogger;
private readonly OnLog<TScope> _decorator;
public DecoratingLogger(ILogger baseLogger, OnLog<TScope> decorator)
{
_baseLogger = baseLogger;
_decorator = decorator;
}
public IDisposable BeginScope<TState>(TState state) => _baseLogger.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => _baseLogger.IsEnabled(logLevel);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
using (BeginScope(_decorator(this, logLevel, eventId, state, exception)))
{
_baseLogger.Log(logLevel, eventId, state, exception, formatter);
}
}
}
private readonly ILoggerProvider _parentLoggerProvider;
private readonly OnLog<TScope> _decorator;
public DecoratingLoggerProvider(ILoggerProvider parentLoggerProvider, OnLog<TScope> decorator)
{
_parentLoggerProvider = parentLoggerProvider;
_decorator = decorator;
}
public ILogger CreateLogger(string categoryName)
{
var logger = _parentLoggerProvider.CreateLogger(categoryName);
return new DecoratingLogger(logger, _decorator);
}
public void Dispose() => _parentLoggerProvider.Dispose();
}
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
namespace DecoratingLogger
{
static class Program
{
static void Main(string[] args)
{
int count = 0;
var c = new ServiceCollection()
.AddLogging(loggingProvider =>
{
loggingProvider
.AddConsole(config =>
{
config.DisableColors = false;
config.IncludeScopes = true;
})
.AddDecorator((l, logLevel, eventId, state, exception) => {
switch(logLevel)
{
case LogLevel.Critical:
return ("Level", "Critical");
case LogLevel.Error:
return ("Level", "Error");
case LogLevel.Warning:
return ("Level", "Warning");
case LogLevel.Information:
return ("Level", "Info");
case LogLevel.Debug:
return ("Level", "Debug");
case LogLevel.Trace:
return ("Level", "Trace");
case LogLevel.None:
return ("Level", "None");
default:
return ("Level", "Unknown");
}
})
.AddDecorator(() => ("count", Interlocked.Increment(ref count)));
})
.BuildServiceProvider();
var logger = c.GetService<ILoggerFactory>().CreateLogger(nameof(Program));
using (logger.BeginScope("scope"))
{
logger.LogInformation("Foo");
}
logger.LogInformation("Bar");
Console.WriteLine("Press enter to terminate the application");
Console.ReadLine();
}
}
}
@Andreas-Hjortland
Copy link
Author

Output from the application:

Press enter to terminate the application
info: Program[0]
      => scope => (count, 1) => (Level, Info)
      Foo
info: Program[0]
      => (count, 2) => (Level, Info)
      Bar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment