Compare commits
48 Commits
eb7e78697c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b3c8a3ab1c | |||
| 29c6e48d86 | |||
| 40ced30a57 | |||
| 57631f1905 | |||
|
b868bb1300
|
|||
|
4147c47f7e
|
|||
|
e813a3e195
|
|||
|
6f62c61897
|
|||
|
f065c08e91
|
|||
|
f43034cea9
|
|||
|
69da9265b8
|
|||
|
bd33f9c8f9
|
|||
|
a211aafed6
|
|||
|
411a6dc358
|
|||
|
cd99e5b4d3
|
|||
|
ab0898f9c4
|
|||
|
4e9fddc593
|
|||
|
b43361da73
|
|||
|
4714ec2bf9
|
|||
|
a209fd6df5
|
|||
| 5b1a987a5b | |||
|
eb2ea38e34
|
|||
| d13fcce836 | |||
| c1108f3f3b | |||
| 22bff91df9 | |||
| a21d84cfcf | |||
| dd98e63bcb | |||
| 04795091a8 | |||
| 79f45c4cfd | |||
| 6f784817f2 | |||
| e1473cde04 | |||
| 2969c4354f | |||
| db10d51429 | |||
| b5641960e8 | |||
| cbc1da831a | |||
| 5a7c41254b | |||
| f33ff72690 | |||
| ba84450417 | |||
| df515b8cad | |||
| 06752299be | |||
| dda2199706 | |||
| ace97e4bfc | |||
| 87cd904c70 | |||
| 73991389f6 | |||
| f93e27fc8d | |||
| 145d54f09f | |||
| 7889a6ec7a | |||
| b03c770b59 |
33
.gitea/workflows/release.yaml
Normal file
33
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Release Zig Application
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_run:
|
||||
workflows: [Zig Project Action]
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release zig project
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup zig installation
|
||||
uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
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
|
||||
run: zig build --release=fast
|
||||
- name: Release build artifacts
|
||||
uses: akkuman/gitea-release-action@v1.3.2
|
||||
with:
|
||||
files: |-
|
||||
./zig-out/bin/**
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Run Tests
|
||||
name: Zig Project Action
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -6,21 +6,22 @@ on:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
run:
|
||||
name: Lint, Spell-check and test zig project
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v1
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup zig installation
|
||||
uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.13.0
|
||||
- run: zig build test
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v1
|
||||
version: latest
|
||||
- name: Lint check
|
||||
run: zig fmt --check .
|
||||
- name: Spell checking
|
||||
uses: crate-ci/typos@v1.39.0
|
||||
with:
|
||||
version: 0.13.0
|
||||
- run: zig fmt --check .
|
||||
config: ./.typos-config
|
||||
- name: Run tests
|
||||
run: zig build test
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
log
|
||||
|
||||
5
.typos-config
Normal file
5
.typos-config
Normal file
@@ -0,0 +1,5 @@
|
||||
[files]
|
||||
extend-exclude = []
|
||||
|
||||
[default.extend-words]
|
||||
WRONLY = "WRONLY"
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 yves-biener
|
||||
Copyright (c) 2024 Yves Biener
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
130
README.md
130
README.md
@@ -1,17 +1,53 @@
|
||||
# zlog
|
||||
|
||||
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`).
|
||||
Standard Library log wrapper. `zlog` provides adjusted `std.log` output and a pretty print function for easy overwriting of user defined types.
|
||||
|
||||
## 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 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
|
||||
|
||||
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`):
|
||||
The following snippet shows an example usage of `zlog` to change the default log format and add pretty printing to the user defined types:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
const zlog = @import("zlog");
|
||||
const Union = union {
|
||||
int: i32,
|
||||
boolean: bool,
|
||||
|
||||
// use this to overwrite the default log format of `std.log`
|
||||
pub const std_options = zlog.std_options;
|
||||
// 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) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
const TaggedUnion = union(enum) {
|
||||
one: i16,
|
||||
two: u32,
|
||||
three: []const u8,
|
||||
nothing,
|
||||
|
||||
// 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) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
const Options = enum {
|
||||
a,
|
||||
@@ -19,7 +55,7 @@ const Options = enum {
|
||||
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 {
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
@@ -29,63 +65,91 @@ const Struct = struct {
|
||||
b: bool = true,
|
||||
c: [5]u16 = .{ 1, 2, 3, 4, 5 },
|
||||
d: []const u8 = "string",
|
||||
e: Options = Options.b,
|
||||
e: Options = .b,
|
||||
f: TaggedUnion = .{ .one = -5 },
|
||||
|
||||
// same function as above...
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void {
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
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`
|
||||
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`)
|
||||
// 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 option_var: Options = .a;
|
||||
const struct_var: Struct = .{};
|
||||
const tagged_union: TaggedUnion = .{
|
||||
.three = "Three",
|
||||
};
|
||||
const void_tagged_union: TaggedUnion = .nothing;
|
||||
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`.
|
||||
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.warn("Warning message {any}", .{void_tagged_union});
|
||||
log.warn("Warning message {any}", .{uniun});
|
||||
log.err("Error message {any}", .{struct_var});
|
||||
log.err("Error message {any}", .{tagged_union});
|
||||
}
|
||||
|
||||
// apply *zlog* logging options to `std` logger
|
||||
pub const std_options = zlog.std_options;
|
||||
|
||||
const std = @import("std");
|
||||
const zlog = @import("zlog");
|
||||
```
|
||||
|
||||
This will result in the following output:
|
||||
|
||||
```
|
||||
[2024-08-28 22:22] debug(main): Debug message 42
|
||||
[2024-08-28 22:22] info(main): Info message { 1, 2, 3, 4 }
|
||||
[2024-08-28 22:22] info(main): Info message "This is a test"
|
||||
[2024-08-28 22:22] warning(main): Warning message main.Options = enum {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
} = a
|
||||
[2024-08-28 22:22] 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,
|
||||
}
|
||||
2025/11/02 11:57:29 [info] Without explicit scope or `.default` scope
|
||||
2025/11/02 11:57:29 [debug](main) Debug message 42
|
||||
2025/11/02 11:57:29 [info](main) Info message { 1, 2, 3, 4 }
|
||||
2025/11/02 11:57:29 [info](main) Info message "This is a test"
|
||||
2025/11/02 11:57:29 [warning](main) Warning message .a
|
||||
2025/11/02 11:57:29 [warning](main) Warning message .{ .nothing = void }
|
||||
2025/11/02 11:57:29 [warning](main) Warning message .{ ... }
|
||||
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/11/02 11:57:29 [error](main) Error message .{ .three = { 84, 104, 114, 101, 101 } }
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
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.
|
||||
- *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.
|
||||
|
||||
---
|
||||
|
||||
> [!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:
|
||||
```zig
|
||||
// assume log is already defined before (with the corresponding scope)
|
||||
const port = port: {
|
||||
errdefer |err| log.err("failed to read the port number: {}", .{err});
|
||||
|
||||
var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined;
|
||||
const len = try process.stdout.?.readAll(&buf);
|
||||
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 using the *file* build option the *ansi* control characters are not omitted when logging to the file.
|
||||
|
||||
36
build.zig
36
build.zig
@@ -4,11 +4,15 @@ const std = @import("std");
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// build options to customize the log message formating
|
||||
// build options to customize the log message formatting
|
||||
const include_timestamp = b.option(bool, "timestamp", "Enable inclusion of timestamps in log messages (default: true)") orelse true;
|
||||
const stderr = b.option(bool, "stderr", "Print all log messages to stderr (default: true)") orelse true;
|
||||
const file = b.option([]const u8, "file", "Print all log messages to the provided file.") orelse "";
|
||||
|
||||
const options = b.addOptions();
|
||||
options.addOption(bool, "timestamp", include_timestamp);
|
||||
options.addOption(bool, "stderr", stderr);
|
||||
options.addOption([]const u8, "file", file);
|
||||
|
||||
const options_module = options.createModule();
|
||||
|
||||
@@ -26,20 +30,25 @@ pub fn build(b: *std.Build) void {
|
||||
const zlog_module = b.addModule("zlog", .{
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = b.path("src/zlog.zig"),
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = include_timestamp, // uses c std library <time.h>
|
||||
.imports = &.{
|
||||
.{ .name = "build_options", .module = options_module },
|
||||
},
|
||||
});
|
||||
zlog_module.addImport("build_options", options_module);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zlog",
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "zlog", .module = zlog_module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
exe.root_module.addImport("zlog", zlog_module);
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
@@ -72,9 +81,14 @@ pub fn build(b: *std.Build) void {
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/zlog.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "build_options", .module = options_module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.{
|
||||
.name = "zlog",
|
||||
// version name should match the zig version except for the last number,
|
||||
// which stands for the version inside a given zig version
|
||||
.version = "0.13.0",
|
||||
.minimum_zig_version = "0.13.0",
|
||||
.name = .zlog,
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
.version = "0.16.0",
|
||||
.fingerprint = 0x55b82e3347a594e8,
|
||||
.minimum_zig_version = "0.16.0-dev.463+f624191f9",
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
||||
54
src/main.zig
54
src/main.zig
@@ -1,7 +1,24 @@
|
||||
const std = @import("std");
|
||||
const zlog = @import("zlog");
|
||||
const Union = union {
|
||||
int: i32,
|
||||
boolean: bool,
|
||||
|
||||
pub const std_options = zlog.std_options;
|
||||
// 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) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
const TaggedUnion = union(enum) {
|
||||
one: i16,
|
||||
two: u32,
|
||||
three: []const u8,
|
||||
nothing,
|
||||
|
||||
// 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) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
const Options = enum {
|
||||
a,
|
||||
@@ -9,7 +26,7 @@ const Options = enum {
|
||||
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 {
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
@@ -19,32 +36,49 @@ const Struct = struct {
|
||||
b: bool = true,
|
||||
c: [5]u16 = .{ 1, 2, 3, 4, 5 },
|
||||
d: []const u8 = "string",
|
||||
e: Options = Options.b,
|
||||
e: Options = .b,
|
||||
f: TaggedUnion = .{ .one = -5 },
|
||||
|
||||
// same function as above...
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) anyerror!void {
|
||||
pub fn format(value: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try zlog.pretty_format(value, fmt, options, writer);
|
||||
}
|
||||
};
|
||||
|
||||
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`
|
||||
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`)
|
||||
// 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 option_var: Options = .a;
|
||||
const struct_var: Struct = .{};
|
||||
const tagged_union: TaggedUnion = .{
|
||||
.three = "Three",
|
||||
};
|
||||
const void_tagged_union: TaggedUnion = .nothing;
|
||||
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`.
|
||||
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.warn("Warning message {any}", .{void_tagged_union});
|
||||
log.warn("Warning message {any}", .{uniun});
|
||||
log.err("Error message {any}", .{struct_var});
|
||||
log.err("Error message {any}", .{tagged_union});
|
||||
}
|
||||
|
||||
// apply *zlog* logging options to `std` logger
|
||||
pub const std_options = zlog.std_options;
|
||||
|
||||
const std = @import("std");
|
||||
const zlog = @import("zlog");
|
||||
|
||||
228
src/root.zig
Normal file
228
src/root.zig
Normal file
@@ -0,0 +1,228 @@
|
||||
/// *zlog* defaultLog function replacement, which adjusts the surrounding contents of every `std.log` message.
|
||||
fn logFn(
|
||||
comptime message_level: log.Level,
|
||||
comptime scope: @Type(.enum_literal),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
var buf: [128]u8 = undefined;
|
||||
if (comptime build_options.file.len > 0) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
.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";
|
||||
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
|
||||
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, comptime ansi: bool, args: anytype) void {
|
||||
nosuspend {
|
||||
if (build_options.timestamp) log_timestamp(writer, ansi);
|
||||
writer.print(format, args) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend the current timestamp in the following format:
|
||||
/// `<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 {
|
||||
try inner_format(object, format, options, writer, 0);
|
||||
}
|
||||
|
||||
fn inner_format(object: anytype, comptime format: []const u8, options: 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(" = struct {\n");
|
||||
inline for (s.fields) |field| {
|
||||
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!
|
||||
// TODO pass along the already parsed formatting options too
|
||||
try inner_format(@field(object, field.name), format, options, writer, depth + 1);
|
||||
try writer.writeAll(",\n");
|
||||
}
|
||||
try writer.writeAll("\t" ** depth);
|
||||
try writer.writeAll("}");
|
||||
},
|
||||
.@"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));
|
||||
},
|
||||
.@"union" => |u| {
|
||||
try writer.writeAll(@typeName(Object));
|
||||
try writer.writeAll(" = union");
|
||||
if (u.tag_type) |Tag| {
|
||||
_ = Tag;
|
||||
try writer.writeAll("(enum)");
|
||||
}
|
||||
try writer.writeAll(" {\n");
|
||||
inline for (u.fields) |field| {
|
||||
try writer.writeAll("\t" ** (depth + 1));
|
||||
try writer.writeAll(field.name);
|
||||
if (@typeInfo(field.type) != .void) {
|
||||
try writer.writeAll(": ");
|
||||
try writer.writeAll(@typeName(field.type));
|
||||
}
|
||||
try writer.writeAll(",\n");
|
||||
}
|
||||
try writer.writeAll("\t" ** depth);
|
||||
try writer.writeAll("} = ");
|
||||
if (u.tag_type) |Tag| {
|
||||
try writer.writeAll(".{ .");
|
||||
try writer.writeAll(@tagName(object));
|
||||
try writer.writeAll(" = ");
|
||||
inline for (u.fields) |u_field| if (object == @field(Tag, u_field.name)) {
|
||||
try inner_format(@field(object, u_field.name), format, options, writer, depth + 1);
|
||||
};
|
||||
try writer.writeAll(" }");
|
||||
} else {
|
||||
// NOTE the value of a union (untagged) is displayed like this also in the standard library,
|
||||
// not sure if you can reflect the used variant (and its value)
|
||||
try fmt.format(writer, "@{x}", .{@intFromPtr(&object)});
|
||||
}
|
||||
},
|
||||
.pointer => |p| switch (p.size) {
|
||||
.slice => switch (@typeInfo(p.child)) {
|
||||
.int => |num| {
|
||||
if (num.signedness != .unsigned) {
|
||||
try fmt.format(writer, "[]{s}: {any}", .{ @typeName(p.child), object });
|
||||
} else {
|
||||
switch (num.bits) {
|
||||
8 => try fmt.format(writer, "\"{s}\"", .{object}),
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
},
|
||||
.c => switch (@typeInfo(p.child)) {
|
||||
.int => |num| {
|
||||
if (num.signedness != .unsigned) {
|
||||
try fmt.format(writer, "[*c]{s}: {any}", .{ @typeName(p.child), object });
|
||||
} else {
|
||||
switch (num.bits) {
|
||||
8 => try fmt.format(writer, "\"{s}\"", .{object}),
|
||||
else => try fmt.format(writer, "[*c]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
},
|
||||
.many => try fmt.format(writer, "[*]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
.one => try fmt.format(writer, "[1]{s}: {any}", .{ @typeName(p.child), object }),
|
||||
},
|
||||
.array => |a| switch (@typeInfo(a.child)) {
|
||||
.int => |num| {
|
||||
if (num.signedness != .unsigned) {
|
||||
try fmt.format(writer, "[]{s}: {any}", .{ @typeName(a.child), object });
|
||||
} else {
|
||||
switch (num.bits) {
|
||||
8 => try fmt.format(writer, "\"{s}\"", .{object}),
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(a.child), object }),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(a.child), object }),
|
||||
},
|
||||
.vector => |v| switch (@typeInfo(v.child)) {
|
||||
.int => |num| {
|
||||
if (num.signedness != .unsigned) {
|
||||
try fmt.format(writer, "[]{s}: {any}", .{ @typeName(v.child), object });
|
||||
} else {
|
||||
switch (num.bits) {
|
||||
8 => try fmt.format(writer, "\"{s}\"", .{object}),
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(v.child), object }),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => try fmt.format(writer, "[]{s}: {any}", .{ @typeName(v.child), object }),
|
||||
},
|
||||
else => try fmt.format(writer, format, .{object}),
|
||||
}
|
||||
}
|
||||
|
||||
pub const std_options: std.Options = .{
|
||||
.logFn = logFn,
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const fs = std.fs;
|
||||
const log = std.log;
|
||||
const fmt = std.fmt;
|
||||
const build_options = @import("build_options");
|
||||
96
src/zlog.zig
96
src/zlog.zig
@@ -1,96 +0,0 @@
|
||||
const build_options = @import("build_options");
|
||||
const c_time = if (build_options.timestamp) @cImport(@cInclude("time.h")) else null;
|
||||
const std = @import("std");
|
||||
|
||||
pub const std_options: std.Options = .{
|
||||
.logFn = logFn,
|
||||
};
|
||||
|
||||
// zlog defaultLog function replacement, which adjusts the surrounding contents of every std.log message.
|
||||
fn logFn(
|
||||
comptime message_level: std.log.Level,
|
||||
comptime scope: @Type(.EnumLiteral),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
// TODO: provide build time configuration to allow tweaking corresponding output
|
||||
// - change output file for writing messages to (default `stderr`)
|
||||
const level_txt = comptime message_level.asText();
|
||||
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
var bw = std.io.bufferedWriter(stderr);
|
||||
const writer = bw.writer();
|
||||
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
nosuspend {
|
||||
if (build_options.timestamp) {
|
||||
const curtime = c_time.time(null);
|
||||
const tm = c_time.localtime(&curtime);
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
_ = c_time.strftime(@ptrCast(&buf), 32, "%F %R", tm);
|
||||
writer.print("[{s}] ", .{buf}) catch return;
|
||||
}
|
||||
writer.print(level_txt ++ prefix ++ format ++ "\n", args) catch return;
|
||||
bw.flush() catch return;
|
||||
}
|
||||
}
|
||||
|
||||
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(" = struct {\n");
|
||||
inline for (s.fields) |field| {
|
||||
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!
|
||||
// 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("}");
|
||||
},
|
||||
.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});
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user