Compare commits

...

15 Commits

Author SHA1 Message Date
b3c8a3ab1c fix: correct ordering of commands; append file logs into corresponding provided file build parameter
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 56s
2026-01-17 21:01:32 +01:00
29c6e48d86 doc: update documentation to no longer mention the warning about the zig master version
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 54s
2026-01-17 11:00:07 +01:00
40ced30a57 mod: adjust to latest release of zig for file writing option
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 55s
2026-01-17 10:55:09 +01:00
57631f1905 mod: pin implementation to latest release version of zig
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m30s
2026-01-17 10:51:29 +01:00
b868bb1300 fix: correct namespace
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m1s
2026-01-06 23:07:41 +01:00
4147c47f7e fix: add missing Io parameter for different configuration option
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m0s
2026-01-06 23:05:51 +01:00
e813a3e195 mod: bump zig master version
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m8s
2026-01-06 22:04:34 +01:00
6f62c61897 doc: correct code snippet for build.zig's inclusion of zlog
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m5s
2025-11-25 19:34:21 +01:00
f065c08e91 mod: bump action dependencies
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m53s
2025-11-05 22:44:25 +01:00
f43034cea9 fix(lint): correct typos
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m1s
2025-11-02 13:17:52 +01:00
69da9265b8 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
2025-11-02 13:09:34 +01:00
bd33f9c8f9 doc: update README; update comments regarding open tasks and further features
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 54s
2025-11-01 00:38:34 +01:00
a211aafed6 doc: remove unnecessary comment
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 54s
2025-11-01 00:29:41 +01:00
411a6dc358 mod: support appending through posix file handle
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m21s
2025-11-01 00:01:50 +01:00
cd99e5b4d3 doc: correct alert blocks 2025-10-26 21:52:15 +01:00
9 changed files with 121 additions and 108 deletions

View File

@@ -17,7 +17,13 @@ jobs:
- name: Setup zig installation - name: Setup zig installation
uses: mlugg/setup-zig@v2 uses: mlugg/setup-zig@v2
with: with:
version: master version: latest
- name: Lint check
run: zig fmt --check .
- name: Spell checking
uses: crate-ci/typos@v1.39.0
with:
config: ./.typos-config
- name: Run tests - name: Run tests
run: zig build --release=fast run: zig build --release=fast
- name: Release build artifacts - name: Release build artifacts

View File

@@ -16,11 +16,11 @@ jobs:
- name: Setup zig installation - name: Setup zig installation
uses: mlugg/setup-zig@v2 uses: mlugg/setup-zig@v2
with: with:
version: master version: latest
- name: Lint check - name: Lint check
run: zig fmt --check . run: zig fmt --check .
- name: Spell checking - name: Spell checking
uses: crate-ci/typos@v1.25.0 uses: crate-ci/typos@v1.39.0
with: with:
config: ./.typos-config config: ./.typos-config
- name: Run tests - name: Run tests

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.zig-cache/ .zig-cache/
zig-out/ zig-out/
log

View File

@@ -1,2 +1,5 @@
[files] [files]
extend-exclude = [] extend-exclude = []
[default.extend-words]
WRONLY = "WRONLY"

View File

@@ -2,8 +2,25 @@
Standard Library log wrapper. `zlog` provides adjusted `std.log` output and a pretty print function for easy overwriting of user defined types. Standard Library log wrapper. `zlog` provides adjusted `std.log` output and a pretty print function for easy overwriting of user defined types.
> [!CAUTION] ## Install
> Only builds using the zig master version are tested to work.
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 dependency to any module in your *build.zig*:
```zig
const zlog_dependency = b.dependency("zlog", .{
.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
@@ -58,6 +75,8 @@ const Struct = struct {
}; };
pub fn main() void { pub fn main() void {
// without explicit 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 +94,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 +106,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,62 +116,30 @@ const zlog = @import("zlog");
This will result in the following output: This will result in the following output:
``` ```
[2025-07-17 19:41:43] [debug](main): Debug message 42 2025/11/02 11:57:29 [info] Without explicit scope or `.default` scope
[2025-07-17 19:41:43] [info](main): Info message { 1, 2, 3, 4 } 2025/11/02 11:57:29 [debug](main) Debug message 42
[2025-07-17 19:41:43] [info](main): Info message "This is a test" 2025/11/02 11:57:29 [info](main) Info message { 1, 2, 3, 4 }
[2025-07-17 19:41:43] [warning](main): 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 } }
[2025-07-17 19:41:43] [warning](main): 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 }
[2025-07-17 19:41:43] [warning](main): Warning message main.Union = union {
int: i32,
boolean: bool,
} = @7fffd1c71048
[2025-07-17 19:41:43] [error](main): 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 },
}
[2025-07-17 19:41:43] [error](main): 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.
> [!CAUTION] - *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.
> Currently not working as log output is not appended and only the last log message will be in the resulting log file! This is a not-yet-implemented feature of the standard library of zig! See this [issue](https://github.com/ziglang/zig/issues/14375) for more details.
> For now you should instead leave this option as it is and pipe the corresponding stderr outputs to a logfile instead.
## 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
@@ -164,4 +152,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.

View 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 },
}, },
}), }),

View File

@@ -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",

View File

@@ -46,6 +46,8 @@ const Struct = struct {
}; };
pub fn main() void { pub fn main() void {
// without explicit 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");

View File

@@ -5,11 +5,36 @@ fn logFn(
comptime format: []const u8, comptime format: []const u8,
args: anytype, args: anytype,
) void { ) void {
// TODO provide build time configuration to allow tweaking corresponding output var buf: [128]u8 = undefined;
// - change output file for writing messages to (default `stderr`) if (comptime build_options.file.len > 0) {
// - write into own file for each level? const prefix = if (scope == .default) " " else "(" ++ @tagName(scope) ++ ") ";
const level_txt = switch (comptime message_level) {
.err => "[error]",
.warn => "[warning]",
.info => "[info]",
.debug => "[debug]",
};
// 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";
// TODO use common logging format, such that in integrates well with other logging frameworks
// (i.e. golang's logger, log4j, etc.)
const fd = std.posix.open(build_options.file, .{
.CREAT = true,
.APPEND = true,
.ACCMODE = .WRONLY,
}, 0o600) catch @panic("Could not append to log file");
defer std.posix.close(fd);
const prefix = if (scope == .default) ": " else "(\x1b[2m" ++ @tagName(scope) ++ "\x1b[0m): "; var file: fs.File = .{ .handle = fd };
var buffer = file.writer(&buf);
var writer = &buffer.interface;
defer writer.flush() catch unreachable;
log_writing(writer, complete_format, false, args);
}
if (comptime build_options.stderr) {
const prefix = if (scope == .default) " " else "(\x1b[2m" ++ @tagName(scope) ++ "\x1b[0m) ";
const level_txt = switch (comptime message_level) { const level_txt = switch (comptime message_level) {
.err => "[\x1b[38;5;9merror\x1b[0m]", .err => "[\x1b[38;5;9merror\x1b[0m]",
.warn => "[\x1b[38;5;11mwarning\x1b[0m]", .warn => "[\x1b[38;5;11mwarning\x1b[0m]",
@@ -17,45 +42,49 @@ fn logFn(
.debug => "[\x1b[38;5;12mdebug\x1b[0m]", .debug => "[\x1b[38;5;12mdebug\x1b[0m]",
}; };
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 handle errors accordingly (i.e. panic?)
// NOTE with zig 0.13.0 there is currently no way to open files to append (except to use libc or talk directly to posix, which this lib should not have to do)
const file = std.fs.openFileAbsolute(build_options.file, .{
.mode = .read_write,
}) catch std.fs.createFileAbsolute(build_options.file, .{
.truncate = false,
}) catch @panic("Failed to open and/or create configured log file");
defer file.close();
var buffer = file.writer(&buf);
var writer = &buffer.interface;
defer writer.flush() catch {};
log_writing(writer, complete_format, args);
}
if (comptime build_options.stderr) {
var buffer = stderr().writer(&buf);
var writer = &buffer.interface;
defer writer.flush() catch {};
std.debug.lockStdErr(); std.debug.lockStdErr();
defer std.debug.unlockStdErr(); defer std.debug.unlockStdErr();
log_writing(writer, complete_format, args); var buffer = fs.File.stderr().writer(&buf);
var writer = &buffer.interface;
defer writer.flush() catch unreachable;
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 {
@@ -193,10 +222,7 @@ pub const std_options: std.Options = .{
}; };
const std = @import("std"); const std = @import("std");
const io = std.io;
const fs = std.fs; const fs = std.fs;
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;