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
event_logboolfalseEnable 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.
tamper_evidentboolfalseEnable cryptographic SHA-256 chaining to produce tamper-evident log records.
mmapboolfalseEnable virtual memory-mapped zero-copy file logging for microsecond-latency writes.

Windows Event Log (Windows only) ​

When event_log 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 -> EVENTLOG_ERROR_TYPE (Constants.EventLogConstants.error_type)
  • Warning -> EVENTLOG_WARNING_TYPE (Constants.EventLogConstants.warning_type)
  • Notice / Info / Success -> EVENTLOG_INFORMATION_TYPE (Constants.EventLogConstants.information_type)

EVENTLOG_SUCCESS (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
max_level?LevelnullMaximum log level (creates level range)

Output Formatting ​

FieldTypeDefaultDescription
jsonboolfalseForce JSON output for this sink
pretty_jsonboolfalsePretty print JSON with indentation
color?boolnullEnable/disable colors (null = auto-detect)
log_format?[]const u8nullCustom log format string
time_format?[]const u8nullCustom time format for this sink
theme?Formatter.ThemenullCustom color theme for this sink

Field Inclusion ​

FieldTypeDefaultDescription
include_timestampbooltrueInclude timestamp in output
include_levelbooltrueInclude log level in output
include_sourceboolfalseInclude source location
include_trace_idboolfalseInclude trace IDs (distributed tracing)

File Write Mode ​

FieldTypeDefaultDescription
overwrite_modeboolfalseLegacy field: false = append (default), true = truncate file on startup. Prefer write_mode.
write_modeWriteMode.appendFile write mode. See WriteMode enum below.
file_mode?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.
.append_rotateAppend to file with explicit rotation trigger. Use with rotation config.
zig
// Append mode (default)
var sink = logly.SinkConfig.file("app.log");
sink.write_mode = .append;

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

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

When using overwrite_mode = true, it is equivalent to write_mode = .overwrite.

File Rotation ​

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

Async Writing & Buffering ​

FieldTypeDefaultDescription
async_writebooltrueEnable async writing with buffering
buffer_sizeusize8192Buffer size for async writing in bytes
max_buffer_recordsusize1000Maximum records to buffer before forcing a flush
flush_interval_msu641000Flush interval in milliseconds
buffered_write_modeboolfalseAccumulate records in buffer until flush() is explicitly called by the user

Rate Limiting ​

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

Error Handling ​

FieldTypeDefaultDescription
on_errorErrorBehavior.log_stderrError handling behavior: .silent, .log_stderr, .disable_sink, .propagate

ErrorBehavior semantics ​

  • Applied across direct writes and buffered flush paths (writeRaw, flush, flushNow).
  • .silent: increments write error stats and suppresses output.
  • .log_stderr: increments write error stats and emits a compact sink error line to stderr.
  • .disable_sink: 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.max_retries
  • Constants.TimeDefaults.retry_delay_ms

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.auto_sink = false;

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

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

_ = try logger.addSink(sink_cfg);

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.auto_sink = false;

var mmap_sink = logly.SinkConfig.file("high_throughput.log");
mmap_sink.mmap = true;         // Enable memory-mapped file sink
mmap_sink.async_write = 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",
    .overwrite_mode = 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",
    .overwrite_mode = 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",
    .overwrite_mode = false,  // Keep all logs forever
});

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

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

Methods ​

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

Initializes a new sink with the specified configuration.

Alias: create

deinit() void ​

Deinitializes the sink and frees resources.

Alias: destroy

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

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

writeWithAllocator(record: *const Record, global_config: Config, scratch_allocator: ?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 on_write callback on success.
  • Applies on_error 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 (total_written, bytes_written, flush_count).
  • Invokes on_write(record_count, bytes) and on_flush(bytes, duration_ns) callbacks.
  • On failure increments write_errors and applies configured on_error 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,
    .size_limit_str = "100MB",
});

JSON Sink for Structured Logging ​

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

Error-Only File Sink ​

zig
_ = try logger.add(.{
    .path = "logs/errors.log",
    .level = .err,           // Minimum: error
    .max_level = .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.global_color_display = false;
logger.configure(config);

High-Throughput Async Sink ​

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

Manual Flush Control ​

zig
var sink = try logly.Sink.init(allocator, .{
    .path = "logs/batch.log",
    .async_write = true,
    .on_error = .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(elapsed_seconds)f64Calculate bytes per second throughput
throughputRecordsPerSecond(elapsed_seconds)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 = .{
        .include_modules = &.{"database", "http"},
        .exclude_modules = &.{"health_check"},
        .include_messages = &.{"important"},
        .exclude_messages = &.{"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, rotation_interval: []const u8, retention_count: 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. It is used when you need multiple outputs to share the identical formatted record representation simultaneously.

See Also ​

Released under the MIT License.