6.4 KiB
6.4 KiB
std.log (Zig 0.16.0)
Standardized logging interface with configurable scopes, levels, and output.
Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html
When writing custom log functions in Zig 0.16, use @EnumLiteral() instead of removed @Type(.enum_literal), use std.Io writers for direct file/stdout/stderr output, and route timestamps through a shared std.Io-aware helper.
Quick Reference
| Function | Purpose |
|---|---|
log.err(fmt, args) |
Log error (something went wrong) |
log.warn(fmt, args) |
Log warning (uncertain if wrong) |
log.info(fmt, args) |
Log info (general state) |
log.debug(fmt, args) |
Log debug (debugging only) |
log.scoped(.name) |
Create scoped logger |
Basic Usage
const std = @import("std");
const log = std.log;
pub fn main() void {
log.info("Starting application", .{});
log.debug("Debug value: {}", .{x}); // Hidden in release builds
log.warn("Config missing, using defaults", .{});
log.err("Failed to connect: {s}", .{@errorName(e)});
}
Log Levels
| Level | Build Mode Default | Purpose |
|---|---|---|
.err |
Always shown | Something went wrong |
.warn |
Always shown | Uncertain if wrong, worth investigating |
.info |
Debug + Release | General program state |
.debug |
Debug only | Messages only useful for debugging |
Default level by build mode:
- Debug:
.debug(all messages) - ReleaseSafe/Fast/Small:
.info(no debug messages)
Scoped Logging
Create loggers with custom scopes for filtering:
const std = @import("std");
// Library logger with custom scope
const log = std.log.scoped(.my_library);
pub fn doWork() void {
log.info("Processing...", .{}); // Prefixed with (my_library)
log.debug("Details: {}", .{x});
}
Multiple scopes in one file:
const network_log = std.log.scoped(.network);
const db_log = std.log.scoped(.database);
fn fetchData() void {
network_log.info("Connecting...", .{});
db_log.debug("Query: {s}", .{sql});
}
Configuration via std_options
Configure logging in your root file:
const std = @import("std");
pub const std_options: std.Options = .{
// Global log level
.log_level = .warn, // Only show warn and err
// Per-scope levels (override global)
.log_scope_levels = &.{
.{ .scope = .my_library, .level = .debug }, // Full debug for this scope
.{ .scope = .noisy_lib, .level = .err }, // Errors only
},
// Custom log function
.logFn = myLogFn,
};
Custom Log Function
Replace the default log output:
const std = @import("std");
pub const std_options: std.Options = .{
.logFn = myLogFn,
};
fn myLogFn(
comptime level: std.log.Level,
comptime scope: @EnumLiteral(),
comptime format: []const u8,
args: anytype,
) void {
// Filter: only errors from unknown scopes
const scope_prefix = switch (scope) {
.my_app, .default => @tagName(scope),
else => if (@intFromEnum(level) <= @intFromEnum(std.log.Level.err))
@tagName(scope)
else
return, // Skip non-error from other scopes
};
const level_txt = comptime level.asText();
const prefix = "[" ++ level_txt ++ "] (" ++ scope_prefix ++ "): ";
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
const io = applicationIo(); // Application-owned accessor for std.Io.
var buf: [64]u8 = undefined;
var stderr = std.Io.File.stderr().writer(io, &buf);
stderr.interface.print(prefix ++ format ++ "\n", args) catch return;
stderr.interface.flush() catch return;
}
Check if Logging Enabled
Avoid expensive computations when logging is disabled:
const log = std.log.scoped(.my_scope);
fn process() void {
// Check before expensive operation
if (std.log.logEnabled(.debug, .my_scope)) {
const debug_info = computeExpensiveDebugInfo();
log.debug("Info: {}", .{debug_info});
}
// For default scope
if (std.log.defaultLogEnabled(.debug)) {
std.log.debug("Debug message", .{});
}
}
Level Methods
const level: std.log.Level = .warn;
// Get text representation
const text = level.asText(); // "warning"
// Compare levels (lower = more severe)
const is_error_or_worse = @intFromEnum(level) <= @intFromEnum(std.log.Level.err);
Default Log Function
Forward to the standard implementation:
fn myLogFn(
comptime level: std.log.Level,
comptime scope: @EnumLiteral(),
comptime format: []const u8,
args: anytype,
) void {
// Add timestamp, then forward to default
std.debug.print("[{d}] ", .{applicationTimestampNow().toNanoseconds()});
std.log.defaultLog(level, scope, format, args);
}
Output Format
Default output format:
level: message # default scope
level(scope): message # named scope
Examples:
info: Server started on port 8080
warning(database): Connection pool exhausted
error(network): Failed to resolve hostname
debug: Variable x = 42
Common Patterns
Conditional Debug Logging
fn processItem(item: Item) void {
if (comptime std.log.logEnabled(.debug, .default)) {
log.debug("Processing: {}", .{item});
}
// ... process
}
Error Context Logging
fn loadConfig(io: std.Io, path: []const u8) !Config {
return std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| {
log.err("Failed to open config '{s}': {s}", .{path, @errorName(err)});
return err;
};
}
Library Logging Pattern
// In library code
pub const log = std.log.scoped(.my_lib);
// Users can filter with:
// .log_scope_levels = &.{ .{ .scope = .my_lib, .level = .warn } }
Log to File
fn fileLogFn(
comptime level: std.log.Level,
comptime scope: @EnumLiteral(),
comptime format: []const u8,
args: anytype,
) void {
const io = applicationIo();
const file = std.Io.Dir.cwd().openFile(io, "app.log", .{ .mode = .write_only }) catch return;
defer file.close(io);
var buf: [256]u8 = undefined;
var writer = file.writer(io, &buf);
const w = &writer.interface;
const level_txt = comptime level.asText();
const scope_txt = if (scope == .default) "" else "(" ++ @tagName(scope) ++ ")";
w.print("[{s}]{s} " ++ format ++ "\n", .{level_txt, scope_txt} ++ args) catch return;
w.flush() catch return;
}