Compare commits

..

12 Commits

Author SHA1 Message Date
1b7d84d6f6 logging without logger, better public API 2025-11-04 09:06:59 +01:00
109aee806d remove separate enabled function 2025-11-04 08:54:41 +01:00
fa61b79252 remove generic 2025-10-30 19:33:56 +01:00
57cfca97cb patch version bump 2025-10-28 10:23:25 +01:00
42f5f94fb4 fix test check 2025-10-28 10:23:01 +01:00
4099617646 minor version bump 2025-10-27 14:15:33 +01:00
f489c96de2 remove base logger 2025-10-27 14:14:56 +01:00
6efcfcded9 disable in test 2025-10-27 14:14:36 +01:00
2dea92dcd9 fix 2025-10-24 14:21:58 +02:00
d39a834f4e remove explicit comptime because of miscompilation 2025-10-24 14:20:51 +02:00
f78a8eaf1a remove preferred optimization mode 2025-10-24 14:13:54 +02:00
372738e03e readme update 2025-10-24 14:13:37 +02:00
4 changed files with 131 additions and 55 deletions

View File

@@ -1,3 +1,24 @@
# faller # faller
Small logging library. Small logging library.
## Installation
Run:
```bash
zig fetch --save git+https://git.pascalzittlau.de/pzittlau/faller.git
```
Then put this in `build.zig`:
```zig
const faller = b.dependency("faller", .{
.target = target,
.optimize = optimize,
// Other options
});
mod.addImport("faller", faller.module("faller"));
```
## License
Apache 2.0

View File

@@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe }); const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const faller = b.addModule("faller", .{ const faller = b.addModule("faller", .{

View File

@@ -1,6 +1,6 @@
.{ .{
.name = .faller, .name = .faller,
.version = "0.1.0", .version = "0.3.1",
.minimum_zig_version = "0.15.1", .minimum_zig_version = "0.15.1",
.paths = .{ .paths = .{
"build.zig", "build.zig",

View File

@@ -1,22 +1,36 @@
//! 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 std = @import("std");
const root = @import("root"); const root = @import("root");
const builtin = @import("builtin");
pub const options: Options = if (@hasDecl(root, "faller_options")) root.faller_options else .{}; pub const options: Options = if (@hasDecl(root, "faller_options")) root.faller_options else .{};
/// The base logger to create scopes from. Or just use `Logger` directly.
pub const logger = Logger(&.{});
pub const Options = struct { pub const Options = struct {
/// If more specialised ways to log are required replace this function.
/// This could be useful for logging to different things than `stderr` or for specifying whether
/// we should log with environment variables or `and`-based queries.
function: fn ( function: fn (
comptime []const @Type(.enum_literal), comptime []const @Type(.enum_literal),
comptime []const u8, comptime []const u8,
anytype, anytype,
) void = defaultLogFunction, ) 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 /// 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 is present it is logged. May be overwritten by `tags_disabled`.
tags_enabled: []const @Type(.enum_literal) = &.{}, tags_enabled: []const @Type(.enum_literal) = &.{},
@@ -24,13 +38,48 @@ pub const Options = struct {
/// tags is present it **isn't** logged. May overwrite `tags_enabled`. /// tags is present it **isn't** logged. May overwrite `tags_enabled`.
tags_disabled: []const @Type(.enum_literal) = &.{}, tags_disabled: []const @Type(.enum_literal) = &.{},
buffer_size: u64 = 64, buffer_size: u64 = 64,
disabled_in_test: bool = false,
}; };
pub fn Logger(comptime base_tags: []const @Type(.enum_literal)) type { /// A Logger with prefixes.
return struct { pub const Logger = struct {
/// Creates a new logger with an extended tag prefix. base_tags: []const @Type(.enum_literal),
pub fn scope(comptime tag: @Type(.enum_literal)) type {
return Logger(base_tags ++ [_]@Type(.enum_literal){tag}); /// 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},
};
}
/// 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);
}
/// 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 = &[_]@Type(.enum_literal){tag},
};
} }
/// Log with one `tag`. Checks whether the log is enabled. /// Log with one `tag`. Checks whether the log is enabled.
@@ -48,12 +97,27 @@ pub fn Logger(comptime base_tags: []const @Type(.enum_literal)) type {
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
) void { ) void {
const all_tags = comptime base_tags ++ tags; if (builtin.is_test and options.disabled_in_test) return;
if (!options.enabled(all_tags)) return; options.function(tags, format, args);
options.function(all_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`. /// Return `false` if `as` and `bs` have no common member. Else return `true`.
@@ -68,32 +132,13 @@ fn intersect(comptime as: []const @Type(.enum_literal), comptime bs: []const @Ty
return false; 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 {
comptime {
// 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;
}
fn defaultLogFunction( fn defaultLogFunction(
comptime tags: []const @Type(.enum_literal), comptime tags: []const @Type(.enum_literal),
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
) void { ) void {
if (tags.len == 0) @compileError("Need at least one tag for logging."); if (tags.len == 0) @compileError("Need at least one tag for logging.");
if (comptime !logEnabled(tags)) return;
comptime var prefix: []const u8 = ""; comptime var prefix: []const u8 = "";
comptime for (tags) |tag| { comptime for (tags) |tag| {
@@ -106,3 +151,13 @@ fn defaultLogFunction(
defer std.debug.unlockStderrWriter(); defer std.debug.unlockStderrWriter();
nosuspend stderr.print(prefix ++ ": " ++ format ++ "\n", args) catch return; 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});
}