From 1b7d84d6f6e657ee5458e4e85899a0a766d501e4 Mon Sep 17 00:00:00 2001 From: Pascal Zittlau Date: Tue, 4 Nov 2025 09:06:59 +0100 Subject: [PATCH] logging without logger, better public API --- build.zig.zon | 2 +- src/root.zig | 124 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 41 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 2e933ef..d641ba0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .faller, - .version = "0.2.1", + .version = "0.3.1", .minimum_zig_version = "0.15.1", .paths = .{ "build.zig", diff --git a/src/root.zig b/src/root.zig index 4ccdff5..c35f834 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,3 +1,21 @@ +//! A tag based Logger. Example: +//! +//! ```zig +//! log(.foo, "Anonymous log", .{}); +//! +//! const module_log = scope(.module); +//! module_log.log(.debug, "Hello {s}!", .{"World"}); +//! +//! const sub_module_log = module_log.scope(.bar); +//! sub_module_log.log(.perf, "Operation took {} ns", .{5}); +//! ``` +//! +//! Output: +//! ``` +//! foo: Anonymous log +//! module|debug: Hello World! +//! module|bar|perf: Operation took 5 ns +//! ``` const std = @import("std"); const root = @import("root"); const builtin = @import("builtin"); @@ -20,51 +38,86 @@ pub const Options = struct { /// tags is present it **isn't** logged. May overwrite `tags_enabled`. tags_disabled: []const @Type(.enum_literal) = &.{}, buffer_size: u64 = 64, - disabled_in_test: bool = true, + disabled_in_test: bool = false, }; -pub const empty_logger = Logger{ .base_tags = &.{} }; +/// A Logger with prefixes. +pub const Logger = struct { + base_tags: []const @Type(.enum_literal), -/// A tag based Logger. Examples: -/// -/// ```zig -/// pub const module_log = Logger{ .base_tags = &.{.module} }; -/// module_log.log(.debug, "Hello {s}!", .{"World"}); -/// -/// pub const sub_module_log = module_log.scope(.bar); -/// sub_module_log.log(.perf, "Operation took {} ns", .{time_ns}); -/// ``` -pub const Logger = @This(); + /// Creates a new logger with an additional tag prefix. + pub fn scope(comptime logger: Logger, comptime tag: @Type(.enum_literal)) Logger { + return .{ + .base_tags = logger.base_tags ++ [_]@Type(.enum_literal){tag}, + }; + } -base_tags: []const @Type(.enum_literal), + /// Log with one additional `tag`. Checks whether the log is enabled. + pub fn log( + comptime logger: Logger, + comptime tag: @Type(.enum_literal), + comptime format: []const u8, + args: anytype, + ) void { + logger.logTags(&.{tag}, format, args); + } -/// Creates a new logger with an extended tag prefix. -pub fn scope(comptime logger: Logger, comptime tag: @TypeOf(.enum_literal)) Logger { + /// Log with multiple `tags`. Checks whether the log is enabled. + pub fn logTags( + comptime logger: Logger, + comptime tags: []const @Type(.enum_literal), + comptime format: []const u8, + args: anytype, + ) void { + if (builtin.is_test and options.disabled_in_test) return; + const all_tags = logger.base_tags ++ tags; + options.function(all_tags, format, args); + } +}; + +/// Creates a new logger with a tag prefix. +pub fn scope(comptime tag: @TypeOf(.enum_literal)) Logger { return .{ - .base_tags = logger.base_tags ++ [_]@Type(.enum_literal){tag}, + .base_tags = &[_]@Type(.enum_literal){tag}, }; } /// Log with one `tag`. Checks whether the log is enabled. pub fn log( - comptime logger: Logger, comptime tag: @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { - logger.logTags(&.{tag}, format, args); + logTags(&.{tag}, format, args); } /// Log with multiple `tags`. Checks whether the log is enabled. pub fn logTags( - comptime logger: Logger, comptime tags: []const @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { if (builtin.is_test and options.disabled_in_test) return; - const all_tags = comptime logger.base_tags ++ tags; - options.function(all_tags, format, args); + options.function(tags, format, args); +} + +/// Checks whether the log is enabled for a specific combination of `tags`. Depends on the `options` +/// set. +pub fn logEnabled(comptime tags: []const @Type(.enum_literal)) bool { + // If `tags` has at least one in `options.tags_disabled` then we never log. + if (options.tags_disabled.len > 0) { + if (intersect(tags, options.tags_disabled)) { + return false; + } + } + + // If `tags` has at least one in `options.tags_enabled` then we should log. + if (options.tags_enabled.len > 0) { + return intersect(tags, options.tags_enabled); + } + + // If neither is set then we just log everything. + return true; } /// Return `false` if `as` and `bs` have no common member. Else return `true`. @@ -79,25 +132,6 @@ fn intersect(comptime as: []const @Type(.enum_literal), comptime bs: []const @Ty return false; } -/// Checks whether the log is enabled for a specific combination of `tags`. Depends on the `options` -/// set. -pub fn logEnabled(comptime tags: []const @Type(.enum_literal)) bool { - // If `tags` has at least one in `options.tags_disabled` then we never log. - if (options.tags_disabled.len > 0) { - if (comptime intersect(tags, options.tags_disabled)) { - return false; - } - } - - // If `tags` has at least one in `options.tags_enabled` then we should log. - if (options.tags_enabled.len > 0) { - return comptime intersect(tags, options.tags_enabled); - } - - // If neither is set then we just log everything. - return true; -} - fn defaultLogFunction( comptime tags: []const @Type(.enum_literal), comptime format: []const u8, @@ -117,3 +151,13 @@ fn defaultLogFunction( defer std.debug.unlockStderrWriter(); nosuspend stderr.print(prefix ++ ": " ++ format ++ "\n", args) catch return; } + +test { + log(.foo, "Anonymous log", .{}); + + const module_log = scope(.module); + module_log.log(.debug, "Hello {s}!", .{"World"}); + + const sub_module_log = module_log.scope(.bar); + sub_module_log.log(.perf, "Operation took {} ns", .{5}); +}