Files
faller/src/root.zig
2025-10-27 14:14:56 +01:00

118 lines
4.0 KiB
Zig

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;
}