Skip to content

Commit 266f087

Browse files
committed
feat(log): structured logging with comptime dispatch
1 parent 4917e8d commit 266f087

File tree

12 files changed

+5869
-5
lines changed

12 files changed

+5869
-5
lines changed

build.zig

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,20 @@ 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+
});
164+
b.modules.put(b.dupe("log"), module_log) catch @panic("OOM");
165+
152166
const module_state_transition = b.createModule(.{
153167
.root_source_file = b.path("src/state_transition/root.zig"),
154168
.target = target,
@@ -202,6 +216,29 @@ pub fn build(b: *std.Build) void {
202216
const tls_run_exe_metrics_stf = b.step("run:metrics_stf", "Run the metrics_stf executable");
203217
tls_run_exe_metrics_stf.dependOn(&run_exe_metrics_stf.step);
204218

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

721+
const test_ring_buffer = b.addTest(.{
722+
.name = "ring_buffer",
723+
.root_module = module_ring_buffer,
724+
.filters = b.option([][]const u8, "ring_buffer.filters", "ring_buffer test filters") orelse &[_][]const u8{},
725+
});
726+
const install_test_ring_buffer = b.addInstallArtifact(test_ring_buffer, .{});
727+
const tls_install_test_ring_buffer = b.step("build-test:ring_buffer", "Install the ring_buffer test");
728+
tls_install_test_ring_buffer.dependOn(&install_test_ring_buffer.step);
729+
730+
const run_test_ring_buffer = b.addRunArtifact(test_ring_buffer);
731+
const tls_run_test_ring_buffer = b.step("test:ring_buffer", "Run the ring_buffer test");
732+
tls_run_test_ring_buffer.dependOn(&run_test_ring_buffer.step);
733+
tls_run_test.dependOn(&run_test_ring_buffer.step);
734+
735+
const test_log = b.addTest(.{
736+
.name = "log",
737+
.root_module = module_log,
738+
.filters = b.option([][]const u8, "log.filters", "log test filters") orelse &[_][]const u8{},
739+
});
740+
const install_test_log = b.addInstallArtifact(test_log, .{});
741+
const tls_install_test_log = b.step("build-test:log", "Install the log test");
742+
tls_install_test_log.dependOn(&install_test_log.step);
743+
744+
const run_test_log = b.addRunArtifact(test_log);
745+
const tls_run_test_log = b.step("test:log", "Run the log test");
746+
tls_run_test_log.dependOn(&run_test_log.step);
747+
tls_run_test.dependOn(&run_test_log.step);
748+
684749
const test_state_transition = b.addTest(.{
685750
.name = "state_transition",
686751
.root_module = module_state_transition,
@@ -723,6 +788,20 @@ pub fn build(b: *std.Build) void {
723788
tls_run_test_metrics_stf.dependOn(&run_test_metrics_stf.step);
724789
tls_run_test.dependOn(&run_test_metrics_stf.step);
725790

791+
const test_log_smoke = b.addTest(.{
792+
.name = "log_smoke",
793+
.root_module = module_log_smoke,
794+
.filters = b.option([][]const u8, "log_smoke.filters", "log_smoke test filters") orelse &[_][]const u8{},
795+
});
796+
const install_test_log_smoke = b.addInstallArtifact(test_log_smoke, .{});
797+
const tls_install_test_log_smoke = b.step("build-test:log_smoke", "Install the log_smoke test");
798+
tls_install_test_log_smoke.dependOn(&install_test_log_smoke.step);
799+
800+
const run_test_log_smoke = b.addRunArtifact(test_log_smoke);
801+
const tls_run_test_log_smoke = b.step("test:log_smoke", "Run the log_smoke test");
802+
tls_run_test_log_smoke.dependOn(&run_test_log_smoke.step);
803+
tls_run_test.dependOn(&run_test_log_smoke.step);
804+
726805
const test_download_spec_tests = b.addTest(.{
727806
.name = "download_spec_tests",
728807
.root_module = module_download_spec_tests,
@@ -1066,6 +1145,8 @@ pub fn build(b: *std.Build) void {
10661145
module_ssz.addImport("hashing", module_hashing);
10671146
module_ssz.addImport("persistent_merkle_tree", module_persistent_merkle_tree);
10681147

1148+
module_log.addImport("ring_buffer", module_ring_buffer);
1149+
10691150
module_state_transition.addImport("build_options", options_module_build_options);
10701151
module_state_transition.addImport("ssz", module_ssz);
10711152
module_state_transition.addImport("config", module_config);
@@ -1088,6 +1169,9 @@ pub fn build(b: *std.Build) void {
10881169
module_metrics_stf.addImport("config", module_config);
10891170
module_metrics_stf.addImport("preset", module_preset);
10901171
module_metrics_stf.addImport("httpz", dep_httpz.module("httpz"));
1172+
module_metrics_stf.addImport("log", module_log);
1173+
1174+
module_log_smoke.addImport("log", module_log);
10911175

10921176
module_download_spec_tests.addImport("spec_test_options", options_module_spec_test_options);
10931177

@@ -1161,7 +1245,7 @@ pub fn build(b: *std.Build) void {
11611245
module_bindings.addImport("config", module_config);
11621246
module_bindings.addImport("fork_types", module_fork_types);
11631247
module_bindings.addImport("state_transition", module_state_transition);
1164-
module_bindings.addImport("zapi:napi", dep_zapi.module("napi"));
1248+
module_bindings.addImport("zapi:zapi", dep_zapi.module("zapi"));
11651249

11661250
module_int.addImport("config", module_config);
11671251
module_int.addImport("download_era_options", options_module_download_era_options);

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
.hash = "zig_yaml-0.1.0-C1161m2NAgDhth5OvjG1o1UNKcdo-XNfO82m10J1g4Cl",
2424
},
2525
.zapi = .{
26-
.url = "git+https://github.com/chainsafe/zapi#cf013457cdf0612a62a6d008d65505ff79e85596",
27-
.hash = "napi_z-0.1.0-iqJb7-4oAgBkIQFWFEDazD0-G9NRBQZaEY0fwTB7Aqbu",
26+
.url = "git+https://github.com/chainsafe/zapi#5a51cf21121372451bd56af97db896f474bce384",
27+
.hash = "zapi-0.1.0-rIqzUTM-AgDqeGAe3zmoh8_y6CtuBra5RE_BJVmUFMfX",
2828
},
2929
.zbench = .{
3030
.url = "git+https://github.com/hendriknielaender/zBench#d8c7dd485306b88b757d52005614ebcb0a336942",

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)