Skip to content

addSink Reference ​

Logger.addSink is the single client-side entry point for adding an output destination. Logger.add is a shorter alias for the same call.

zig
const index = try logger.addSink(logly.SinkConfig.file("logs/app.log"));

It returns the sink's index, which logger.getSink(index) resolves back to a handle for inspection. The logger owns every sink added this way and releases it on deinit.

Choosing a target ​

A sink's target is decided by the fields you set. Setting path makes a file or network sink; leaving it unset makes a console sink.

zig
// Console (stdout)
_ = try logger.addSink(.{});

// Console (stderr)
_ = try logger.addSink(.{ .isStderr = true });

// File
_ = try logger.addSink(.{ .path = "logs/app.log" });

// Network, by URI scheme in `path`
_ = try logger.addSink(.{ .path = "tcp://127.0.0.1:9000" });
_ = try logger.addSink(.{ .path = "udp://127.0.0.1:9000" });

// In-memory ring buffer
_ = try logger.addSink(logly.SinkConfig.memory());
path valueTarget
unset, isMemory = falseConsole (stdout)
unset, isStderr = trueConsole (stderr)
"NUL", "/dev/null"Null device; output is discarded, no file is created
"tcp://host:port"TCP stream
"udp://host:port"UDP socket
any other pathFile, with parent directories created as needed

Preset helpers ​

These build a SinkConfig with sensible defaults. A struct literal gives the same result with explicit control.

zig
_ = try logger.addSink(logly.SinkConfig.console());
_ = try logger.addSink(logly.SinkConfig.file("logs/app.log"));
_ = try logger.addSink(logly.SinkConfig.jsonFile("logs/app.json"));
_ = try logger.addSink(logly.SinkConfig.rotating("logs/app.log", "daily", 7));
_ = try logger.addSink(logly.SinkConfig.errorOnly("logs/errors.log"));
_ = try logger.addSink(logly.SinkConfig.network("tcp://127.0.0.1:9000"));
_ = try logger.addSink(logly.SinkConfig.memory());

Options ​

Target and identity ​

FieldTypeDefaultDescription
path?[]const u8nullDestination path or URI. Unset means console.
name?[]const u8nullIdentifier used in metrics and diagnostics.
isMemoryboolfalseRoute records to an in-process ring buffer.
isStderrboolfalseWrite to stderr instead of stdout.
eventLogboolfalseEmit to the platform event log. Binary formats are rejected.
enabledbooltrueInitial enabled state.

Format ​

FieldTypeDefaultDescription
format?Config.FormatnullOutput format. null inherits the logger-wide format.
prettyJsonboolfalseIndent .json document output.

Config.Format is one of text, json, ndjson, logfmt, syslog, syslog3164, or msgpack. Set it per sink to render the same records differently:

zig
_ = try logger.addSink(.{ .path = "logs/app.log", .format = .text });
_ = try logger.addSink(.{ .path = "logs/app.ndjson", .format = .ndjson });
_ = try logger.addSink(.{ .path = "logs/app.syslog", .format = .syslog });

Level and filtering ​

FieldTypeDefaultDescription
level?LevelnullMinimum level for this sink. Records below it are skipped.
maxLevel?LevelnullMaximum level for this sink, forming a level range.
zig
// Everything
_ = try logger.addSink(.{ .path = "logs/app.log" });
// Warnings and above only
_ = try logger.addSink(.{ .path = "logs/warnings.log", .level = .warning });
// Errors through critical only
_ = try logger.addSink(.{ .path = "logs/incidents.log", .level = .err, .maxLevel = .critical });

Color ​

FieldTypeDefaultDescription
color?boolnullnull auto-detects: on for a TTY console, off for files and network.

Color is presentation applied per sink. A console color setting never adds ANSI to a file or network sink, and files never receive ANSI unless asked:

zig
_ = try logger.addSink(.{ .name = "console", .color = true });
_ = try logger.addSink(.{ .path = "logs/app.log", .color = false });

Set the color mode and per-field colors on Config with colorMode and columnColors. See Colors.

Buffering and flushing ​

FieldTypeDefaultDescription
asyncWritebooltrueBuffer records and write in batches.
bufferSizeusizesink defaultBuffer threshold in bytes that triggers a flush.
maxBufferRecordsusizesink defaultRecord count that triggers a flush.
flushIntervalMsu64sink defaultMaximum time a record may sit unflushed.
rateLimitPerSecondu320Records per second; 0 disables rate limiting.

asyncWrite only controls buffering. It does not change which target is written, and it does not interact with the logger-wide autoFlush.

Rotation ​

FieldTypeDefaultDescription
rotation?[]const u8nullTime interval: minutely, hourly, daily, weekly, monthly, yearly.
sizeLimit?u64nullRotate once the file passes this many bytes.
sizeLimitStr?[]const u8nullHuman-readable size such as "10MB", used when sizeLimit is unset.
retention?usizenullNumber of rotated files to keep. Older archives are pruned.
namingFormat?[]const u8nullArchive name template, e.g. "{base}-{date}{ext}".
zig
// Daily, keeping 7 archives
_ = try logger.addSink(.{ .path = "logs/app.log", .rotation = "daily", .retention = 7 });

// Size cap expressed as a string
_ = try logger.addSink(.{ .path = "logs/data.log", .sizeLimitStr = "50MB", .retention = 5 });

// Rotate on either condition
_ = try logger.addSink(.{
    .path = "logs/combined.log",
    .rotation = "daily",
    .sizeLimit = 5 * 1024 * 1024,
    .retention = 10,
});

Rotation callbacks and total-size caps are logger-wide; set config.rotation.onRotate and config.rotation.maxTotalSize on Config.

Write mode ​

FieldTypeDefaultDescription
writeModeWriteMode.appendappend, overwrite, or appendRotate.
overwriteModeboolfalseConvenience flag: true truncates the file at startup.

Performance ​

FieldTypeDefaultDescription
mmapboolfalseMemory-map the file and grow the mapping on demand.

Integrity ​

FieldTypeDefaultDescription
tamperEvidentboolfalseChain each record with a SHA-256 signature over the previous one.

Record content ​

FieldTypeDefaultDescription
includeTimestampbooltrueRender the timestamp.
includeLevelbooltrueRender the level.
includeSourceboolfalseRender file:line.
includeTraceIdboolfalseRender trace and span identifiers.

Compression ​

FieldTypeDefaultDescription
compressionCompressionConfig.{}Compress rotated files. Set .enabled and .algorithm.
zig
_ = try logger.addSink(.{
    .path = "logs/app.log",
    .rotation = "daily",
    .retention = 7,
    .compression = .{ .enabled = true, .algorithm = .gzip },
});

Storage and display flags ​

Config.globalConsoleDisplay and Config.globalFileStorage decide which sinks are legal. A request that contradicts the active flags fails with a typed error instead of creating a dead file or an empty console target:

SituationResult
globalFileStorage = false, file sink addederror.FileStorageDisabled
globalConsoleDisplay = false, console sink addederror.ConsoleDisplayDisabled
Null-device path with file storage offAllowed; creates no file
Memory sink with console display offAllowed; captures in-process

Config.autoSink adds a console sink at startup, and only when globalConsoleDisplay is also enabled.

Inspecting sinks ​

zig
const index = try logger.addSink(logly.SinkConfig.memory());
if (logger.getSink(index)) |sink| {
    // Option A: Owned collection with single deinit()
    var msgs = try sink.messages(allocator);
    defer msgs.deinit();
    for (msgs.items) |m| std.debug.print("{s}\n", .{m});

    // Option B: Zero-allocation direct iterator callback
    const print = struct {
        fn call(msg: []const u8) void {
            std.debug.print("{s}\n", .{msg});
        }
    }.call;
    try sink.forEachMessage(print);
}

Sink management helpers: getSinkCount(), removeSink(index) (alias remove), removeAllSinks() (aliases removeAll and clear).

Explicit Io handle ​

Logging works with zero configuration: Logly uses a stateless default std.Io that covers synchronous file and console paths. When using network transports or custom concurrent runtimes that require async/concurrent capability, provide your own std.Io handle explicitly through configuration:

zig
var threaded = std.Io.Threaded.init(allocator, .{});
defer threaded.deinit();

var config = logly.Config.default();
config.io = threaded.io();

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

Released under the MIT License.