const std = @import("std"); const root = @import("root"); const builtin = @import("builtin"); pub const options: Options = if (@hasDecl(root, "faller_options")) root.faller_options else .{}; pub const Options = struct { function: fn ( comptime []const @Type(.enum_literal), comptime []const u8, anytype, ) void = defaultLogFunction, /// If more specialised ways to determine whether something should be logged are required, /// replace this function. /// This could be useful for instance for specifying it with environment variables or /// `and`-based queries. enabled: fn (comptime []const @Type(.enum_literal)) bool = logEnabled, /// If there is at least one tag given this acts like a whitelist. Meaning if **any** of the /// tags is present it is logged. May be overwritten by `tags_disabled`. tags_enabled: []const @Type(.enum_literal) = &.{}, /// If there is at least one tag given this acts like a blacklist. Meaning if **any** of the /// 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, }; /// Used to create a logger from. /// /// Examples: /// ```zig /// const logger = Logger(&.{.my_module}); /// const foo_bar_logger = Logger(&.{.foo, .bar}); /// const foo_bar_log = foo_bar_logger.log; /// /// ``` pub fn Logger(comptime base_tags: []const @Type(.enum_literal)) type { return struct { /// Creates a new logger with an extended tag prefix. pub fn scope(comptime tag: @Type(.enum_literal)) type { return Logger(base_tags ++ [_]@Type(.enum_literal){tag}); } /// Log with one `tag`. Checks whether the log is enabled. pub fn log( comptime tag: @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { logTags(&.{tag}, format, args); } /// Log with multiple `tags`. Checks whether the log is enabled. pub fn logTags( comptime tags: []const @Type(.enum_literal), comptime format: []const u8, args: anytype, ) void { if (options.disabled_in_test) return; const all_tags = comptime base_tags ++ tags; if (!options.enabled(all_tags)) return; options.function(all_tags, format, args); } }; } /// Return `false` if `as` and `bs` have no common member. Else return `true`. fn intersect(comptime as: []const @Type(.enum_literal), comptime bs: []const @Type(.enum_literal)) bool { for (as) |a| { for (bs) |b| { if (std.mem.eql(u8, @tagName(a), @tagName(b))) { return true; } } } 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, args: anytype, ) void { if (tags.len == 0) @compileError("Need at least one tag for logging."); comptime var prefix: []const u8 = ""; comptime for (tags) |tag| { prefix = prefix ++ @tagName(tag) ++ "|"; }; prefix = prefix[0 .. prefix.len - 1]; // Remove last '|' var buffer: [options.buffer_size]u8 = undefined; const stderr = std.debug.lockStderrWriter(&buffer); defer std.debug.unlockStderrWriter(); nosuspend stderr.print(prefix ++ ": " ++ format ++ "\n", args) catch return; }