aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortsne <tsne.dev@outlook.com>2026-09-05 13:05:28 +0200
committertsne <tsne.dev@outlook.com>2026-09-05 13:05:28 +0200
commitc915693969b04fef644095615604ef5220ea8c3c (patch)
tree62093b1828222a0f36643f5da86a2ceb0e07c68a
parentinitial (diff)
downloadmulibs-c915693969b04fef644095615604ef5220ea8c3c.tar.gz
zig: add log
-rw-r--r--README.md2
-rw-r--r--zig/log.zig223
2 files changed, 224 insertions, 1 deletions
diff --git a/README.md b/README.md
index ddf7061..bdae7b8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
# Mulibs
-Tiny libraries to copy and use. Each library fits in a single file.
+A collection of tiny libraries to copy and use. Each library fits in a single file.
diff --git a/zig/log.zig b/zig/log.zig
new file mode 100644
index 0000000..05502bd
--- /dev/null
+++ b/zig/log.zig
@@ -0,0 +1,223 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const Level = std.log.Level;
+
+const unscoped = scoped("");
+pub const fatal = unscoped.fatal;
+pub const err = unscoped.err;
+pub const warn = unscoped.warn;
+pub const info = unscoped.info;
+pub const debug = unscoped.debug;
+
+pub fn scoped(comptime name: []const u8) type {
+ const prefix = if (name.len == 0) "" else name ++ ": ";
+
+ return struct {
+ pub fn fatal(comptime msg: []const u8, details: anytype) noreturn {
+ @branchHint(.cold);
+ log_structured("FAT", msg, details);
+ std.process.exit(1);
+ }
+
+ pub fn err(comptime msg: []const u8, details: anytype) void {
+ @branchHint(.cold);
+ log(.err, prefix ++ msg, details);
+ }
+
+ pub inline fn warn(comptime msg: []const u8, details: anytype) void {
+ log(.warn, prefix ++ msg, details);
+ }
+
+ pub inline fn info(comptime msg: []const u8, details: anytype) void {
+ log(.info, prefix ++ msg, details);
+ }
+
+ pub inline fn debug(comptime msg: []const u8, details: anytype) void {
+ log(.debug, prefix ++ msg, details);
+ }
+ };
+}
+
+inline fn log(
+ comptime level: Level,
+ comptime msg: []const u8,
+ details: anytype,
+) void {
+ if (comptime std.log.defaultLogEnabled(level)) {
+ const level_text = switch (level) {
+ .err => "ERR",
+ .warn => "WRN",
+ .info => "INF",
+ .debug => "DBG",
+ };
+ log_structured(level_text, msg, details);
+ }
+}
+
+fn log_structured(
+ comptime level: []const u8,
+ comptime msg: []const u8,
+ details: anytype,
+) void {
+ comptime {
+ if (std.mem.indexOfScalar(u8, msg, '"') != null) {
+ @compileError("double quotation marks not allowed in log message");
+ }
+ }
+
+ // <level> "a formatted message" {key=val, ...}
+ const stderr = std.io.getStdErr().writer();
+ nosuspend {
+ const message = level ++ " \"" ++ msg ++ "\"";
+ if (@typeInfo(@TypeOf(details)).@"struct".fields.len > 0) {
+ stderr.print(message ++ " {}\n", .{structured(details)}) catch return;
+ } else {
+ stderr.print(message ++ "\n", .{}) catch return;
+ }
+ }
+}
+
+fn Structured(comptime T: type) type {
+ comptime {
+ assert(@typeInfo(T) == .@"struct");
+ assert(@typeInfo(T).@"struct".fields.len > 0);
+
+ for (@typeInfo(T).@"struct".fields) |field| {
+ for (field.name) |ch| {
+ switch (ch) {
+ 'a'...'z', 'A'...'Z', '0'...'9', '_' => {},
+ else => @compileError("invalid key '" ++ field.name ++ "' in log details"),
+ }
+ }
+ }
+ }
+
+ return struct {
+ val: T,
+
+ inline fn init(val: T) @This() {
+ return .{ .val = val };
+ }
+
+ pub inline fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = fmt;
+ _ = options;
+ try format_structured_struct(self.val, writer);
+ }
+ };
+}
+
+inline fn structured(value: anytype) Structured(@TypeOf(value)) {
+ return Structured(@TypeOf(value)).init(value);
+}
+
+inline fn format_structured_any(value: anytype, writer: anytype) !void {
+ const T = @TypeOf(value);
+ switch (@typeInfo(T)) {
+ .void => {
+ try writer.writeAll("void");
+ },
+ .bool => {
+ try writer.writeAll(if (value) "true" else "false");
+ },
+ .int => {
+ try std.fmt.formatIntValue(value, "d", .{}, writer);
+ },
+ .float => {
+ try std.fmt.formatFloatValue(value, "e", .{}, writer);
+ },
+ .pointer => |pointer| {
+ if (pointer.size != .slice) @compileError("cannot log pointers (only slices allowed)");
+ if (pointer.child == u8) {
+ try format_structured_string(value, writer);
+ } else {
+ try format_structured_sequence(value, writer);
+ }
+ },
+ .array => |array| {
+ if (array.child == u8) {
+ try format_structured_string(value, writer);
+ } else {
+ try format_structured_sequence(value, writer);
+ }
+ },
+ .@"struct" => {
+ try format_structured_struct(value, writer);
+ },
+ .null => {
+ try writer.writeAll("null");
+ },
+ .optional => {
+ if (value) |v| {
+ try format_structured_any(v, writer);
+ } else {
+ try writer.writeAll("null");
+ }
+ },
+ .error_set => {
+ try writer.writeAll(@errorName(value));
+ },
+ .@"enum" => {
+ try format_structured_enum(value, writer);
+ },
+ .@"union" => {
+ try format_structured_union(value, writer);
+ },
+ else => @compileError("cannot log type " ++ @typeName(T)),
+ }
+}
+
+fn format_structured_string(value: []const u8, writer: anytype) !void {
+ try writer.writeAll("\"");
+ try writer.writeAll(value);
+ try writer.writeAll("\"");
+}
+
+fn format_structured_sequence(value: anytype, writer: anytype) !void {
+ if (value.len == 0) {
+ try writer.writeAll("[]");
+ } else {
+ try writer.writeAll("[");
+ try format_structured_any(value[0], writer);
+ for (value[1..]) |v| {
+ try writer.writeAll(", ");
+ try format_structured_any(v, writer);
+ }
+ try writer.writeAll("]");
+ }
+}
+
+fn format_structured_struct(value: anytype, writer: anytype) !void {
+ const struct_info = @typeInfo(@TypeOf(value)).@"struct";
+ if (struct_info.fields.len == 0) {
+ try writer.writeAll("{}");
+ } else {
+ try writer.writeAll("{ " ++ struct_info.fields[0].name ++ "=");
+ try format_structured_any(@field(value, struct_info.fields[0].name), writer);
+ inline for (struct_info.fields[1..]) |field| {
+ try writer.writeAll(", " ++ field.name ++ "=");
+ try format_structured_any(@field(value, field.name), writer);
+ }
+ try writer.writeAll(" }");
+ }
+}
+
+inline fn format_structured_enum(value: anytype, writer: anytype) !void {
+ const E = @TypeOf(value);
+ if (std.meta.hasMethod(E, "format")) {
+ try value.format("", .{}, writer);
+ } else if (@typeInfo(E).Enum.is_exhaustive) {
+ try writer.writeAll(@tagName(value));
+ } else {
+ try std.fmt.formatIntValue(@intFromEnum(value), "d", .{}, writer);
+ }
+}
+
+inline fn format_structured_union(value: anytype, writer: anytype) !void {
+ const U = @TypeOf(value);
+ if (std.meta.hasMethod(U, "format")) {
+ try value.format("", .{}, writer);
+ } else {
+ @compileError("cannot log union " ++ @typeName(U));
+ }
+}