zlog
Standard Library log wrapper. zlog provides adjusted std.log output and a pretty print function for easy overwriting of user defined types.
Caution
Only builds using the zig master version are tested to work.
Install
Add or update this library as a dependency in your zig project run the following command:
zig fetch --save git+https://gitea.yves-biener.de/yves-biener/zlog
Afterwards add the library as a dependency to any module in your build.zig:
const ztime_dependency = b.dependency("ztime", .{
.target = target,
.optimize = optimize,
.timestamp = true, // default (only required if non-default value shall be used)
.stderr = true, // default (only required if non-default value shall be used)
.file = "", // default (only required if non-default value shall be used)
});
Usage
The following snippet shows an example usage of zlog to change the default log format and add pretty printing to the user defined types:
const Union = union {
int: i32,
boolean: bool,
// copy and paste this function into your user defined types to enable pretty printing for these types
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
try zlog.pretty_format(value, fmt, options, writer);
}
};
const TaggedUnion = union(enum) {
one: i16,
two: u32,
three: []const u8,
nothing,
// copy and paste this function into your user defined types to enable pretty printing for these types
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
try zlog.pretty_format(value, fmt, options, writer);
}
};
const Options = enum {
a,
b,
c,
// copy and paste this function into your user defined types to enable pretty printing for these types
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
try zlog.pretty_format(value, fmt, options, writer);
}
};
const Struct = struct {
a: usize = 42,
b: bool = true,
c: [5]u16 = .{ 1, 2, 3, 4, 5 },
d: []const u8 = "string",
e: Options = .b,
f: TaggedUnion = .{ .one = -5 },
// same function as above...
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
try zlog.pretty_format(value, fmt, options, writer);
}
};
pub fn main() void {
// without explicit scope (i.e. `.default` scope)
std.log.info("Without explicit scope or `.default` scope", .{});
// initialize zlog with the scope of `main`
const log = std.log.scoped(.main);
// NOTE the scope of `default` will result in no scoping being printed in
// the resulting output (default behavior of `std.log.defaultLog`)
// some variables to log
const int_var = 42;
const array_var = [_]i32{ 1, 2, 3, 4 };
const string_var = "This is a test";
const option_var: Options = .a;
const struct_var: Struct = .{};
const tagged_union: TaggedUnion = .{
.three = "Three",
};
const void_tagged_union: TaggedUnion = .nothing;
const uniun: Union = .{ .boolean = true };
// NOTE Depending on the optimization target some of these log messages
// will not show, which is inline with the behavior of `std.log`.
log.debug("Debug message {any}", .{int_var});
log.info("Info message {any}", .{array_var});
log.info("Info message \"{s}\"", .{string_var});
log.warn("Warning message {any}", .{option_var});
log.warn("Warning message {any}", .{void_tagged_union});
log.warn("Warning message {any}", .{uniun});
log.err("Error message {any}", .{struct_var});
log.err("Error message {any}", .{tagged_union});
}
// apply *zlog* logging options to `std` logger
pub const std_options = zlog.std_options;
const std = @import("std");
const zlog = @import("zlog");
This will result in the following output:
2025/11/02 11:57:29 [info] Without explicit scope or `.default` scope
2025/11/02 11:57:29 [debug](main) Debug message 42
2025/11/02 11:57:29 [info](main) Info message { 1, 2, 3, 4 }
2025/11/02 11:57:29 [info](main) Info message "This is a test"
2025/11/02 11:57:29 [warning](main) Warning message .a
2025/11/02 11:57:29 [warning](main) Warning message .{ .nothing = void }
2025/11/02 11:57:29 [warning](main) Warning message .{ ... }
2025/11/02 11:57:29 [error](main) Error message .{ .a = 42, .b = true, .c = { 1, 2, 3, 4, 5 }, .d = { 115, 116, 114, 105, 110, 103 }, .e = .b, .f = .{ .one = -5 } }
2025/11/02 11:57:29 [error](main) Error message .{ .three = { 84, 104, 114, 101, 101 } }
Customization
For more details about the output customization see the configuration options of the zlog module. Following options are available:
- timestamp (default:
true): Prepend the current timestamp before each log message. If disabled the date and time (i.e.2025/11/02 11:57:29) are not prepended to each log message. - stderr (default:
true): Print log messages to stderr. - file (default:
""): File path to log (appending; creates if does not exist) messages to. Without a path no log file will be created and logged to. Can be used in parallel with the stderr option.
Tip
The following list shows some tips on how to use logging more effectively. These tips do not apply just to
zlog(and not even only to zig code).
- Use
errdeferto directly print messages on failures in the same function they occur:// assume log is already defined before (with the corresponding scope) const port = port: { errdefer |err| log.err("failed to read the port number: {}", .{err}); var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined; const len = try process.stdout.?.readAll(&buf); break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10); }; - When looking through the output of the log (i.e. written to disk by
program 2> logwhen using the stderr build option) thelogfile contains control code characters (ansi) for the colored outputs. You can still see them correctly with the following commandless -R log. When using the file build option the ansi control characters are not omitted when logging to the file.