My job scheduler runs jobs, each of which has its own logger. If a job succeeds then its accumulated log events are logged, whereas if it fails then I discard the job (and its log events).
This works using hacky custom MEL logging, but I'm struggling to convert to Serilog. I'm using console, postgres and seq sinks.
This is similar to audit logging; but whereas there one commits a transaction after logs are flushed, here one prevents logging until some point in the future. So it's almost the reverse.
Summary:
_logger.Debug("Foo {Bar}", 42)
I investigated a custom ILogger, async sink wrappers and batching sink wrappers - but am unsure which way to go.
Any pointers?
I figured it out: a custom logger which caches log events until requested to emit them.
ICachingLogger`T.cs
public interface ICachingLogger<T> : ILogger where T : class
{
void Flush();
}
CachingLogger`T.cs
using System.Collections.Concurrent;
using Serilog;
using Serilog.Events;
public sealed class CachingLogger<T> : ICachingLogger<T>
{
private readonly ConcurrentQueue<LogEvent> _cache = new();
private readonly ILogger _proxiedLogger;
public CachingLogger(ILogger proxiedLogger) =>
_proxiedLogger = proxiedLogger?.ForContext<T>() ?? throw new ArgumentNullException(nameof(proxiedLogger));
public void Write(LogEvent logEvent) // implements "ILogger.Write(LogEvent)"
{
ArgumentNullException.ThrowIfNull(logEvent, nameof(logEvent));
//_proxiedLogger.Write(logEvent); // do not emit
_cache.Enqueue(logEvent);
}
public void Flush()
{
// maybe lock cache from further enqueueing?
while (_cache.TryDequeue(out var logEvent))
_proxiedLogger.Write(logEvent);
}
}
Program.cs
builder.Services.AddTransient(typeof(ICachingLogger<>), typeof(CachingLogger<>));
Consumer.cs
public class Consumer
{
private readonly ICachingLogger<Consumer> _logger;
public Scraper(ICachingLogger<Consumer> logger) =>
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
public void DoStuff() {
// use logger in normal way
_logger
.ForContext("Foo", "Bar")
.Information("Hello {What}", "World!");
}
public void FlushLogs() {
// when required, emit all cached log events
_logger.Flush();
}
}
Seems to work for me, so far.