From 510b3ddfcf83d9ec9781f5be2a8c9bf41705d269 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 28 Aug 2024 17:40:41 +0200 Subject: [PATCH] mod(zlog): pretty printing for user defined types --- README.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.zig | 42 +++++++------------------- src/zlog.zig | 74 ++++++++++++++++++++++++++------------------- 3 files changed, 137 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index f0485de..08963d4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,85 @@ # zlog -Standard Library log wrapper. \ No newline at end of file +Standard Library log wrapper. `zlog` provides adjusted `std.log` output and a pretty print function for easy overwriting of user defined types (`struct`, `enum`, `union` and `vector`). + +## Usage + +The following snippet shows an example usage of `zlog` to change the default log format and add pretty printing to both user defined types (`Options` and `Struct`): + +```zig +const std = @import("std"); +const zlog = @import("zlog"); + +// use this to overwrite the default log format of `std.log` +pub const std_options = zlog.std_options; + +const Options = enum { + a, + b, + c, + + // copy and paste this function into your user defined types to enable pretty printing for these types + pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void { + try zlog.pretty_format(value, fmt, options, writer); + } +}; + +const Struct = struct { + a: usize = 42, + b: bool = true, + c: [5]u16 = .{ 1, 2, 3, 4, 5 }, + d: []const u8 = "string", + e: Options = Options.b, + + // same function as above... + pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void { + try zlog.pretty_format(value, fmt, options, writer); + } +}; + +pub fn main() void { + // initialize zlog with the scope of `main` + const log = std.log.scoped(.main); + // NOTE: the scope of `default` will result in no scoping being printed in + // the resulting output (default behavior of `std.log.defaultLog`) + + // some variables to log + const int_var = 42; + const array_var = [_]i32{ 1, 2, 3, 4 }; + const string_var = "This is a test"; + const option_var = Options.a; + const struct_var: Struct = .{}; + + // NOTE: Depending on the optimization target some of these log messages + // will not show, which is inline with the behavior of `std.log`. + log.debug("Debug message {any}", .{int_var}); + log.info("Info message {any}", .{array_var}); + log.info("Info message \"{s}\"", .{string_var}); + log.warn("Warning message {any}", .{option_var}); + log.err("Error message {any}", .{struct_var}); +} +``` + +This will result in the following output: + +``` +debug(main): Debug message 42 +info(main): Info message { 1, 2, 3, 4 } +info(main): Info message "This is a test" +warning(main): Warning message main.Options = enum { + a, + b, + c, +} = a +error(main): Error message main.Struct = struct { + .a = 42, + .b = true, + .c = []u16: { 1, 2, 3, 4, 5 }, + .d = { 115, 116, 114, 105, 110, 103 }, + .e = main.Options = enum { + a, + b, + c, + } = b, +} +``` diff --git a/src/main.zig b/src/main.zig index fc04de1..549d0b5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,10 +1,17 @@ const std = @import("std"); const zlog = @import("zlog"); +pub const std_options = zlog.std_options; + const Options = enum { a, b, c, + + // copy and paste this function into your user defined types to enable pretty printing for these types + pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void { + try zlog.pretty_format(value, fmt, options, writer); + } }; const Struct = struct { @@ -14,44 +21,17 @@ const Struct = struct { d: []const u8 = "string", e: Options = Options.b, - // pub fn format(value: Struct, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - // // TODO: make the creation of this function comptime - // try writer.writeAll("main.Struct{ "); - // try writer.writeAll(".a: usize = "); - // try std.fmt.formatIntValue(value.a, fmt, options, writer); - // try writer.writeAll(", "); - - // try writer.writeAll(".b: bool = "); - // try std.fmt.formatBuf(if (value.b) "true" else "false", options, writer); - // try writer.writeAll(", "); - - // try writer.writeAll(".c: [5]u16 = "); - // try std.fmt.format(writer, "{any}", .{value.c}); - // try writer.writeAll(", "); - - // try writer.writeAll(".d: []const u8 = "); - // try std.fmt.format(writer, "\"{s}\"", .{value.d}); - // try writer.writeAll(", "); - - // try writer.writeAll(".e: main.Options = "); - // try std.fmt.format(writer, "{any}", .{value.e}); - - // try writer.writeAll(" }"); - // } - - // TODO: can this become comptime entirely? - i.e. create the entire struct through a helper function and provide the created one with the format function already attached? - pub fn format(value: Struct, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void { - try zlog.generic_format(value, fmt, options, writer); + // same function as above... + pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void { + try zlog.pretty_format(value, fmt, options, writer); } }; -pub const std_options = zlog.std_options; - pub fn main() void { // initialize zlog with the scope of `main` const log = std.log.scoped(.main); // NOTE: the scope of `default` will result in no scoping being printed in - // the resulting output + // the resulting output (default behavior of `std.log.defaultLog`) // some variables to log const int_var = 42; diff --git a/src/zlog.zig b/src/zlog.zig index af15bae..d19a30f 100644 --- a/src/zlog.zig +++ b/src/zlog.zig @@ -1,7 +1,5 @@ const std = @import("std"); -const max_depth = 5; - // pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void // TODO: provide comptime helper function to add format function for user types: // - pretty printing @@ -30,7 +28,7 @@ fn logFn( // - should there be spacing? // - should the messages be aligned (left, center, right)? const level_txt = comptime message_level.asText(); - const prefix2 = if (scope == .default) ":\t" else "(" ++ @tagName(scope) ++ "):\t"; + const prefix = if (scope == .default) ":\t" else "(" ++ @tagName(scope) ++ "):\t"; const stderr = std.io.getStdErr().writer(); var bw = std.io.bufferedWriter(stderr); const writer = bw.writer(); @@ -38,51 +36,65 @@ fn logFn( std.debug.lockStdErr(); defer std.debug.unlockStdErr(); nosuspend { - writer.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; + writer.print(level_txt ++ prefix ++ format ++ "\n", args) catch return; bw.flush() catch return; } } -pub fn generic_format(object: anytype, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; +pub fn pretty_format(object: anytype, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + try inner_format(object, fmt, options, writer, 0); +} + +fn inner_format(object: anytype, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, comptime depth: u8) !void { const Object = @TypeOf(object); const object_info = @typeInfo(Object); switch (object_info) { .Struct => |s| { try writer.writeAll(@typeName(Object)); - try writer.writeAll(" = {\n"); + try writer.writeAll(" = struct {\n"); inline for (s.fields) |field| { - try writer.writeAll("\t ."); + try writer.writeAll("\t" ** (depth + 1)); + try writer.writeAll("."); try writer.writeAll(field.name); try writer.writeAll(" = "); // TODO check corresponding type and try to adapt fmt accordingly! - try std.fmt.format(writer, "{any}", .{@field(object, field.name)}); + // TODO: pass along the already parsed formatting options too + try inner_format(@field(object, field.name), fmt, options, writer, depth + 1); try writer.writeAll(",\n"); } + try writer.writeAll("\t" ** depth); try writer.writeAll("}"); }, - else => {}, - } -} - -pub fn enhance_format(comptime object: type) type { - // TODO: append a function to the type if it is a user defined type - - // NOTE: this has the clear disadvantage that the type is not the same anymore! (but maybe this is still fine) - const object_info = @typeInfo(object); - switch (object_info) { - .Struct => |s| { - return @Type(.{ - .Struct = .{ - .layout = .auto, - .fields = s.fields, - .decls = s.decls, - .is_tuple = false, - }, - }); + .Enum => |e| { + try writer.writeAll(@typeName(Object)); + try writer.writeAll(" = enum {\n"); + inline for (e.fields) |field| { + try writer.writeAll("\t" ** (depth + 1)); + try writer.writeAll(field.name); + try writer.writeAll(",\n"); + } + try writer.writeAll("\t" ** depth); + try writer.writeAll("} = "); + try writer.writeAll(@tagName(object)); + }, + // TODO: implement prett_printing for other user defined types (`union` and `vector`) + // TODO: recognize []const u8 types and print them as strings + .Array => |a| { + if (a.child == @TypeOf([:0]const u8)) { + try std.fmt.format(writer, "\"{s}\"", .{object}); + } else { + try std.fmt.format(writer, "[]{s}: {any}", .{ @typeName(a.child), object }); + } + }, + .Vector => |v| { + if (v.child == @TypeOf([:0]const u8)) { + try std.fmt.format(writer, "\"{s}\"", .{object}); + } else { + try std.fmt.format(writer, "[]{s}: {any}", .{ @typeName(v.child), object }); + } + }, + else => { + try std.fmt.format(writer, "{any}", .{object}); }, - else => {}, } - return object; }