Compare commits
46 Commits
7889a6ec7a
...
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 |
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:
|
||||
|
||||
|
||||
119
README.md
119
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,71 +65,81 @@ 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.
|
||||
- _stderr_ (default: `true`): Print log messages to stderr. **NOTE**: 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 0.13.0! See this [issue](https://github.com/ziglang/zig/issues/14375) for more details.
|
||||
- *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.
|
||||
|
||||
## 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:
|
||||
```zig
|
||||
@@ -106,3 +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);
|
||||
};
|
||||
```
|
||||
- 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.
|
||||
|
||||
32
build.zig
32
build.zig
@@ -4,7 +4,7 @@ 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 "";
|
||||
@@ -30,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
|
||||
@@ -76,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");
|
||||
122
src/zlog.zig
122
src/zlog.zig
@@ -1,122 +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`)
|
||||
// write into own file for each level?
|
||||
const level_txt = comptime message_level.asText();
|
||||
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||
const fmt = level_txt ++ prefix ++ format ++ "\n";
|
||||
if (comptime build_options.file.len != 0) blk: {
|
||||
// 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 break :blk;
|
||||
defer file.close();
|
||||
|
||||
var buffer = std.io.bufferedWriter(file.writer());
|
||||
defer buffer.flush() catch {};
|
||||
|
||||
const writer = buffer.writer();
|
||||
log_writing(writer, fmt, args);
|
||||
}
|
||||
|
||||
if (comptime build_options.stderr) {
|
||||
var buffer = std.io.bufferedWriter(std.io.getStdErr().writer());
|
||||
defer buffer.flush() catch {};
|
||||
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
|
||||
const writer = buffer.writer();
|
||||
log_writing(writer, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
fn log_writing(writer: anytype, comptime fmt: []const u8, args: anytype) void {
|
||||
nosuspend {
|
||||
if (build_options.timestamp) log_timestamp(writer);
|
||||
writer.print(fmt, args) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
fn log_timestamp(writer: anytype) void {
|
||||
const curtime = c_time.time(null);
|
||||
const tm = c_time.localtime(&curtime);
|
||||
|
||||
var buffer: [32]u8 = undefined;
|
||||
_ = c_time.strftime(@ptrCast(&buffer), 32, "%F %R", tm);
|
||||
writer.print("[{s}] ", .{buffer}) 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