diff options
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | zig/log.zig | 223 |
2 files changed, 224 insertions, 1 deletions
@@ -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)); + } +} |