# 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 ```zig 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` | Enabled by the default configuration | Something went wrong | | `.warn` | Enabled by the default configuration | Uncertain if wrong, worth investigating | | `.info` | Enabled in common default modes | General program state | | `.debug` | Commonly Debug-only by default | Messages only useful for debugging | Compile-time level/scope filtering is controlled by `std.options`; a custom `logFn` also decides what its sink actually emits. 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: ```zig 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: ```zig 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: ```zig 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: ```zig 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 ++ "): "; var buf: [64]u8 = undefined; const locked = std.debug.lockStderr(&buf); defer std.debug.unlockStderr(); // flushes the returned writer locked.file_writer.interface.print(prefix ++ format ++ "\n", args) catch return; } ``` ## Check if Logging Enabled Avoid expensive computations when logging is disabled: ```zig 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.logEnabled(.debug, .default)) { std.log.debug("Debug message", .{}); } } ``` ## Level Methods ```zig 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: ```zig fn myLogFn( comptime level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, ) void { // Emit the timestamp and message under one stderr lock so concurrent // records cannot split the prefix from the message. var buf: [128]u8 = undefined; const locked = std.debug.lockStderr(&buf); defer std.debug.unlockStderr(); locked.file_writer.interface.print( "[{d}] " ++ format ++ "\n", .{applicationTimestampNow().toNanoseconds()} ++ args, ) catch return; } ``` ## 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 ```zig fn processItem(item: Item) void { if (comptime std.log.logEnabled(.debug, .default)) { log.debug("Processing: {}", .{item}); } // ... process } ``` ### Error Context Logging ```zig 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 ```zig // 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 ```zig fn fileLogFn( comptime level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype, ) void { const io = applicationIo(); // Returns a long-lived handle opened during startup with an explicit // create/append policy. Plain write_only would require an existing file // and reopening each record could overwrite earlier data. const file = applicationLogFile(); 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; } ``` If multiple tasks share this sink, serialize the complete record and keep the file/writer alive; opening, seeking, writing, and closing separately for each record is not atomic.