From 216cc5107261daeb106564da0d834f5f87f56f69 Mon Sep 17 00:00:00 2001 From: Tristan Pemble Date: Mon, 24 Nov 2025 14:31:53 -0800 Subject: [PATCH 1/4] std.log: add generic tracing support with std.log.span --- lib/std/Io/Threaded.zig | 12 ++ lib/std/log.zig | 293 ++++++++++++++++++++++++++++++++++++++++ lib/std/std.zig | 13 ++ 3 files changed, 318 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0e5c12504b15..1927e6f39415 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -16,6 +16,9 @@ const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; const assert = std.debug.assert; const posix = std.posix; +const log = std.log; +const AnySpan = std.log.AnySpan; +const ExecutorId = std.log.ExecutorId; /// Thread-safe. allocator: Allocator, @@ -94,6 +97,7 @@ const Closure = struct { start: Start, node: std.SinglyLinkedList.Node = .{}, cancel_tid: CancelId, + span: ?*AnySpan = null, const Start = *const fn (*Closure) void; @@ -203,6 +207,9 @@ fn join(t: *Threaded) void { fn worker(t: *Threaded) void { defer t.wait_group.finish(); + const executor_id: ExecutorId = .createAndEnter(); + defer executor_id.exit(); + t.mutex.lock(); defer t.mutex.unlock(); @@ -210,7 +217,9 @@ fn worker(t: *Threaded) void { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); const closure: *Closure = @fieldParentPtr("node", closure_node); + if (closure.span) |span| span.link(); closure.start(closure); + if (closure.span) |span| span.unlink(); t.mutex.lock(); t.busy_count -= 1; } @@ -478,6 +487,7 @@ const AsyncClosure = struct { .closure = .{ .cancel_tid = .none, .start = start, + .span = log.current_span, }, .func = func, .context_alignment = context_alignment, @@ -623,6 +633,7 @@ const GroupClosure = struct { // Even though we already know the task is canceled, we must still // run the closure in case there are side effects. } + current_closure = closure; gc.func(group, gc.contextPointer()); current_closure = null; @@ -664,6 +675,7 @@ const GroupClosure = struct { .closure = .{ .cancel_tid = .none, .start = start, + .span = log.current_span, }, .t = t, .group = group, diff --git a/lib/std/log.zig b/lib/std/log.zig index 9568f9ba5211..3b15ddf7ae14 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -113,6 +113,285 @@ pub fn defaultLog( stderr.print(format ++ "\n", args) catch return; } +fn trace( + comptime level: Level, + comptime scope: @EnumLiteral(), + any_span: *AnySpan, + event: SpanEvent, + executor: ExecutorId, + comptime format: []const u8, + args: anytype, +) void { + if (comptime !logEnabled(level, scope)) return; + + std.options.traceFn(level, scope, any_span, event, executor, format, args); +} + +pub fn defaultTrace( + comptime level: Level, + comptime scope: @EnumLiteral(), + any_span: *AnySpan, + event: SpanEvent, + executor: ExecutorId, + comptime format: []const u8, + args: anytype, +) void { + _ = any_span; + _ = executor; + switch (event) { + .begin => log(level, scope, "[>>] " ++ format, args), + .end => log(level, scope, "[<<] " ++ format, args), + else => {}, + } +} + +/// This thread's current executor ID. +pub threadlocal var current_executor: ExecutorId = .none; + +/// This thread's currently tracked span. +pub threadlocal var current_span: ?*AnySpan = null; + +/// A monotonically increasing, unique identifier for an "executor". Depending +/// on the `std.Io` implementation in use, an executor can be a native thread, a +/// green thread, etc. +pub const ExecutorId = enum(u64) { + none = std.math.maxInt(u64), + _, + + /// A globally unique, monotonically increasing executor identifier. + var next_id: std.atomic.Value(u64) = .init(0); + + pub fn create() ExecutorId { + return @enumFromInt(next_id.fetchAdd(1, .monotonic)); + } + + pub fn createAndEnter() ExecutorId { + const self = create(); + self.enter(); + return self; + } + + /// Acquires a new executor ID, replacing the current one on this thread. + pub fn enter(self: ExecutorId) void { + std.debug.assert(current_executor == .none); + current_executor = self; + } + + /// Places this executor as the current executor on this thread, asserting + /// that the current executor is what we expect. + pub fn compareExchange(self: ExecutorId, expected: ExecutorId) void { + std.debug.assert(current_executor == expected); + current_executor = self; + } + + /// Releases this executor ID, setting the current executor ID to `.none`. + pub fn exit(self: ExecutorId) void { + std.debug.assert(current_executor == self); + current_executor = .none; + } +}; + +/// A monotonically increasing, unique identifier for an individual `Span`. Only +/// unique within the application's lifetime. +pub const SpanId = enum(u64) { + none = std.math.maxInt(u64), + _, + + /// A globally unique, monotonically increasing `Span` identifier. + var next_id: std.atomic.Value(u64) = .init(0); + + /// Acquires a new `SpanId`. + pub fn createNext() SpanId { + return @enumFromInt(next_id.fetchAdd(1, .monotonic)); + } +}; + +/// A type erased interface to a `Span`. +pub const AnySpan = struct { + linkFn: *const fn (*AnySpan) void, + unlinkFn: *const fn (*AnySpan) void, + + pub const empty: AnySpan = .{ + .linkFn = struct { + fn link(_: *AnySpan) void {} + }.link, + .unlinkFn = struct { + fn unlink(_: *AnySpan) void {} + }.unlink, + }; + + pub fn link(self: *AnySpan) void { + self.linkFn(self); + } + + pub fn unlink(self: *AnySpan) void { + self.unlinkFn(self); + } + + /// Get a pointer to the typed `Span`. + pub fn asSpan( + any: *AnySpan, + comptime level: Level, + comptime scope: @EnumLiteral(), + comptime format: []const u8, + comptime Args: type, + ) *Span(level, scope, format, Args) { + const self: *Span(level, scope, format, Args) = @fieldParentPtr("any", any); + return self; + } +}; + +/// A tracing span that is generic over the log level and scope, which can be +/// configured via `std.Options`. When the scope or level has been disabled, this +/// type becomes zero sized and all methods become no-ops, allowing tracing to be +/// compiled out. +/// +/// ```zig +/// const span = log.span(.info, "begin request", .{}); +/// span.begin(); +/// defer span.end(); +/// ``` +/// +/// An initialized span can be moved where it needs to be, but once it has begun, +/// moving or copying a span is illegal behavior. +/// +/// When dealing with concurrent execution, to properly track spans in the task +/// that is running concurrently, the original span must be linked and unlinked to +/// the new executor. +/// +/// ```zig +/// std.thread.Spawn(.{}, struct { +/// fn myFn(span: *AnySpan) void { +/// span.link(); +/// defer span.unlink(); +/// +/// // new spans on this thread are linked to the original +/// } +/// }.myFn, .{ &span.any }); +/// ``` +pub fn Span( + comptime level: Level, + comptime scope: @EnumLiteral(), + comptime format: []const u8, + comptime Args: type, +) type { + return if (!logEnabled(level, scope)) struct { + const Self = @This(); + + id: void, + address: void, + args: void, + prev: void, + any: AnySpan = .empty, + userdata: void, + + pub fn init(_: usize, _: Args) Self { + return .{}; + } + pub fn begin(_: *Self) void {} + pub fn end(_: *Self) void {} + pub fn enter(_: *Self) void {} + pub fn exit(_: *Self) void {} + pub fn link(_: *Self) void {} + pub fn unlink(_: *Self) void {} + } else struct { + const Self = @This(); + + id: SpanId, + address: usize, + args: Args, + /// A pointer to the parent span on this executor, forming a linked list. + prev: ?*AnySpan, + /// The `AnySpan` type erased interface. + any: AnySpan, + /// Custom userdata for the span, defined on `std.Options`. + userdata: std.options.SpanUserdata, + + /// Initializes the `Span`, but does not begin it. + pub fn init(address: usize, args: Args) Self { + return .{ + .id = .createNext(), + .address = address, + .args = args, + .prev = null, + .any = .{ + .linkFn = struct { + fn link(any: *AnySpan) void { + const self = any.asSpan(level, scope, format, Args); + return self.link(); + } + }.link, + .unlinkFn = struct { + fn unlink(any: *AnySpan) void { + const self = any.asSpan(level, scope, format, Args); + return self.unlink(); + } + }.unlink, + }, + .userdata = undefined, + }; + } + + /// Begins the span on this thread's current executor. + pub fn begin(self: *Self) void { + trace(level, scope, &self.any, .begin, current_executor, format, self.args); + self.prev = current_span; + current_span = &self.any; + } + + /// Ends the span on this thread's current executor. + pub fn end(self: *Self) void { + std.debug.assert(current_span == &self.any); + trace(level, scope, &self.any, .end, current_executor, format, self.args); + current_span = self.prev; + self.* = undefined; + } + + /// Signals that this span has entered execution on the current executor. Replaces this thread's + /// current span with itself. + pub fn enter(self: *Self) void { + std.debug.assert(current_span == null); + current_span = &self.any; + trace(level, scope, &self.any, .enter, current_executor, format, self.args); + } + + /// Signals that this span has exited execution on the current executor. Replaces this thread's + /// current span with the previous span. + pub fn exit(self: *Self) void { + std.debug.assert(current_span == &self.any); + trace(level, scope, &self.any, .exit, current_executor, format, self.args); + current_span = null; + } + + /// Links the span to this thread's current executor. + pub fn link(self: *Self) void { + std.debug.assert(current_span == null); + trace(level, scope, &self.any, .link, current_executor, format, self.args); + } + + /// Unlinks the span from this thread's current executor. + pub fn unlink(self: *Self) void { + std.debug.assert(current_span == null); + trace(level, scope, &self.any, .unlink, current_executor, format, self.args); + } + }; +} + +pub const SpanEvent = enum { + /// The span has begun work on this thread's current executor. + begin, + /// The span has ended work on this thread's current executor. + end, + /// A child executor has been linked to the span. + link, + /// A child executor has been unlinked from the span. + unlink, + /// The span itself has moved to an executor. + enter, + /// The span itself has moved from an executor. + exit, +}; + /// Returns a scoped logging namespace that logs all messages using the scope /// provided here. pub fn scoped(comptime scope: @EnumLiteral()) type { @@ -155,6 +434,16 @@ pub fn scoped(comptime scope: @EnumLiteral()) type { ) void { log(.debug, scope, format, args); } + + /// Initialize a new tracing span. The span must be explicitly begun + /// and ended after initialization. + pub fn span( + comptime level: Level, + comptime format: []const u8, + args: anytype, + ) Span(level, scope, format, @TypeOf(args)) { + return .init(@returnAddress(), args); + } }; } @@ -180,3 +469,7 @@ pub const info = default.info; /// Log a debug message using the default scope. This log level is intended to /// be used for messages which are only useful for debugging. pub const debug = default.debug; + +/// Initialize a new tracing span using the default scope. The span must be +/// explicitly begun and ended after initialization. +pub const span = default.span; diff --git a/lib/std/std.zig b/lib/std/std.zig index 5c500d3f55d3..9a9730560ccd 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -129,6 +129,19 @@ pub const Options = struct { args: anytype, ) void = log.defaultLog, + /// Userdata associated with every std.log.Span. + SpanUserdata: type = void, + + traceFn: fn ( + comptime level: log.Level, + comptime scope: @EnumLiteral(), + span: *log.AnySpan, + event: log.SpanEvent, + executor: log.ExecutorId, + comptime format: []const u8, + args: anytype, + ) void = log.defaultTrace, + /// Overrides `std.heap.page_size_min`. page_size_min: ?usize = null, /// Overrides `std.heap.page_size_max`. From f4aedd39be3be1bed89eceb963106631166f053c Mon Sep 17 00:00:00 2001 From: Tristan Pemble Date: Tue, 25 Nov 2025 18:05:08 -0800 Subject: [PATCH 2/4] fix mistakes in some docblocks --- lib/std/log.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/std/log.zig b/lib/std/log.zig index 3b15ddf7ae14..ae9817a16d8a 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -171,7 +171,8 @@ pub const ExecutorId = enum(u64) { return self; } - /// Acquires a new executor ID, replacing the current one on this thread. + /// Places this executor as the current for this thread, asserting that + /// there is no existing executor in use. pub fn enter(self: ExecutorId) void { std.debug.assert(current_executor == .none); current_executor = self; @@ -355,8 +356,8 @@ pub fn Span( trace(level, scope, &self.any, .enter, current_executor, format, self.args); } - /// Signals that this span has exited execution on the current executor. Replaces this thread's - /// current span with the previous span. + /// Signals that this span has exited execution on the current executor. Removes the thread's + /// current span. pub fn exit(self: *Self) void { std.debug.assert(current_span == &self.any); trace(level, scope, &self.any, .exit, current_executor, format, self.args); From 456e7065edef9fb1b5000b1321bd201b75b83d29 Mon Sep 17 00:00:00 2001 From: Tristan Pemble Date: Tue, 25 Nov 2025 18:08:21 -0800 Subject: [PATCH 3/4] place trace call after current_span is set --- lib/std/log.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/log.zig b/lib/std/log.zig index ae9817a16d8a..113012d8f33c 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -335,9 +335,9 @@ pub fn Span( /// Begins the span on this thread's current executor. pub fn begin(self: *Self) void { - trace(level, scope, &self.any, .begin, current_executor, format, self.args); self.prev = current_span; current_span = &self.any; + trace(level, scope, &self.any, .begin, current_executor, format, self.args); } /// Ends the span on this thread's current executor. From 7ea1d030bcf3411ad77c8db4a732d01f53378504 Mon Sep 17 00:00:00 2001 From: Tristan Pemble Date: Wed, 26 Nov 2025 17:10:23 -0800 Subject: [PATCH 4/4] wip --- lib/std/Io/Threaded.zig | 18 +-- lib/std/log.zig | 295 ++++++++++++++++------------------------ lib/std/std.zig | 11 +- 3 files changed, 134 insertions(+), 190 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1927e6f39415..5c80b8003f28 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -17,8 +17,8 @@ const Alignment = std.mem.Alignment; const assert = std.debug.assert; const posix = std.posix; const log = std.log; -const AnySpan = std.log.AnySpan; -const ExecutorId = std.log.ExecutorId; +const Span = std.log.Span; +const Executor = std.log.Executor; /// Thread-safe. allocator: Allocator, @@ -97,7 +97,7 @@ const Closure = struct { start: Start, node: std.SinglyLinkedList.Node = .{}, cancel_tid: CancelId, - span: ?*AnySpan = null, + span: Span = .empty, const Start = *const fn (*Closure) void; @@ -207,8 +207,8 @@ fn join(t: *Threaded) void { fn worker(t: *Threaded) void { defer t.wait_group.finish(); - const executor_id: ExecutorId = .createAndEnter(); - defer executor_id.exit(); + const executor: Executor = .create(); + defer executor.exit(); t.mutex.lock(); defer t.mutex.unlock(); @@ -217,9 +217,9 @@ fn worker(t: *Threaded) void { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); const closure: *Closure = @fieldParentPtr("node", closure_node); - if (closure.span) |span| span.link(); + executor.link(&closure.span); closure.start(closure); - if (closure.span) |span| span.unlink(); + executor.unlink(&closure.span); t.mutex.lock(); t.busy_count -= 1; } @@ -487,7 +487,7 @@ const AsyncClosure = struct { .closure = .{ .cancel_tid = .none, .start = start, - .span = log.current_span, + .span = log.thread_span, }, .func = func, .context_alignment = context_alignment, @@ -675,7 +675,7 @@ const GroupClosure = struct { .closure = .{ .cancel_tid = .none, .start = start, - .span = log.current_span, + .span = log.thread_span, }, .t = t, .group = group, diff --git a/lib/std/log.zig b/lib/std/log.zig index 113012d8f33c..aad633116715 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -25,7 +25,10 @@ //! ``` const std = @import("std.zig"); +const assert = std.debug.assert; const builtin = @import("builtin"); +const SourceLocation = std.builtin.SourceLocation; +const SpanUserdata = std.options.SpanUserdata; pub const Level = enum { /// Error: something has gone wrong. This might be recoverable or might @@ -114,86 +117,96 @@ pub fn defaultLog( } fn trace( - comptime level: Level, + comptime level: log.Level, comptime scope: @EnumLiteral(), - any_span: *AnySpan, - event: SpanEvent, - executor: ExecutorId, - comptime format: []const u8, - args: anytype, + comptime src: SourceLocation, + comptime event: log.SpanEvent, + executor: log.Executor, + span: *log.Span, ) void { if (comptime !logEnabled(level, scope)) return; - - std.options.traceFn(level, scope, any_span, event, executor, format, args); + std.options.traceFn(level, scope, event, executor, span); } pub fn defaultTrace( - comptime level: Level, + comptime level: log.Level, comptime scope: @EnumLiteral(), - any_span: *AnySpan, - event: SpanEvent, - executor: ExecutorId, - comptime format: []const u8, - args: anytype, + comptime src: SourceLocation, + comptime event: log.SpanEvent, + executor: log.Executor, + span: *log.Span, ) void { - _ = any_span; + _ = level; + _ = scope; + _ = event; _ = executor; - switch (event) { - .begin => log(level, scope, "[>>] " ++ format, args), - .end => log(level, scope, "[<<] " ++ format, args), - else => {}, - } + _ = span; } -/// This thread's current executor ID. -pub threadlocal var current_executor: ExecutorId = .none; +/// This thread's currently running executor. +pub threadlocal var thread_executor: Executor = .none; -/// This thread's currently tracked span. -pub threadlocal var current_span: ?*AnySpan = null; +/// This thread's currently excuting span. +pub threadlocal var thread_span: Span = .empty; -/// A monotonically increasing, unique identifier for an "executor". Depending -/// on the `std.Io` implementation in use, an executor can be a native thread, a -/// green thread, etc. -pub const ExecutorId = enum(u64) { +/// An executor can be a thread, fiber, or whatever the std.Io implementation +/// decides it is. Internally it is represented by a monotonically increasing +/// integer, but that is an implementation detail, and should not be relied +/// upon. +pub const Executor = enum(u64) { none = std.math.maxInt(u64), _, /// A globally unique, monotonically increasing executor identifier. var next_id: std.atomic.Value(u64) = .init(0); - pub fn create() ExecutorId { + pub fn create() Executor { return @enumFromInt(next_id.fetchAdd(1, .monotonic)); } - pub fn createAndEnter() ExecutorId { - const self = create(); - self.enter(); - return self; + pub fn link(self: Executor, span_: *Span) void { + if (span_.id == .none) return; + span_.vtable.linkFn(span_, self); } - /// Places this executor as the current for this thread, asserting that - /// there is no existing executor in use. - pub fn enter(self: ExecutorId) void { - std.debug.assert(current_executor == .none); - current_executor = self; + pub fn unlink(self: Executor, span_: *Span) void { + if (span_.id == .none) return; + span_.vtable.unlinkFn(span_, self); } +}; + +/// An execution span. +pub const Span = struct { + id: SpanId, + vtable: *const VTable, + userdata: SpanUserdata, + + pub const empty: Span = .{ + .id = .none, + .vtable = undefined, + .userdata = undefined, + }; - /// Places this executor as the current executor on this thread, asserting - /// that the current executor is what we expect. - pub fn compareExchange(self: ExecutorId, expected: ExecutorId) void { - std.debug.assert(current_executor == expected); - current_executor = self; + pub const VTable = struct { + suspendFn: *const fn (self: *Span) void, + resumeFn: *const fn (self: *Span) void, + linkFn: *const fn (self: *Span, executor: Executor) void, + unlinkFn: *const fn (self: *Span, executor: Executor) void, + }; + + pub fn @"suspend"(self: *Span) Span { + if (self.id == .none) return empty; + return self.vtable.suspendFn(self); } - /// Releases this executor ID, setting the current executor ID to `.none`. - pub fn exit(self: ExecutorId) void { - std.debug.assert(current_executor == self); - current_executor = .none; + pub fn @"resume"(self: *Span) void { + if (self.id == .none) return; + self.vtable.resumeFn(self); } }; -/// A monotonically increasing, unique identifier for an individual `Span`. Only -/// unique within the application's lifetime. +/// Internally this is represented by a monotonically increasing integer, but +/// that is an implementation detail, and should not be relied upon. pub const SpanId = enum(u64) { none = std.math.maxInt(u64), _, @@ -207,41 +220,6 @@ pub const SpanId = enum(u64) { } }; -/// A type erased interface to a `Span`. -pub const AnySpan = struct { - linkFn: *const fn (*AnySpan) void, - unlinkFn: *const fn (*AnySpan) void, - - pub const empty: AnySpan = .{ - .linkFn = struct { - fn link(_: *AnySpan) void {} - }.link, - .unlinkFn = struct { - fn unlink(_: *AnySpan) void {} - }.unlink, - }; - - pub fn link(self: *AnySpan) void { - self.linkFn(self); - } - - pub fn unlink(self: *AnySpan) void { - self.unlinkFn(self); - } - - /// Get a pointer to the typed `Span`. - pub fn asSpan( - any: *AnySpan, - comptime level: Level, - comptime scope: @EnumLiteral(), - comptime format: []const u8, - comptime Args: type, - ) *Span(level, scope, format, Args) { - const self: *Span(level, scope, format, Args) = @fieldParentPtr("any", any); - return self; - } -}; - /// A tracing span that is generic over the log level and scope, which can be /// configured via `std.Options`. When the scope or level has been disabled, this /// type becomes zero sized and all methods become no-ops, allowing tracing to be @@ -270,127 +248,95 @@ pub const AnySpan = struct { /// } /// }.myFn, .{ &span.any }); /// ``` -pub fn Span( - comptime level: Level, - comptime scope: @EnumLiteral(), - comptime format: []const u8, - comptime Args: type, -) type { +pub fn ScopedSpan(comptime level: Level, comptime scope: @EnumLiteral(), comptime src: std.builtin.SourceLocation) type { return if (!logEnabled(level, scope)) struct { const Self = @This(); - id: void, - address: void, - args: void, - prev: void, - any: AnySpan = .empty, - userdata: void, - - pub fn init(_: usize, _: Args) Self { + pub fn begin() Self { return .{}; } - pub fn begin(_: *Self) void {} pub fn end(_: *Self) void {} - pub fn enter(_: *Self) void {} - pub fn exit(_: *Self) void {} - pub fn link(_: *Self) void {} - pub fn unlink(_: *Self) void {} } else struct { const Self = @This(); id: SpanId, - address: usize, - args: Args, - /// A pointer to the parent span on this executor, forming a linked list. - prev: ?*AnySpan, - /// The `AnySpan` type erased interface. - any: AnySpan, - /// Custom userdata for the span, defined on `std.Options`. - userdata: std.options.SpanUserdata, - - /// Initializes the `Span`, but does not begin it. - pub fn init(address: usize, args: Args) Self { - return .{ - .id = .createNext(), - .address = address, - .args = args, - .prev = null, - .any = .{ - .linkFn = struct { - fn link(any: *AnySpan) void { - const self = any.asSpan(level, scope, format, Args); - return self.link(); - } - }.link, - .unlinkFn = struct { - fn unlink(any: *AnySpan) void { - const self = any.asSpan(level, scope, format, Args); - return self.unlink(); - } - }.unlink, + prev: Span, + + pub fn begin() Self { + const id: SpanId = .createNext(); + const prev = thread_span; + thread_span = .{ + .id = id, + .vtable = &.{ + .suspendFn = @"suspend", + .resumeFn = @"resume", + .linkFn = link, + .unlinkFn = unlink, }, .userdata = undefined, }; + trace(level, scope, src, .begin, thread_executor, &thread_span); + return .{ .id = id, .prev = prev }; } - /// Begins the span on this thread's current executor. - pub fn begin(self: *Self) void { - self.prev = current_span; - current_span = &self.any; - trace(level, scope, &self.any, .begin, current_executor, format, self.args); - } - - /// Ends the span on this thread's current executor. pub fn end(self: *Self) void { - std.debug.assert(current_span == &self.any); - trace(level, scope, &self.any, .end, current_executor, format, self.args); - current_span = self.prev; + // Swap the previous span, that we stored from begin, + assert(thread_span.id != .none); + assert(thread_span.id == self.id); + trace(level, scope, src, .end, thread_executor, &thread_span); + thread_span = self.span; self.* = undefined; } - /// Signals that this span has entered execution on the current executor. Replaces this thread's - /// current span with itself. - pub fn enter(self: *Self) void { - std.debug.assert(current_span == null); - current_span = &self.any; - trace(level, scope, &self.any, .enter, current_executor, format, self.args); + fn @"suspend"(span_: *Span) Span { + assert(span_.id != .none); + assert(thread_span.id != .none); + assert(thread_span.id == span_.id); + trace(level, scope, src, .@"suspend", thread_executor, &thread_span); + const suspended = span_.*; + thread_span = .empty; + return suspended; } - /// Signals that this span has exited execution on the current executor. Removes the thread's - /// current span. - pub fn exit(self: *Self) void { - std.debug.assert(current_span == &self.any); - trace(level, scope, &self.any, .exit, current_executor, format, self.args); - current_span = null; + fn @"resume"(span_: *Span) void { + assert(span_.id != .none); + assert(thread_span.id == .none); + thread_span = span_.*; + trace(level, scope, src, .@"resume", thread_executor, &thread_span); } - /// Links the span to this thread's current executor. - pub fn link(self: *Self) void { - std.debug.assert(current_span == null); - trace(level, scope, &self.any, .link, current_executor, format, self.args); + fn link(span_: *Span, executor: Executor) void { + assert(executor != .none); + assert(span_.id != .none); + assert(thread_executor == .none); + assert(thread_span.id == .none); + thread_executor = executor; + trace(level, scope, src, .link, thread_executor, span_); } - /// Unlinks the span from this thread's current executor. - pub fn unlink(self: *Self) void { - std.debug.assert(current_span == null); - trace(level, scope, &self.any, .unlink, current_executor, format, self.args); + fn unlink(span_: *Span, executor: Executor) void { + assert(thread_executor != .none); + assert(thread_executor == executor); + assert(thread_span.id == .none); + trace(level, scope, src, .unlink, thread_executor, span_); + thread_executor = null; } }; } pub const SpanEvent = enum { - /// The span has begun work on this thread's current executor. + /// An executor has begun work on this span. begin, - /// The span has ended work on this thread's current executor. + /// An executor has completed work on this span. end, - /// A child executor has been linked to the span. + /// An executor has suspended work on this span. + @"suspend", + /// An executor has resumed work on this span. + @"resume", + /// An executor has started work requested within the span. link, - /// A child executor has been unlinked from the span. + /// An executor has stopped work requested within the span. unlink, - /// The span itself has moved to an executor. - enter, - /// The span itself has moved from an executor. - exit, }; /// Returns a scoped logging namespace that logs all messages using the scope @@ -440,10 +386,9 @@ pub fn scoped(comptime scope: @EnumLiteral()) type { /// and ended after initialization. pub fn span( comptime level: Level, - comptime format: []const u8, - args: anytype, - ) Span(level, scope, format, @TypeOf(args)) { - return .init(@returnAddress(), args); + comptime src: SourceLocation, + ) ScopedSpan(level, scope, src) { + return .begin(); } }; } diff --git a/lib/std/std.zig b/lib/std/std.zig index 9a9730560ccd..15f61814a61d 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -129,17 +129,16 @@ pub const Options = struct { args: anytype, ) void = log.defaultLog, - /// Userdata associated with every std.log.Span. + /// Per-span userdata. Copied on every span/executor context change; keep it small. SpanUserdata: type = void, traceFn: fn ( comptime level: log.Level, comptime scope: @EnumLiteral(), - span: *log.AnySpan, - event: log.SpanEvent, - executor: log.ExecutorId, - comptime format: []const u8, - args: anytype, + comptime src: SourceLocation, + comptime event: log.SpanEvent, + executor: log.Executor, + span: *log.Span, ) void = log.defaultTrace, /// Overrides `std.heap.page_size_min`.