ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.65k stars 2.53k forks source link

Allow adding file name and line number to logging output in std.log #7106

Open demizer opened 3 years ago

demizer commented 3 years ago

When running code in a live environment, it is incredibly handy to have the line number and filename where the log statement was made to quickly jump to that portion of the code.

The following is the best I could come up with (with help from IRC) in a timely manner and limited experience (DON'T COPY THIS).

std.debug.StackIterator is used to get the address of the function where the logging call is made and printed to a buffer. Then i get the end of the line where ": " (note the space) is and used that to slice the buffer. Very janky:

const std = @import("std");

// Set the log level to warning
pub const log_level: std.log.Level = .warn;

var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
var allocator = &arena.allocator;

// Define root.log to override the std implementation
pub fn log(
    comptime level: std.log.Level,
    comptime scope: @TypeOf(.EnumLiteral),
    comptime format: []const u8,
    args: anytype,
) void {
    const scope_prefix = "(" ++ switch (scope) {
        .my_project, .nice_library, .default => @tagName(scope),
        else => if (@enumToInt(level) <= @enumToInt(std.log.Level.crit))
            @tagName(scope)
        else
            return,
    } ++ ")";

    var config: std.debug.TTY.Config = .no_color;
    var buf = std.ArrayList(u8).init(allocator);
    const debug_info = std.debug.getSelfDebugInfo() catch unreachable;
    var it = std.debug.StackIterator.init(@returnAddress(), null);
    var count: u8 = 0;
    var address: usize = 0;
    while (it.next()) |return_address| {
        if (count == 2) {
            address = return_address;
            break;
        }
        count += 1;
    }
    std.debug.printSourceAtAddress(debug_info, buf.outStream(), address - 1, config) catch unreachable;

    const colPos = std.mem.indexOf(u8, buf.items[0..], ": ");
    var bufPrefix: [400]u8 = undefined;
    const prefix = std.fmt.bufPrint(bufPrefix[0..], "[{}] {} [{}]: ", .{ @tagName(level), scope_prefix, buf.items[0..colPos.?] }) catch unreachable;
    var bufOut: [400]u8 = undefined;
    const out = std.fmt.bufPrint(bufOut[0..], format, args) catch unreachable;
    const final = std.mem.concat(allocator, u8, &[_][]const u8{ prefix, out, "\n" }) catch unreachable;

    // Print the message to stderr, silently ignoring any errors
    const held = std.debug.getStderrMutex().acquire();
    defer held.release();
    const stderr = std.io.getStdErr().writer();
    nosuspend stderr.writeAll(final) catch return;
}

pub fn main() void {
    // Using the default scope:
    std.log.info("Just a simple informational log message", .{}); // Won't be printed as log_level is .warn
    std.log.warn("Flux capacitor is starting to overheat", .{});

    // Using scoped logging:
    const my_project_log = std.log.scoped(.my_project);
    const nice_library_log = std.log.scoped(.nice_library);
    const verbose_lib_log = std.log.scoped(.verbose_lib);

    my_project_log.info("Starting up", .{}); // Won't be printed as log_level is .warn
    nice_library_log.err("Something went very wrong, sorry", .{});
    verbose_lib_log.err("Added 1 + 1: {}", .{1 + 1}); // Won't be printed as it gets filtered out by our log function
}

With output that looks like:

[warn] (default) [/data/code/zig/markzig/logline.zig:56:17]: Flux capacitor is starting to overheat
[err] (nice_library) [/data/code/zig/markzig/logline.zig:64:25]: Something went very wrong, sorry

There are other ways this could possibly be done with @src() but that would require it being used everywhere a log statement is made. Also having to create a buffer for every log statement is probably not good.

There should be a better way to do this, for example, Go has it built into the logging library: https://golang.org/pkg/log/#pkg-constants. Same goes for Python.

komuw commented 3 years ago

I might be missing something, is this what you are looking for?;

const std = @import("std");

pub fn main() void {
    std.log.info("{}-{}: Hello world", .{ @src().fn_name, @src().line });
}

https://ziglang.org/documentation/master/#src

VojtechStep commented 3 years ago

I think the idea here is to be able to implement it in the log function only, without the caller having to provide the @src() argument.

demizer commented 3 years ago

I have another janky implementation here that doesn't require an allocator. This allows one to set the logging format when testing. The test runner doesn't expose the root source file so it's not currently possible to setup a logging scope.

LGFae commented 1 year ago

I'd like to point out that this would also be very helpful if it was implemented by default for std.debug.print. The documentation says explicitly that it is meant to be used for printf debugging. I think that having something like the filename and line number printed by default next to print statement would be very useful in this scenario.

shizhaojingszj commented 2 weeks ago

Is there any update here? Actually this is essential when debugging with stdout ...