mod: rem zlog dependency; stream line logging structure; do not write ansi control characters when logging to file
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m21s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m21s
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
log
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -5,6 +5,26 @@ Standard Library log wrapper. `zlog` provides adjusted `std.log` output and a pr
|
|||||||
> [!caution]
|
> [!caution]
|
||||||
> Only builds using the zig master version are tested to work.
|
> Only builds using the zig master version are tested to work.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Add or update this library as a dependency in your zig project run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zig fetch --save git+https://gitea.yves-biener.de/yves-biener/zlog
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards add the library as a dependendy to any module in your *build.zig*:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
const ztime_dependency = b.dependency("ztime", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.timestamp = true, // default (only required if non-default value shall be used)
|
||||||
|
.stderr = true, // default (only required if non-default value shall be used)
|
||||||
|
.file = "", // default (only required if non-default value shall be used)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The following snippet shows an example usage of `zlog` to change the default log format and add pretty printing to the user defined types:
|
The following snippet shows an example usage of `zlog` to change the default log format and add pretty printing to the user defined types:
|
||||||
@@ -58,6 +78,8 @@ const Struct = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
|
// without explict scope (i.e. `.default` scope)
|
||||||
|
std.log.info("Without explicit scope or `.default` scope", .{});
|
||||||
// initialize zlog with the scope of `main`
|
// initialize zlog with the scope of `main`
|
||||||
const log = std.log.scoped(.main);
|
const log = std.log.scoped(.main);
|
||||||
// NOTE the scope of `default` will result in no scoping being printed in
|
// NOTE the scope of `default` will result in no scoping being printed in
|
||||||
@@ -75,7 +97,7 @@ pub fn main() void {
|
|||||||
const void_tagged_union: TaggedUnion = .nothing;
|
const void_tagged_union: TaggedUnion = .nothing;
|
||||||
const uniun: Union = .{ .boolean = true };
|
const uniun: Union = .{ .boolean = true };
|
||||||
|
|
||||||
// NOTE: Depending on the optimization target some of these log messages
|
// NOTE Depending on the optimization target some of these log messages
|
||||||
// will not show, which is inline with the behavior of `std.log`.
|
// will not show, which is inline with the behavior of `std.log`.
|
||||||
log.debug("Debug message {any}", .{int_var});
|
log.debug("Debug message {any}", .{int_var});
|
||||||
log.info("Info message {any}", .{array_var});
|
log.info("Info message {any}", .{array_var});
|
||||||
@@ -87,6 +109,7 @@ pub fn main() void {
|
|||||||
log.err("Error message {any}", .{tagged_union});
|
log.err("Error message {any}", .{tagged_union});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply *zlog* logging options to `std` logger
|
||||||
pub const std_options = zlog.std_options;
|
pub const std_options = zlog.std_options;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
@@ -96,60 +119,30 @@ const zlog = @import("zlog");
|
|||||||
This will result in the following output:
|
This will result in the following output:
|
||||||
|
|
||||||
```
|
```
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;12mdebug[0m]([2mmain[0m): Debug message 42
|
2025/11/02 11:57:29 [info] Without explicit scope or `.default` scope
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;10minfo[0m]([2mmain[0m): Info message { 1, 2, 3, 4 }
|
2025/11/02 11:57:29 [debug](main) Debug message 42
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;10minfo[0m]([2mmain[0m): Info message "This is a test"
|
2025/11/02 11:57:29 [info](main) Info message { 1, 2, 3, 4 }
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;11mwarning[0m]([2mmain[0m): Warning message main.Options = enum {
|
2025/11/02 11:57:29 [info](main) Info message "This is a test"
|
||||||
a,
|
2025/11/02 11:57:29 [warning](main) Warning message .a
|
||||||
b,
|
2025/11/02 11:57:29 [warning](main) Warning message .{ .nothing = void }
|
||||||
c,
|
2025/11/02 11:57:29 [warning](main) Warning message .{ ... }
|
||||||
} = .a
|
2025/11/02 11:57:29 [error](main) Error message .{ .a = 42, .b = true, .c = { 1, 2, 3, 4, 5 }, .d = { 115, 116, 114, 105, 110, 103 }, .e = .b, .f = .{ .one = -5 } }
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;11mwarning[0m]([2mmain[0m): Warning message main.TaggedUnion = union(enum) {
|
2025/11/02 11:57:29 [error](main) Error message .{ .three = { 84, 104, 114, 101, 101 } }
|
||||||
one: i16,
|
|
||||||
two: u32,
|
|
||||||
three: []const u8,
|
|
||||||
nothing,
|
|
||||||
} = .{ .nothing = void }
|
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;11mwarning[0m]([2mmain[0m): Warning message main.Union = union {
|
|
||||||
int: i32,
|
|
||||||
boolean: bool,
|
|
||||||
} = @7fffd1c71048
|
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;9merror[0m]([2mmain[0m): Error message main.Struct = struct {
|
|
||||||
.a = 42,
|
|
||||||
.b = true,
|
|
||||||
.c = []u16: { 1, 2, 3, 4, 5 },
|
|
||||||
.d = "string",
|
|
||||||
.e = main.Options = enum {
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c,
|
|
||||||
} = .b,
|
|
||||||
.f = main.TaggedUnion = union(enum) {
|
|
||||||
one: i16,
|
|
||||||
two: u32,
|
|
||||||
three: []const u8,
|
|
||||||
nothing,
|
|
||||||
} = .{ .one = -5 },
|
|
||||||
}
|
|
||||||
[[1m2025-07-17 19:41:43[0m] [[38;5;9merror[0m]([2mmain[0m): Error message main.TaggedUnion = union(enum) {
|
|
||||||
one: i16,
|
|
||||||
two: u32,
|
|
||||||
three: []const u8,
|
|
||||||
nothing,
|
|
||||||
} = .{ .three = "Three" }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
For more details about the output customization see the configuration options of the `zlog` module. Following options are available:
|
For more details about the output customization see the configuration options of the `zlog` module. Following options are available:
|
||||||
|
|
||||||
- *timestamp* (default: `true`): Prepend the current timestamp before each log message.
|
- *timestamp* (default: `true`): Prepend the current timestamp before each log message. If disabled the date and time (i.e. `2025/11/02 11:57:29 `) are not prepended to each log message.
|
||||||
- *stderr* (default: `true`): Print log messages to stderr.
|
- *stderr* (default: `true`): Print log messages to stderr.
|
||||||
- *file* (default: `""`): File path to log messages to. Without a path no log file will be created and logged to. Can be used in parallel with the *stderr* option
|
- *file* (default: `""`): File path to log (appending; creates if does not exist) messages to. Without a path no log file will be created and logged to. Can be used in parallel with the *stderr* option.
|
||||||
|
|
||||||
## Tips
|
---
|
||||||
|
|
||||||
The following list shows some tips on how to use logging more effectively. These tips do not apply just to `zlog` (and not even only to zig code).
|
> [!tip]
|
||||||
|
> The following list shows some tips on how to use logging more effectively.
|
||||||
|
> These tips do not apply just to `zlog` (and not even only to zig code).
|
||||||
|
|
||||||
- Use `errdefer` to directly print messages on failures in the same function they occur:
|
- Use `errdefer` to directly print messages on failures in the same function they occur:
|
||||||
```zig
|
```zig
|
||||||
@@ -162,4 +155,4 @@ The following list shows some tips on how to use logging more effectively. These
|
|||||||
break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10);
|
break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
- When looking through the output of the log (i.e. written to disk by `program 2> log` when using the _stderr_ build option) the `log` file contains control code characters (*ansi*) for the colored outputs. You can still see them correctly with the following command `less -R log`.
|
- When looking through the output of the log (i.e. written to disk by `program 2> log` when using the *stderr* build option) the `log` file contains control code characters (*ansi*) for the colored outputs. You can still see them correctly with the following command `less -R log`. When using the *file* build option the *ansi* control characters are not omitted when logging to the file.
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const ztime_dependency = b.dependency("ztime", .{
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
const zlog_module = b.addModule("zlog", .{
|
const zlog_module = b.addModule("zlog", .{
|
||||||
// In this case the main source file is merely a path, however, in more
|
// In this case the main source file is merely a path, however, in more
|
||||||
// complicated build scripts, this could be a generated file.
|
// complicated build scripts, this could be a generated file.
|
||||||
@@ -39,7 +34,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "ztime", .module = ztime_dependency.module("ztime") },
|
|
||||||
.{ .name = "build_options", .module = options_module },
|
.{ .name = "build_options", .module = options_module },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -51,7 +45,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "ztime", .module = ztime_dependency.module("ztime") },
|
|
||||||
.{ .name = "zlog", .module = zlog_module },
|
.{ .name = "zlog", .module = zlog_module },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -93,7 +86,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "ztime", .module = ztime_dependency.module("ztime") },
|
|
||||||
.{ .name = "build_options", .module = options_module },
|
.{ .name = "build_options", .module = options_module },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
.{
|
.{
|
||||||
.name = .zlog,
|
.name = .zlog,
|
||||||
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
|
.version = "0.16.0",
|
||||||
.fingerprint = 0x55b82e3347a594e8,
|
.fingerprint = 0x55b82e3347a594e8,
|
||||||
// version name should match the zig version except for the last number,
|
.minimum_zig_version = "0.16.0-dev.463+f624191f9",
|
||||||
// which stands for the version inside a given zig version
|
.dependencies = .{},
|
||||||
.version = "0.15.0",
|
|
||||||
.dependencies = .{
|
|
||||||
.ztime = .{
|
|
||||||
.url = "git+https://gitea.yves-biener.de/yves-biener/ztime#6a2f853f36aa55146e089d7c0c3f041012c0f8fe",
|
|
||||||
.hash = "ztime-0.0.0-e3nBJL5xAAC_guuqgVnOZncpvwH76NnKRC7JJ_zTF9rV",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.minimum_zig_version = "0.15.0",
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ const Struct = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
|
// without explict scope (i.e. `.default` scope)
|
||||||
|
std.log.info("Without explicit scope or `.default` scope", .{});
|
||||||
// initialize zlog with the scope of `main`
|
// initialize zlog with the scope of `main`
|
||||||
const log = std.log.scoped(.main);
|
const log = std.log.scoped(.main);
|
||||||
// NOTE the scope of `default` will result in no scoping being printed in
|
// NOTE the scope of `default` will result in no scoping being printed in
|
||||||
@@ -63,7 +65,7 @@ pub fn main() void {
|
|||||||
const void_tagged_union: TaggedUnion = .nothing;
|
const void_tagged_union: TaggedUnion = .nothing;
|
||||||
const uniun: Union = .{ .boolean = true };
|
const uniun: Union = .{ .boolean = true };
|
||||||
|
|
||||||
// NOTE: Depending on the optimization target some of these log messages
|
// NOTE Depending on the optimization target some of these log messages
|
||||||
// will not show, which is inline with the behavior of `std.log`.
|
// will not show, which is inline with the behavior of `std.log`.
|
||||||
log.debug("Debug message {any}", .{int_var});
|
log.debug("Debug message {any}", .{int_var});
|
||||||
log.info("Info message {any}", .{array_var});
|
log.info("Info message {any}", .{array_var});
|
||||||
@@ -75,6 +77,7 @@ pub fn main() void {
|
|||||||
log.err("Error message {any}", .{tagged_union});
|
log.err("Error message {any}", .{tagged_union});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply *zlog* logging options to `std` logger
|
||||||
pub const std_options = zlog.std_options;
|
pub const std_options = zlog.std_options;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|||||||
63
src/root.zig
63
src/root.zig
@@ -5,18 +5,17 @@ fn logFn(
|
|||||||
comptime format: []const u8,
|
comptime format: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) void {
|
) void {
|
||||||
const prefix = if (scope == .default) ": " else "(\x1b[2m" ++ @tagName(scope) ++ "\x1b[0m): ";
|
var buf: [128]u8 = undefined;
|
||||||
// TODO this should only happen for messages that are written to *stderr* in files the escape codes are only annoying
|
if (comptime build_options.file.len > 0) {
|
||||||
|
const prefix = if (scope == .default) " " else "(" ++ @tagName(scope) ++ ") ";
|
||||||
const level_txt = switch (comptime message_level) {
|
const level_txt = switch (comptime message_level) {
|
||||||
.err => "[\x1b[38;5;9merror\x1b[0m]",
|
.err => "[error]",
|
||||||
.warn => "[\x1b[38;5;11mwarning\x1b[0m]",
|
.warn => "[warning]",
|
||||||
.info => "[\x1b[38;5;10minfo\x1b[0m]",
|
.info => "[info]",
|
||||||
.debug => "[\x1b[38;5;12mdebug\x1b[0m]",
|
.debug => "[debug]",
|
||||||
};
|
};
|
||||||
// TODO let user configure the format he wants to use for logging and use a pretty good default one?
|
// TODO let user configure the format he wants to use for logging and use a pretty good default one?
|
||||||
const complete_format = level_txt ++ prefix ++ format ++ "\n";
|
const complete_format = level_txt ++ prefix ++ format ++ "\n";
|
||||||
var buf: [128]u8 = undefined;
|
|
||||||
if (comptime build_options.file.len > 0) {
|
|
||||||
// TODO use common logging format, such that in integrates well with other logging frameworks
|
// TODO use common logging format, such that in integrates well with other logging frameworks
|
||||||
// (i.e. golang's logger, log4j, etc.)
|
// (i.e. golang's logger, log4j, etc.)
|
||||||
const fd = std.posix.open(build_options.file, .{
|
const fd = std.posix.open(build_options.file, .{
|
||||||
@@ -28,32 +27,63 @@ fn logFn(
|
|||||||
|
|
||||||
var buffer = std.fs.File.Writer.init(.{ .handle = fd }, &buf);
|
var buffer = std.fs.File.Writer.init(.{ .handle = fd }, &buf);
|
||||||
var writer = &buffer.interface;
|
var writer = &buffer.interface;
|
||||||
defer writer.flush() catch {};
|
defer writer.flush() catch unreachable;
|
||||||
|
|
||||||
log_writing(writer, complete_format, args);
|
log_writing(writer, complete_format, false, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime build_options.stderr) {
|
if (comptime build_options.stderr) {
|
||||||
|
const prefix = if (scope == .default) " " else "(\x1b[2m" ++ @tagName(scope) ++ "\x1b[0m) ";
|
||||||
|
const level_txt = switch (comptime message_level) {
|
||||||
|
.err => "[\x1b[38;5;9merror\x1b[0m]",
|
||||||
|
.warn => "[\x1b[38;5;11mwarning\x1b[0m]",
|
||||||
|
.info => "[\x1b[38;5;10minfo\x1b[0m]",
|
||||||
|
.debug => "[\x1b[38;5;12mdebug\x1b[0m]",
|
||||||
|
};
|
||||||
|
const complete_format = level_txt ++ prefix ++ format ++ "\n";
|
||||||
|
|
||||||
var buffer = stderr().writer(&buf);
|
var buffer = stderr().writer(&buf);
|
||||||
var writer = &buffer.interface;
|
var writer = &buffer.interface;
|
||||||
defer writer.flush() catch {};
|
defer writer.flush() catch unreachable;
|
||||||
|
|
||||||
std.debug.lockStdErr();
|
std.debug.lockStdErr();
|
||||||
defer std.debug.unlockStdErr();
|
defer std.debug.unlockStdErr();
|
||||||
|
|
||||||
log_writing(writer, complete_format, args);
|
log_writing(writer, complete_format, true, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn log_writing(writer: *std.Io.Writer, comptime format: []const u8, args: anytype) void {
|
inline fn log_writing(writer: *std.Io.Writer, comptime format: []const u8, comptime ansi: bool, args: anytype) void {
|
||||||
nosuspend {
|
nosuspend {
|
||||||
if (build_options.timestamp) log_timestamp(writer);
|
if (build_options.timestamp) log_timestamp(writer, ansi);
|
||||||
writer.print(format, args) catch return;
|
writer.print(format, args) catch return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn log_timestamp(writer: anytype) void {
|
/// Prepend the current timestamp in the following format:
|
||||||
writer.print("[\x1b[1m{any}\x1b[0m] ", .{ztime.DateTime.now()}) catch return;
|
/// `<year>/<month>/<day> <hour>:<minute>:<sec> `
|
||||||
|
///
|
||||||
|
/// NOTE all information are displayed using numbers
|
||||||
|
inline fn log_timestamp(writer: anytype, comptime ansi: bool) void {
|
||||||
|
const time = std.posix.clock_gettime(.REALTIME) catch @panic("Cannot request timestamp");
|
||||||
|
const epoch_secs: std.time.epoch.EpochSeconds = .{ .secs = @intCast(time.sec) };
|
||||||
|
const day_secs = epoch_secs.getDaySeconds();
|
||||||
|
const year_and_day = epoch_secs.getEpochDay().calculateYearDay();
|
||||||
|
const month_and_day = year_and_day.calculateMonthDay();
|
||||||
|
writer.print(
|
||||||
|
if (comptime ansi)
|
||||||
|
"\x1b[1m{d}/{d:0>2}/{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}\x1b[0m "
|
||||||
|
else
|
||||||
|
"{d}/{d:0>2}/{d:0>2} {d:0>2}:{d:0>2}:{d:0>2} ",
|
||||||
|
.{
|
||||||
|
year_and_day.year,
|
||||||
|
month_and_day.month.numeric(),
|
||||||
|
month_and_day.day_index + 1,
|
||||||
|
day_secs.getHoursIntoDay(),
|
||||||
|
day_secs.getMinutesIntoHour(),
|
||||||
|
day_secs.getSecondsIntoMinute(),
|
||||||
|
},
|
||||||
|
) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_format(object: anytype, comptime format: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
|
pub fn pretty_format(object: anytype, comptime format: []const u8, options: fmt.FormatOptions, writer: anytype) !void {
|
||||||
@@ -197,4 +227,3 @@ const stderr = fs.File.stderr;
|
|||||||
const log = std.log;
|
const log = std.log;
|
||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const ztime = if (build_options.timestamp) @import("ztime") else null;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user