When I set my custom log function weird compilation stuff emerges

I wrote a custom log function that overwrites the default via std.options in the root .zig file:

pub const std_options = .{
    .log_level = .info,
    .logFn = tpiLog,
};

But in a completely different place, that I have been using for 2 weeks now started showing weird format related compile time errors that weren’t related to logging at all:

/home/user/tools/zig-linux-x86_64-0.13.0/lib/std/fmt.zig:88:9: error: expected tuple or struct argument, found usize
        "@"compileError("expected tuple or struct argument, found " ++ "@"typeName(ArgsType));
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I have only been writing this logging function and didn’t change anything else in the codebase, the error doesn’t show up when I remove my logFn in the options.

My (very clunky) logging function for completeness:

inline fn fallbackLog(comptime format: []const u8, args: anytype) void {
    nosuspend std.io.getStdOut().writer().print(format ++ "\n", args) catch return;
}

fn tpiLog(
    comptime level: std.log.Level,
    comptime _: "@"TypeOf(.EnumLiteral),
    comptime format: []const u8,
    args: anytype,
) void {
    var arena_state = std.heap.ArenaAllocator.init(std.heap.c_allocator);
    defer arena_state.deinit();

    const c_allocator = arena_state.allocator();

    const timestamp_buffer = c_allocator.alloc(u8, 64) catch {
        fallbackLog(format, args);
        return;
    };
    defer c_allocator.free(timestamp_buffer);

    const timer = cTime.time(0);
    const tm = cTime.localtime(&timer);
    const tmfrmtres = cTime.strftime(timestamp_buffer.ptr, 64, "%y-%m-%d_%H:%M:%S", tm);
    if (tmfrmtres != 0) {
        std.debug.print("strftime: {}\n", tmfrmtres);
    }

    const timestamp = std.fmt.allocPrint(alloc, "[{s}] |{s}|", .{ timestamp_buffer, level.asText() }) catch {
        fallbackLog(format, args);
        return;
    };
    defer alloc.free(timestamp);

    const padded_format = std.fmt.allocPrint(alloc, format, args) catch {
        fallbackLog(format, args);
        return;
    };
    defer alloc.free(padded_format);

    switch (level) {
        .err => {
            const stderr = std.io.getStdErr().writer();
            _ = stderr.write(timestamp) catch unreachable;
            nosuspend stderr.print(format ++ "\n", args) catch return;
        },
        else => {
            const stdout = std.io.getStdOut().writer();
            _ = stdout.write(timestamp) catch unreachable;
            nosuspend stdout.print(format ++ "\n", args) catch return;
        },
    }
}

Hello @LovasR
welcome to ziggit :slight_smile:

I had a similar problem when calling log.debug from compile time with a custom log function.
The solution in my case was not calling the log function in comptime:

fn logError(comptime err: anytype, comptime fmt: []const u8, args: anytype) TypeOf(err) {
    if (@inComptime()) {
        var buf: [120]u8 = undefined;
        const s = std.fmt.bufPrint(&buf, fmt, args) catch unreachable;
        @compileError(s);
    } else {
        log.debug(fmt, args);
    }
    return err;
}

Normally with -freference-trace you might get a call stack that gives you a hint about the comptime log call.


Additionally avoid using heap allocations when logging.
For example, your first alloc(u8, 64) can be allocated on the stack as:

var timestamp_buffer: [64]u8 = undefined;

The stack is the ultimate arena.


EDIT:
An additional limitation is that the functions used in the custom logger must not call log.

3 Likes

The source of the compile error is this call to std.debug.print (which in turn calls std.fmt.format), where you are passing a bare usize value as the second argument instead of a tuple. You likely intended to type .{tmfrmtres} but forgot the dotbrace.

5 Likes