Skip to content

Sink API ​

The Sink struct represents a destination for log messages. Sinks can write to console, files, or custom outputs with individual configuration options.

Quick Reference: Method Aliases ​

Full MethodAlias(es)Description
init()create()Initialize sink
deinit()destroy(), close()Deinitialize sink
writeWithAllocator()writeWithAlloc()Write with custom allocator
setWriteCallback()onWrite()Set write callback
setFlushCallback()onFlush()Set flush callback
setErrorCallback()onError()Set error callback
setRotationCallback()onRotation()Set rotation callback
setStateChangeCallback()onStateChange()Set state change callback
getStats()statistics(), stats_()Get sink statistics
clearBuffer()clear()Clear write buffer
flush()sync()Flush buffered data
isEnabled()is_enabled()Check if sink is enabled
isAsyncEnabled()asyncEnabled()Check if async writing is enabled
flushNow()flushImmediate()Force immediate flush
getName()name()Get sink name

SinkConfig ​

Configuration for a sink.

Core Fields ​

FieldTypeDefaultDescription
path?[]const u8nullPath to log file (null for console). Supports dynamic placeholders like {date}, {time}. Also supports network schemes tcp://host:port and udp://host:port.
name?[]const u8nullSink identifier for metrics and debugging
enabledbooltrueEnable/disable sink initially
eventLogboolfalseEnable system event log output (Windows Event Log on Windows, Syslog on POSIX). See the Windows Event Log mapping below; constants are provided by Constants.EventLogConstants.
tamperEvidentboolfalseEnable cryptographic SHA-256 chaining to produce tamper-evident log records.
mmapboolfalseEnable virtual memory-mapped zero-copy file logging for microsecond-latency writes.
io?std.IonullExplicit I/O handle for this sink. If null, inherits the logger's configured I/O handle or falls back to logly.defaultIo().

Windows Event Log (Windows only) ​

When eventLog is enabled on Windows, log entries are sent to the Windows Event Viewer using the ReportEventA API. Logly maps internal log Level values to Windows event types as follows:

  • Error / Critical / Fail / Fatal -> eventlogErrorType (Constants.EventLogConstants.errorType)
  • Warning -> eventlogWarningType (Constants.EventLogConstants.warningType)
  • Notice / Info / Success -> eventlogInformationType (Constants.EventLogConstants.informationType)

eventlogSuccess (Constants.EventLogConstants.success) may be used for success/audit events.

These values are centralized in src/constants.zig under EventLogConstants and are reused by the sink implementation to ensure consistency across platforms.

Dynamic Path Formatting ​

The path field supports dynamic placeholders that are resolved when the sink is initialized:

  • {date}: YYYY-MM-DD
  • {time}: HH-mm-ss
  • {YYYY}, {MM}, {DD}, {HH}, {mm}, {ss}: Custom date/time components.

Level Filtering ​

FieldTypeDefaultDescription
level?LevelnullMinimum log level for this sink
maxLevel?LevelnullMaximum log level (creates level range)

Output Formatting ​

FieldTypeDefaultDescription
format?Config.FormatnullOutput format for this sink (null inherits the logger-wide Config.format). One sink renders exactly one format: .text, .json, .ndjson, .logfmt, .syslog, .syslog3164, .msgpack.
prettyJsonboolfalsePretty print JSON with indentation
color?boolnullEnable/disable colors (null = auto-detect)
logFormat?[]const u8nullCustom log format string
timeFormat?[]const u8nullCustom time format for this sink
theme?Formatter.ThemenullCustom color theme for this sink

Event-log sinks (eventLog = true) only carry text-compatible formats. Selecting .msgpack, .syslog, or .syslog3164 on an event-log sink fails at creation with error.UnsupportedFormatForSink (binary cannot survive NUL-terminated transports; syslog framing would be doubled by the daemon), as does inheriting one of those logger-wide formats via Logger.addSink.

JSON-array (.json) file sinks keep every file a valid standalone document: the opening bracket is written at creation, an existing file continues its array when it ends with the array tail (anything else is rejected with error.JsonArrayAppendUnsupported instead of being corrupted), rotation closes the archived document and opens a fresh one (memory-mapped files are remapped onto the fresh handle), and shutdown closes the active document. Use .ndjson for append workflows that must never look back.

Field Inclusion ​

FieldTypeDefaultDescription
includeTimestampbooltrueInclude timestamp in output
includeLevelbooltrueInclude log level in output
includeSourceboolfalseInclude source location
includeTraceIdboolfalseInclude trace IDs (distributed tracing)

File Write Mode ​

FieldTypeDefaultDescription
overwriteModeboolfalseLegacy field: false = append (default), true = truncate file on startup. Prefer writeMode.
writeModeWriteMode.appendFile write mode. See WriteMode enum below.
fileMode?u32nullFile permissions for created log files (Unix only).

WriteMode Enum ​

VariantBehavior
.appendAppend to existing file (default). File grows over time.
.overwriteTruncate file on startup. Only current session logs are kept.
.appendRotateAppend to file with explicit rotation trigger. Use with rotation config.
zig
// Append mode (default)
var sink = logly.SinkConfig.file("app.log");
sink.writeMode = .append;

// Overwrite mode (fresh start each run)
var sink = logly.SinkConfig.file("session.log");
sink.writeMode = .overwrite;

// Append with rotation
var sink = logly.SinkConfig.file("app.log");
sink.writeMode = .appendRotate;
sink.rotation = "daily";
sink.retention = 7;

When using overwriteMode = true, it is equivalent to writeMode = .overwrite.

File Rotation ​

FieldTypeDefaultDescription
rotation?[]const u8nullRotation interval: "minutely", "hourly", "daily", "weekly", "monthly", "yearly"
sizeLimit?u64nullMax file size in bytes
sizeLimitStr?[]const u8nullMax file size as string (e.g., "10MB", "1GB")
retention?usizenullNumber of rotated files to keep

Async Writing & Buffering ​

FieldTypeDefaultDescription
asyncWritebooltrueEnable async writing with buffering
bufferSizeusize8192Buffer size for async writing in bytes
maxBufferRecordsusize1000Maximum records to buffer before forcing a flush
flushIntervalMsu641000Flush interval in milliseconds
buffered_write_modeboolfalseAccumulate records in buffer until flush() is explicitly called by the user

Rate Limiting ​

FieldTypeDefaultDescription
rateLimitPerSecond?u32nullMaximum messages allowed per second. Extra messages are dropped.

Error Handling ​

FieldTypeDefaultDescription
onErrorErrorBehavior.logStderrError handling behavior: .silent, .logStderr, .disableSink, .propagate

ErrorBehavior semantics ​

  • Applied across direct writes and buffered flush paths (writeRaw, flush, flushNow).
  • .silent: increments write error stats and suppresses output.
  • .logStderr: increments write error stats and emits a compact sink error line to stderr.
  • .disableSink: disables the sink after an error and triggers onStateChange(false) callback when configured.
  • .propagate: returns the original write/flush error to the caller.

For TCP sinks, reconnect attempts use centralized retry defaults:

  • Constants.TimeDefaults.maxRetries
  • Constants.TimeDefaults.retryDelayMs

Advanced Options ​

FieldTypeDefaultDescription
compressionCompressionConfig{}Compression settings for file and network sinks
filterFilterConfig{}Per-sink filter configuration

Cryptographic Log Chaining ​

To enforce strict, tamper-evident log integrity, Logly offers built-in SHA-256 cryptographic chaining.

How It Works ​

  1. When a log record is written to a tamper-evident sink, Logly calculates a SHA-256 hash.
  2. The hash input is a combination of the current log record bytes and the hash of the previous log record written to that sink.
  3. The resulting hash signature is appended to the written log line in a standard "SIG:<hash>" format.
  4. If an attacker modifies or deletes any log line, the cryptographic chain is broken. Subsequent signatures will no longer match the calculated hashes, making tampering immediately detectable during log auditing.

Usage Example ​

zig
var config = logly.Config.default();
config.autoSink = false;

var sinkCfg = logly.SinkConfig.file("secure_audit.log");
sinkCfg.tamperEvident = true; // Enable tamper-evident chaining

const logger = try logly.Logger.initWithConfig(allocator, config);
defer logger.deinit();

 _ = try logger.addSink(sinkCfg);

try logger.info("Critical user balance update", @src());
try logger.flush();

Memory-Mapped Sinks (Mmap) ​

For ultra-high-performance logging workloads, Logly provides portable memory-mapped file writing via POSIX mmap (on Linux and macOS) and Win32 Virtual Memory mapping APIs (on Windows).

How It Works ​

  • By mapping the log file directly into the process's virtual memory address space, Logly performs zero-copy logging directly to RAM, completely bypassing standard operating system write calls (write / WriteFile).
  • The OS kernel handles flushing memory pages to disk asynchronously in the background.
  • Logly manages mapping boundaries, handles dynamic pre-allocation growth when the file capacity is exceeded, and gracefully truncates the log file to its exact written offset during sink deinitialization.

Usage Example ​

zig
var config = logly.Config.default();
config.autoSink = false;

var mmap_sink = logly.SinkConfig.file("high_throughput.log");
mmap_sink.mmap = true;         // Enable memory-mapped file sink
mmap_sink.asyncWrite = false; // direct zero-copy write

const logger = try logly.Logger.initWithConfig(allocator, config);
defer logger.deinit();

 _ = try logger.addSink(mmap_sink);

try logger.info("Microsecond-latency zero-copy write", @src());
try logger.flush();

Write Mode Examples ​

Append Mode (Default) ​

Keep a permanent log file that grows over time:

zig
 _ = try logger.addSink(.{
    .path = "logs/app.log",
    .overwriteMode = false,  // Default: append to existing file
});

Every time you run the application, new logs are appended to the file.

Overwrite Mode ​

Start fresh each run with only current session logs:

zig
 _ = try logger.addSink(.{
    .path = "logs/session.log",
    .overwriteMode = true,  // Overwrite file on initialization
});

When the sink is initialized, it truncates the file, creating a fresh log for the current session.

Mixed Approach ​

Use different modes for different sinks:

zig
// Persistent history - append mode
 _ = try logger.addSink(.{
    .path = "logs/history.log",
    .overwriteMode = false,  // Keep all logs forever
});

// Current session - overwrite mode
 _ = try logger.addSink(.{
    .path = "logs/session.log",
    .overwriteMode = true,   // Fresh start each time
});

// Error tracking - append mode for permanent record
 _ = try logger.addSink(.{
    .path = "logs/errors.log",
    .level = .err,
    .overwriteMode = false,  // Keep error history
});

Methods ​

init(allocator: std.mem.Allocator, config: SinkConfig) !*Sink ​

Initializes a new sink with the specified configuration.

Alias: create

initWithIo(allocator: std.mem.Allocator, io_handle: std.Io, config: SinkConfig) !*Sink ​

Initializes a new sink with an explicit I/O handle and the specified configuration.

deinit() void ​

Deinitializes the sink and frees resources.

Alias: destroy

write(record: *const Record, globalConfig: Config) !void ​

Writes a log record to the sink. Uses the internal allocator for formatting.

writeWithAllocator(record: *const Record, globalConfig: Config, scratchAllocator: ?std.mem.Allocator) !void ​

Writes a log record using an optional scratch allocator for formatting.

Example:

zig
try sink.writeWithAllocator(record, config, logger.scratchAllocator());

writeRaw(data: []const u8) !void ​

Writes preformatted data directly to the sink, bypassing record formatting.

  • Appends a newline for file/console/network raw writes.
  • Updates SinkStats and onWrite callback on success.
  • Applies onError behavior consistently on failures.

flush() !void ​

Flushes buffered sink data to its destination.

  • Returns immediately when buffer is empty.
  • Uses internal buffered record accounting so stats reflect batch flushes accurately.
  • On success updates SinkStats (totalWritten, bytesWritten, flushCount).
  • Invokes onWrite(recordCount, bytes) and onFlush(bytes, durationNs) callbacks.
  • On failure increments writeErrors and applies configured onError behavior.

isAsyncEnabled() bool ​

Returns true if async writing is enabled for this sink, false otherwise.

isHealthy() bool ​

Returns false if the sink has encountered consecutive write errors exceeding its threshold or if the underlying connection/file handle is broken.

enableAsync() void ​

Enables async writing for this sink, allowing buffered writes with periodic flushing.

disableAsync() void ​

Disables async writing for this sink, forcing immediate synchronous writes and flushing any pending buffer.

flushNow() !void ​

Manually flushes the sink buffer immediately, regardless of async settings.

Use this in batch checkpoints when async buffering is enabled but immediate durability is required.

Examples ​

Console Sink (Default) ​

zig
// Using add() alias (same as addSink())
 _ = try logger.add(SinkConfig.default());

File Sink with Rotation ​

zig
 _ = try logger.add(.{
    .path = "logs/app.log",
    .rotation = "daily",
    .retention = 7,
    .sizeLimitStr = "100MB",
});

JSON Sink for Structured Logging ​

zig
 _ = try logger.add(.{
    .path = "logs/app.json",
    .format = .json,
    .prettyJson = true,
    .includeTraceId = true,
});

Error-Only File Sink ​

zig
 _ = try logger.add(.{
    .path = "logs/errors.log",
    .level = .err,           // Minimum: error
    .maxLevel = .critical,  // Maximum: critical
    .color = false,
});

Console with Color Control ​

zig
// Disable colors for console output
 _ = try logger.add(.{
    .color = false,  // Override auto-detection
});

// Or use global setting
var config = Config.default();
config.globalColorDisplay = false;
logger.configure(config);

High-Throughput Async Sink ​

zig
 _ = try logger.add(.{
    .path = "logs/high-volume.log",
    .asyncWrite = true,
    .bufferSize = 65536, // 64KB buffer
});

Manual Flush Control ​

zig
var sink = try logly.Sink.init(allocator, .{
    .path = "logs/batch.log",
    .asyncWrite = true,
    .onError = .propagate,
});
defer sink.deinit();

try sink.writeRaw("batch line 1");
try sink.writeRaw("batch line 2");

// Explicit durability checkpoint
try sink.flushNow();

const stats = sink.getStats();
std.debug.print("flushed records: {}\n", .{stats.getTotalWritten()});

Multiple Sinks with Different Levels ​

zig
// Console: info and above
 _ = try logger.addSink(.{
    .level = .info,
});

// File: all levels
 _ = try logger.addSink(.{
    .path = "logs/debug.log",
    .level = .trace,
});

// Errors file: errors only
 _ = try logger.addSink(.{
    .path = "logs/errors.log",
    .level = .err,
});

Color Auto-Detection ​

When color is null (default), the sink auto-detects:

  • Console sinks: Colors enabled if terminal supports ANSI
  • File sinks: Colors disabled

Override with explicit true or false:

zig
// Force colors off for console
 _ = try logger.addSink(.{ .color = false });

// Force colors on for file (e.g., for viewing with `less -R`)
 _ = try logger.addSink(.{
    .path = "logs/colored.log",
    .color = true,
});

SinkStats ​

Statistics for monitoring sink performance.

Getter Methods ​

MethodReturnDescription
getTotalWritten()u64Get total number of records written
getBytesWritten()u64Get total bytes written
getWriteErrors()u64Get number of write errors
getFlushCount()u64Get number of flush operations
getRotationCount()u64Get number of file rotations

Boolean Checks ​

MethodReturnDescription
hasWritten()boolCheck if any records have been written
hasErrors()boolCheck if any errors have occurred
hasFlushed()boolCheck if any flushes have occurred
hasRotated()boolCheck if any rotations have occurred

Rate Calculations ​

MethodReturnDescription
throughputBytesPerSecond(elapsedSeconds)f64Calculate bytes per second throughput
throughputRecordsPerSecond(elapsedSeconds)f64Calculate records per second throughput
errorRate()f64Calculate error rate (0.0 - 1.0)
successRate()f64Calculate success rate (0.0 - 1.0)
avgBytesPerWrite()f64Calculate average bytes per write
avgFlushesPerRotation()f64Calculate average flushes per rotation

Reset ​

MethodDescription
reset()Reset all statistics to initial state

Example ​

zig
const stats = sink.getStats();
std.debug.print("Written: {} records, {} bytes\n", .{
    stats.getTotalWritten(),
    stats.getBytesWritten(),
});
std.debug.print("Error rate: {d:.2}%\n", .{stats.errorRate() * 100});
std.debug.print("Avg bytes/write: {d:.1}\n", .{stats.avgBytesPerWrite()});

Compression Configuration ​

zig
 _ = try logger.addSink(.{
    .path = "logs/app.log",
    .compression = .{
        .enabled = true,
        .algorithm = .gzip,
        .level = 6,
    },
});

Per-Sink Filtering ​

zig
 _ = try logger.addSink(.{
    .path = "logs/filtered.log",
    .filter = .{
        .includeModules = &.{"database", "http"},
        .excludeModules = &.{"health_check"},
        .includeMessages = &.{"important"},
        .excludeMessages = &.{"debug"},
    },
});

Aliases ​

The Sink module provides convenience aliases:

AliasMethodDescription
statisticsgetStats()Get sink statistics
stats_getStats()Get sink statistics
clearclearBuffer()Clear internal buffer
syncflush()Synchronize buffer to storage
closedeinit()Close and cleanup sink
namegetName()Get sink name

New Methods (v0.0.9) ​

zig
// State management
const enabled = sink.isEnabled();
sink.enable();
sink.disable();

// Buffer management
sink.clearBuffer();

// Name access
const name = sink.getName();

// Statistics
const stats = sink.getStats();  // or sink.statistics()

Configuration Helpers ​

Helper methods on SinkConfig for common use cases:

zig
// Usage: logger.addSink(SinkConfig.console());

pub const SinkConfig = struct {
    /// Returns the default sink configuration (Console, async).
    pub fn default() SinkConfig;

    /// Returns a console sink configuration.
    pub fn console() SinkConfig;
    
    /// Returns a standard error sink configuration.
    pub fn stderr() SinkConfig;
    
    /// Returns a file sink configuration.
    pub fn file(path: []const u8) SinkConfig;

    /// Returns a JSON file sink configuration.
    pub fn jsonFile(path: []const u8) SinkConfig;
    
    /// Returns a rotating file sink configuration.
    pub fn rotating(path: []const u8, rotationInterval: []const u8, retentionCount: usize) SinkConfig;
    
    /// Returns an error-only sink configuration.
    pub fn errorOnly(path: []const u8) SinkConfig;
    
    /// Returns a network sink configuration.
    pub fn network(uri: []const u8) SinkConfig;

    /// Returns an in-memory ring buffer sink for testing/snapshots.
    pub fn memory() SinkConfig;
};

SinkGroup ​

The SinkGroup struct enables atomic fan-out routing of a single log record to multiple named sinks efficiently.

zig
pub const SinkGroup = struct {
    allocator: std.mem.Allocator,
    io: std.Io,
    sinks: std.ArrayListUnmanaged(*Sink),
    mutex: std.Io.Mutex,

    pub fn init(allocator: std.mem.Allocator) SinkGroup;
    pub fn initWithIo(allocator: std.mem.Allocator, io_handle: std.Io) SinkGroup;
    pub fn deinit(self: *SinkGroup) void;
    pub fn addSink(self: *SinkGroup, sink: *Sink) !void;
    pub fn write(self: *SinkGroup, record: *const Record, globalConfig: anytype) !void;
    pub fn flush(self: *SinkGroup) !void;
};

MmapFile ​

High-performance memory-mapped file abstraction for sub-microsecond logging.

zig
pub const MmapFile = struct {
    allocator: std.mem.Allocator,
    io: std.Io,
    file: std.Io.File,
    memory: []align(std.heap.page_size_min) u8,
    writePtr: usize,
    capacity: usize,
    isMapped: bool,

    pub fn init(allocator: std.mem.Allocator, file: std.Io.File, initialSize: usize) !MmapFile;
    pub fn initWithIo(allocator: std.mem.Allocator, io_handle: std.Io, file: std.Io.File, initialSize: usize) !MmapFile;
    pub fn deinit(self: *MmapFile) void;
    pub fn write(self: *MmapFile, data: []const u8) !void;
    pub fn flush(self: *MmapFile) void;
    pub fn finalizeForRotation(self: *MmapFile) void;
    pub fn remapToFile(self: *MmapFile, newFile: std.Io.File, initialSize: usize) !void;
    pub fn resumeMapping(self: *MmapFile) void;
};

See Also ​

Released under the MIT License.