Skip to content

Instantly share code, notes, and snippets.

@Erwinvandervalk
Created November 19, 2021 08:22
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 Erwinvandervalk/89fda5ba57f10ac3a5d22f26a5c42ca8 to your computer and use it in GitHub Desktop.
Save Erwinvandervalk/89fda5ba57f10ac3a5d22f26a5c42ca8 to your computer and use it in GitHub Desktop.
Serilog TestLoggerProvider
public class ElapsedEnricher : ILogEventEnricher
{
public const string PropertyName = "Elapsed";
private readonly Stopwatch _stopwatch;
public ElapsedEnricher()
{
_stopwatch = Stopwatch.StartNew();
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var value = (int)Math.Floor(_stopwatch.Elapsed.TotalSeconds) + "." + _stopwatch.Elapsed.Milliseconds;
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(PropertyName,
value));
}
}
/// <summary>
/// Adds a formatted version of an exception as a property, which then can easily be pretty-printed.
/// </summary>
/// <seealso cref="Serilog.Core.ILogEventEnricher" />
public class ExceptionFormattingEnricher : ILogEventEnricher
{
public const string EnrichedExceptionPropertyName = "_detailedException";
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent.Exception != null)
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(EnrichedExceptionPropertyName,
FormatException(logEvent.Exception)));
}
private static string FormatData(IDictionary dict, string indent)
{
if (dict == null) return string.Empty;
indent = indent + " ";
var retVal = "";
foreach (var val in dict.Keys) retVal += $"{Environment.NewLine}{indent}'{val}' : '{dict[val]}'";
return retVal;
}
public static string FormatException(Exception arg)
{
var builder = new StringBuilder();
FormatException(arg, builder);
return builder.ToString();
}
private static void FormatException(Exception exception, StringBuilder logs, int tabs = 4)
{
if (exception == null) return;
var indent = new string(' ', tabs);
logs.AppendLine().AppendLine($"{indent}{exception.GetType()?.Name}:")
.AppendLine($"{indent}Message : '{exception.Message}");
if (exception.Data.Count > 0) logs.AppendLine($"{indent}Data : {FormatData(exception.Data, indent)}");
logs.AppendLine(
$"{indent}StackTrace : '{Environment.NewLine}{indent} {exception.StackTrace?.Replace(Environment.NewLine, Environment.NewLine + indent + " ")}")
.AppendLine($"{indent}TargetSite : '{exception.TargetSite}");
if (exception.InnerException != null) logs.AppendLine($"{indent}InnerEx : ");
FormatException(exception.InnerException, logs, tabs + 4);
}
}
/// <summary>
/// Writes the log messages through the serilog extensions
/// </summary>
public class TestLoggerProvider : IDisposable, ILoggerProvider
{
public static readonly string ApplicationNameProperty = "ApplicationName";
/// <summary>
/// This message template is denser and contains the enriched exceptions
/// </summary>
public static readonly string ImprovedLoggingTemplate =
"{" + ElapsedEnricher.PropertyName + "} [{Level:u3}]{" + ApplicationNameProperty + "} "
+ "{Message} {SpanId} {ParentId} {TraceId} - {sub} {NewLine}{" +
ExceptionFormattingEnricher.EnrichedExceptionPropertyName + "}";
/// <summary>
/// Use the default serilog message template if you are not happy with the improved one.
/// </summary>
public static readonly string DefaultSerilogMessageTemplate =
"{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}";
private readonly Logger _serilog;
public readonly LoggerFactory LoggerFactory;
private readonly TextWriter logs = new StringWriter();
public readonly Serilog.ILogger Serilog;
public TestLoggerProvider(ITestOutputHelper output,
string applicationName = null,
string outputTemplate = null,
LogEventLevel minimumLogLevel = LogEventLevel.Verbose,
Func<LoggerConfiguration, LoggerConfiguration> configure = null
)
{
var loggerConfiguration = new LoggerConfiguration()
.Enrich.With(new ElapsedEnricher())
.Enrich.With(new ExceptionFormattingEnricher())
.Enrich.FromLogContext().MinimumLevel.Is(minimumLogLevel)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
;
if (configure != null)
{
loggerConfiguration = configure.Invoke(loggerConfiguration);
}
if (applicationName != null)
{
loggerConfiguration.Enrich.WithProperty(ApplicationNameProperty,
" " + applicationName + " -");
}
_serilog = loggerConfiguration.WriteTo.TextWriter(logs, minimumLogLevel, ImprovedLoggingTemplate).WriteTo
.TestOutput(output, minimumLogLevel, ImprovedLoggingTemplate).CreateLogger();
Serilog = _serilog;
LoggerFactory = new LoggerFactory();
LoggerFactory.AddProvider(new SerilogLoggerProvider(Serilog));
}
public string Logs => logs.ToString();
public void Dispose()
{
_serilog.Dispose();
LoggerFactory?.Dispose();
logs?.Dispose();
}
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
{
return LoggerFactory.CreateLogger(categoryName);
}
public ILogger<T> BuildLogger<T>()
{
return LoggerFactory.CreateLogger<T>();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment