mod(zlog): pretty printing for user defined types
This commit is contained in:
84
README.md
84
README.md
@@ -1,3 +1,85 @@
|
||||
# zlog
|
||||
|
||||
Standard Library log wrapper.
|
||||
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,
|
||||
}
|
||||
```
|
||||
|
||||
42
src/main.zig
42
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;
|
||||
|
||||
74
src/zlog.zig
74
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user