Skip to content

Instantly share code, notes, and snippets.

@nblumhardt
Created June 5, 2017 22:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nblumhardt/e9477bf4c3e2acf13ae4227323cd0407 to your computer and use it in GitHub Desktop.
Save nblumhardt/e9477bf4c3e2acf13ae4227323cd0407 to your computer and use it in GitHub Desktop.
A sketch implementation of a mapped sink wrapper for Serilog
using System;
using System.Collections.Generic;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
namespace Serilog.Sinks.Map
{
class Program
{
static void Main()
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Map("SourceContext", (source, wt) => wt.RollingFile($"./logs/log-{source}-{{Date}}.txt"))
.CreateLogger();
Log.ForContext<string>().Information("Hello, from string!");
Log.ForContext<int>().Information("Hello, from int!");
Log.CloseAndFlush();
}
}
public static class MapLoggerConfigurationExtensions
{
public static LoggerConfiguration Map(
this LoggerSinkConfiguration loggerSinkConfiguration,
string keyPropertyName,
Action<string, LoggerSinkConfiguration> configure,
string defaultKey = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
return Map<string>(loggerSinkConfiguration, keyPropertyName, configure, defaultKey, restrictedToMinimumLevel, levelSwitch);
}
public static LoggerConfiguration Map<TKey>(
this LoggerSinkConfiguration loggerSinkConfiguration,
string keyPropertyName,
Action<TKey, LoggerSinkConfiguration> configure,
TKey defaultKey = default(TKey),
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
if (loggerSinkConfiguration == null) throw new ArgumentNullException(nameof(loggerSinkConfiguration));
if (keyPropertyName == null) throw new ArgumentNullException(nameof(keyPropertyName));
if (configure == null) throw new ArgumentNullException(nameof(configure));
return Map(loggerSinkConfiguration, le =>
{
if (le.Properties.TryGetValue(keyPropertyName, out var v) &&
v is ScalarValue sv &&
sv.Value is TKey key)
{
return key;
}
return defaultKey;
}, configure, restrictedToMinimumLevel, levelSwitch);
}
public static LoggerConfiguration Map<TKey>(
this LoggerSinkConfiguration loggerSinkConfiguration,
Func<LogEvent, TKey> keySelector,
Action<TKey, LoggerSinkConfiguration> configure,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
if (loggerSinkConfiguration == null) throw new ArgumentNullException(nameof(loggerSinkConfiguration));
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
if (configure == null) throw new ArgumentNullException(nameof(configure));
return loggerSinkConfiguration.Sink(new MappedSink<TKey>(keySelector, configure), restrictedToMinimumLevel, levelSwitch);
}
}
class MappedSink<TKey> : ILogEventSink, IDisposable
{
readonly Func<LogEvent, TKey> _keySelector;
readonly Action<TKey, LoggerSinkConfiguration> _configure;
readonly object _sync = new object();
readonly Dictionary<TKey, Logger> _sinkMap = new Dictionary<TKey, Logger>();
public MappedSink(Func<LogEvent, TKey> keySelector, Action<TKey, LoggerSinkConfiguration> configure)
{
_keySelector = keySelector;
_configure = configure;
}
public void Emit(LogEvent logEvent)
{
var key = _keySelector(logEvent);
Logger sink;
lock (_sync)
{
if (!_sinkMap.TryGetValue(key, out sink))
{
var config = new LoggerConfiguration()
.MinimumLevel.Is(LevelAlias.Minimum);
_configure(key, config.WriteTo);
sink = _sinkMap[key] = config.CreateLogger();
}
}
// Outside the lock to improve concurrency; this means the sink
// may throw ObjectDisposedException, which is fine.
sink.Write(logEvent);
}
public void Dispose()
{
lock (_sync)
{
var values = _sinkMap.Values;
_sinkMap.Clear();
foreach (var sink in values)
{
sink.Dispose();
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment