Sinks
Built-in sinks
ConsoleSink
Writes ANSI-colored UTF8 directly to Console.OpenStandardOutput(), bypassing Console.WriteLine and its encoding overhead.
config.AddConsoleSink();
config.AddConsoleSink(colored: false); // disable ANSI colors
Output format:
[12:34:56 DBG] Renderer: Draw call 42 completed in 1.3ms
[12:34:56 INF] Renderer: Frame rendered 100 with 50000 triangles
[12:34:56 WRN] Audio: Buffer underrun detected
[12:34:56 ERR] Network: Connection to 10.0.0.1:8080 lost
FileSink
Async-buffered file writing using Channel<T>. The calling thread enqueues a buffered copy and returns immediately. A background task flushes to disk.
config.AddFileSink("logs/app.log");
config.AddFileSink("logs/app.log", rollingInterval: RollingInterval.Daily);
config.AddFileSink("logs/app.log", rollingInterval: RollingInterval.Hourly, maxFileSizeBytes: 50_000_000);
config.AddFileSink("logs/app.log", shared: true); // multi-process safe
Rolling intervals: None, Hourly, Daily, Weekly, Monthly. Size-based rolling and time-based rolling can be combined. In shared mode (shared: true), the file is opened with FileShare.ReadWrite for safe concurrent appends from multiple processes.
StreamSink
Writes to any Stream via async-buffered Channel<T>. Useful for network streams, memory streams, or Console.OpenStandardOutput().
config.AddStreamSink(networkStream, leaveOpen: true);
When leaveOpen is true, the stream is flushed but not disposed when the sink is disposed.
DebugSink
Writes to System.Diagnostics.Debug, which routes to the IDE output window. Useful during development. Automatically stripped from release builds by the runtime.
config.AddDebugSink();
RecordingSink
Captures log entries to an in-memory list for test assertions. See Testing.
var sink = new RecordingSink();
config.AddSink(sink);
NullSink
Discards all output. Useful for benchmarking the logging pipeline itself or for disabling logging without removing call sites.
config.AddNullSink();
Sink filtering by category
Any sink can be restricted to specific categories:
config.AddFileSink("logs/render.log", category: "Renderer");
config.AddFileSink("logs/network.log", category: "Network");
config.AddConsoleSink(); // receives everything
Custom sinks
Implement ILogSink for text output or IStructuredLogSink for structured property access:
public sealed class CustomSink : ILogSink
{
public bool IsEnabled(LogLevel level) => level >= LogLevel.Warning;
public void Write(in LogEntry entry, ReadOnlySpan<byte> utf8Message)
{
// Write the formatted message to your target
}
public void Dispose() { }
}
Register at initialization:
Log.Initialize(config =>
{
config.AddSink(new CustomSink());
});
Sink base classes
TextLogSink and BufferedLogSink provide common patterns. BufferedLogSink uses a Channel<T>-based async queue so the calling thread never blocks on I/O:
public sealed class MyNetworkSink : BufferedLogSink
{
protected override void Flush(in LogEntry entry, ReadOnlySpan<byte> utf8Message)
{
// Called on background thread, safe to do I/O
}
}