Files
zlog/README.md
Yves Biener bd33f9c8f9
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 54s
doc: update README; update comments regarding open tasks and further features
2025-11-01 00:38:34 +01:00

5.8 KiB
Raw Blame History

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.

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 {
    // 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});
}

pub const std_options = zlog.std_options;

const std = @import("std");
const zlog = @import("zlog");

This will result in the following output:

[2025-07-17 19:41:43] [debug](main): Debug message 42
[2025-07-17 19:41:43] [info](main): Info message { 1, 2, 3, 4 }
[2025-07-17 19:41:43] [info](main): Info message "This is a test"
[2025-07-17 19:41:43] [warning](main): Warning message main.Options = enum {
	a,
	b,
	c,
} = .a
[2025-07-17 19:41:43] [warning](main): Warning message main.TaggedUnion = union(enum) {
	one: i16,
	two: u32,
	three: []const u8,
	nothing,
} = .{ .nothing = void }
[2025-07-17 19:41:43] [warning](main): Warning message main.Union = union {
	int: i32,
	boolean: bool,
} = @7fffd1c71048
[2025-07-17 19:41:43] [error](main): Error message main.Struct = struct {
	.a = 42,
	.b = true,
	.c = []u16: { 1, 2, 3, 4, 5 },
	.d = "string",
	.e = main.Options = enum {
		a,
		b,
		c,
	} = .b,
	.f = main.TaggedUnion = union(enum) {
		one: i16,
		two: u32,
		three: []const u8,
		nothing,
	} = .{ .one = -5 },
}
[2025-07-17 19:41:43] [error](main): Error message main.TaggedUnion = union(enum) {
	one: i16,
	two: u32,
	three: []const u8,
	nothing,
} = .{ .three = "Three" }

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.
  • stderr (default: true): Print log messages to stderr.
  • file (default: ""): File path to log messages to. Without a path no log file will be created and logged to. Can be used in parallel with the stderr option

Tips

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 errdefer to 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> log when using the stderr build option) the log file contains control code characters (ansi) for the colored outputs. You can still see them correctly with the following command less -R log.