Skip to content

Commit 786d479

Browse files
committed
feat(log): structured logging with comptime dispatch, aligned with logforth
1 parent 4917e8d commit 786d479

File tree

11 files changed

+5849
-2
lines changed

11 files changed

+5849
-2
lines changed

build.zig

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,23 @@ pub fn build(b: *std.Build) void {
149149
module_bls.linkLibrary(dep_blst.artifact("blst"));
150150
b.modules.put(b.dupe("bls"), module_bls) catch @panic("OOM");
151151

152+
const module_ring_buffer = b.createModule(.{
153+
.root_source_file = b.path("src/ring_buffer.zig"),
154+
.target = target,
155+
.optimize = optimize,
156+
});
157+
b.modules.put(b.dupe("ring_buffer"), module_ring_buffer) catch @panic("OOM");
158+
159+
const module_log = b.createModule(.{
160+
.root_source_file = b.path("src/log/root.zig"),
161+
.target = target,
162+
.optimize = optimize,
163+
.imports = &.{
164+
.{ .name = "ring_buffer", .module = module_ring_buffer },
165+
},
166+
});
167+
b.modules.put(b.dupe("log"), module_log) catch @panic("OOM");
168+
152169
const module_state_transition = b.createModule(.{
153170
.root_source_file = b.path("src/state_transition/root.zig"),
154171
.target = target,
@@ -202,6 +219,29 @@ pub fn build(b: *std.Build) void {
202219
const tls_run_exe_metrics_stf = b.step("run:metrics_stf", "Run the metrics_stf executable");
203220
tls_run_exe_metrics_stf.dependOn(&run_exe_metrics_stf.step);
204221

222+
const module_log_smoke = b.createModule(.{
223+
.root_source_file = b.path("examples/log_smoke.zig"),
224+
.target = target,
225+
.optimize = optimize,
226+
});
227+
b.modules.put(b.dupe("log_smoke"), module_log_smoke) catch @panic("OOM");
228+
229+
const exe_log_smoke = b.addExecutable(.{
230+
.name = "log_smoke",
231+
.root_module = module_log_smoke,
232+
});
233+
234+
const install_exe_log_smoke = b.addInstallArtifact(exe_log_smoke, .{});
235+
236+
const tls_install_exe_log_smoke = b.step("build-exe:log_smoke", "Install the log_smoke executable");
237+
tls_install_exe_log_smoke.dependOn(&install_exe_log_smoke.step);
238+
b.getInstallStep().dependOn(&install_exe_log_smoke.step);
239+
240+
const run_exe_log_smoke = b.addRunArtifact(exe_log_smoke);
241+
if (b.args) |args| run_exe_log_smoke.addArgs(args);
242+
const tls_run_exe_log_smoke = b.step("run:log_smoke", "Run the log_smoke executable");
243+
tls_run_exe_log_smoke.dependOn(&run_exe_log_smoke.step);
244+
205245
const module_download_spec_tests = b.createModule(.{
206246
.root_source_file = b.path("test/spec/download_spec_tests.zig"),
207247
.target = target,
@@ -681,6 +721,20 @@ pub fn build(b: *std.Build) void {
681721
tls_run_test_bls.dependOn(&run_test_bls.step);
682722
tls_run_test.dependOn(&run_test_bls.step);
683723

724+
const test_log = b.addTest(.{
725+
.name = "log",
726+
.root_module = module_log,
727+
.filters = b.option([][]const u8, "log.filters", "log test filters") orelse &[_][]const u8{},
728+
});
729+
const install_test_log = b.addInstallArtifact(test_log, .{});
730+
const tls_install_test_log = b.step("build-test:log", "Install the log test");
731+
tls_install_test_log.dependOn(&install_test_log.step);
732+
733+
const run_test_log = b.addRunArtifact(test_log);
734+
const tls_run_test_log = b.step("test:log", "Run the log test");
735+
tls_run_test_log.dependOn(&run_test_log.step);
736+
tls_run_test.dependOn(&run_test_log.step);
737+
684738
const test_state_transition = b.addTest(.{
685739
.name = "state_transition",
686740
.root_module = module_state_transition,
@@ -723,6 +777,20 @@ pub fn build(b: *std.Build) void {
723777
tls_run_test_metrics_stf.dependOn(&run_test_metrics_stf.step);
724778
tls_run_test.dependOn(&run_test_metrics_stf.step);
725779

780+
const test_log_smoke = b.addTest(.{
781+
.name = "log_smoke",
782+
.root_module = module_log_smoke,
783+
.filters = b.option([][]const u8, "log_smoke.filters", "log_smoke test filters") orelse &[_][]const u8{},
784+
});
785+
const install_test_log_smoke = b.addInstallArtifact(test_log_smoke, .{});
786+
const tls_install_test_log_smoke = b.step("build-test:log_smoke", "Install the log_smoke test");
787+
tls_install_test_log_smoke.dependOn(&install_test_log_smoke.step);
788+
789+
const run_test_log_smoke = b.addRunArtifact(test_log_smoke);
790+
const tls_run_test_log_smoke = b.step("test:log_smoke", "Run the log_smoke test");
791+
tls_run_test_log_smoke.dependOn(&run_test_log_smoke.step);
792+
tls_run_test.dependOn(&run_test_log_smoke.step);
793+
726794
const test_download_spec_tests = b.addTest(.{
727795
.name = "download_spec_tests",
728796
.root_module = module_download_spec_tests,
@@ -1088,6 +1156,9 @@ pub fn build(b: *std.Build) void {
10881156
module_metrics_stf.addImport("config", module_config);
10891157
module_metrics_stf.addImport("preset", module_preset);
10901158
module_metrics_stf.addImport("httpz", dep_httpz.module("httpz"));
1159+
module_metrics_stf.addImport("log", module_log);
1160+
1161+
module_log_smoke.addImport("log", module_log);
10911162

10921163
module_download_spec_tests.addImport("spec_test_options", options_module_spec_test_options);
10931164

examples/log_smoke.zig

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! Log integration smoke example.
2+
//!
3+
//! Demonstrates the V2 structured Logger API, std.log bridge,
4+
//! all three async builder configs, and all three layout formats.
5+
//!
6+
//! Run:
7+
//! zig build run:log_smoke
8+
//!
9+
//! Expected output: structured log lines on stderr from each dispatcher/layout.
10+
11+
const std = @import("std");
12+
const log_mod = @import("log");
13+
14+
/// Re-export std_options so std.log calls route through the V2 pipeline.
15+
pub const std_options = log_mod.std_options;
16+
17+
const Logger = log_mod.Logger;
18+
const Attr = log_mod.Attr;
19+
const Dispatch = log_mod.Dispatch;
20+
const Dispatcher = log_mod.Dispatcher;
21+
const LevelFilter = log_mod.LevelFilter;
22+
const FlushableWriter = log_mod.FlushableWriter;
23+
const WriterAppend = log_mod.WriterAppend;
24+
const TextLayout = log_mod.TextLayout;
25+
const JsonLayout = log_mod.JsonLayout;
26+
const LogfmtLayout = log_mod.LogfmtLayout;
27+
28+
/// Helper: emit a standard set of log lines through `log`.
29+
fn emitSample(log: anytype) void {
30+
log.err("error msg", .{ .code = @as(u64, 500) });
31+
log.warn("warn msg", .{});
32+
log.info("info msg", .{ .slot = @as(u64, 42) });
33+
log.debug("debug msg", .{ .flag = true });
34+
}
35+
36+
pub fn main() !void {
37+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
38+
defer _ = gpa.deinit();
39+
const allocator = gpa.allocator();
40+
41+
// ── 1. Default (sync stderr, TextLayout) ─────────────────
42+
43+
std.debug.print("\n=== 1. Default (sync stderr, TextLayout) ===\n", .{});
44+
{
45+
const std_log = std.log.scoped(.smoke);
46+
std_log.err("std.log bridge err", .{});
47+
std_log.info("std.log bridge info", .{});
48+
49+
const log = Logger(4).init(.smoke, log_mod.getGlobalDispatcher());
50+
emitSample(log);
51+
52+
const child = log.with(&[_]Attr{Attr.str("module", "fc")});
53+
child.info("child log", .{ .epoch = @as(u64, 7) });
54+
}
55+
56+
// ── 2. Console builder (async stderr, TextLayout) ────────
57+
58+
std.debug.print("\n=== 2. Console builder (async stderr, TextLayout) ===\n", .{});
59+
{
60+
try log_mod.initConsoleDispatcher(allocator, .{ .min_level = .debug });
61+
defer log_mod.deinitGlobalDispatcher();
62+
63+
const log = Logger(4).init(.smoke, log_mod.getGlobalDispatcher());
64+
emitSample(log);
65+
log_mod.flushGlobalDispatcher();
66+
}
67+
68+
// ── 3. File builder (async rolling file, TextLayout) ─────
69+
70+
std.debug.print("\n=== 3. File builder (async rolling file, TextLayout) ===\n", .{});
71+
{
72+
try log_mod.initFileDispatcher(allocator, .{
73+
.min_level = .debug,
74+
.dir = std.fs.cwd(),
75+
.base_name = "smoke.log",
76+
.rolling = .{ .rotation = .never, .max_bytes = 1024 * 1024 },
77+
});
78+
defer log_mod.deinitGlobalDispatcher();
79+
80+
const log = Logger(4).init(.smoke, log_mod.getGlobalDispatcher());
81+
emitSample(log);
82+
log_mod.flushGlobalDispatcher();
83+
std.debug.print(" → wrote smoke.log\n", .{});
84+
}
85+
86+
// ── 4. Combined builder (async stderr + file, TextLayout) ─
87+
88+
std.debug.print("\n=== 4. Combined builder (async stderr + file, TextLayout) ===\n", .{});
89+
{
90+
try log_mod.initCombinedDispatcher(allocator, .{
91+
.min_level = .debug,
92+
.dir = std.fs.cwd(),
93+
.base_name = "smoke_combined.log",
94+
.rolling = .{ .rotation = .never, .max_bytes = 1024 * 1024 },
95+
});
96+
defer log_mod.deinitGlobalDispatcher();
97+
98+
const log = Logger(4).init(.smoke, log_mod.getGlobalDispatcher());
99+
emitSample(log);
100+
log_mod.flushGlobalDispatcher();
101+
std.debug.print(" → wrote smoke_combined.log\n", .{});
102+
}
103+
104+
// ── 5. Custom pipeline — JsonLayout (sync stderr) ────────
105+
106+
std.debug.print("\n=== 5. JsonLayout (sync stderr) ===\n", .{});
107+
{
108+
const WA = WriterAppend(JsonLayout);
109+
const D = Dispatch(struct { LevelFilter }, struct {}, struct { WA });
110+
const Disp = Dispatcher(struct { D });
111+
112+
var dispatcher = Disp{
113+
.dispatches = .{
114+
D{
115+
.filters = .{LevelFilter.init(.debug)},
116+
.diagnostics = .{},
117+
.appenders = .{WA.init(allocator, JsonLayout{}, FlushableWriter.stderrWriter())},
118+
},
119+
},
120+
};
121+
122+
const log = Logger(4).init(.smoke, dispatcher.any());
123+
emitSample(log);
124+
}
125+
126+
// ── 6. Custom pipeline — LogfmtLayout (sync stderr) ──────
127+
128+
std.debug.print("\n=== 6. LogfmtLayout (sync stderr) ===\n", .{});
129+
{
130+
const WA = WriterAppend(LogfmtLayout);
131+
const D = Dispatch(struct { LevelFilter }, struct {}, struct { WA });
132+
const Disp = Dispatcher(struct { D });
133+
134+
var dispatcher = Disp{
135+
.dispatches = .{
136+
D{
137+
.filters = .{LevelFilter.init(.debug)},
138+
.diagnostics = .{},
139+
.appenders = .{WA.init(allocator, LogfmtLayout{}, FlushableWriter.stderrWriter())},
140+
},
141+
},
142+
};
143+
144+
const log = Logger(4).init(.smoke, dispatcher.any());
145+
emitSample(log);
146+
}
147+
148+
// cleanup generated files
149+
std.fs.cwd().deleteFile("smoke.log") catch {};
150+
std.fs.cwd().deleteFile("smoke_combined.log") catch {};
151+
152+
std.debug.print("\n=== All 6 configurations OK ===\n", .{});
153+
}

0 commit comments

Comments
 (0)