diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-12-09 18:44:39 -0800 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-12-23 22:15:09 -0800 |
| commit | 78d262d96ee6200c7a6bc0a41fe536d263c24d92 (patch) | |
| tree | cfc5dbe215a58d9001f90f1efc1e11d907d669f4 /lib | |
| parent | 03526c59d4e2a00f83347cf06c741a3ed4fec520 (diff) | |
| download | zig-78d262d96ee6200c7a6bc0a41fe536d263c24d92.tar.gz zig-78d262d96ee6200c7a6bc0a41fe536d263c24d92.zip | |
std: WIP: debug-level stderr writing
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/Io.zig | 31 | ||||
| -rw-r--r-- | lib/std/Io/Dir.zig | 5 | ||||
| -rw-r--r-- | lib/std/Io/Threaded.zig | 32 | ||||
| -rw-r--r-- | lib/std/Progress.zig | 149 | ||||
| -rw-r--r-- | lib/std/debug.zig | 73 | ||||
| -rw-r--r-- | lib/std/log.zig | 16 |
6 files changed, 181 insertions, 125 deletions
diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 193998f037..833cc4ec0f 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -560,6 +560,13 @@ pub const net = @import("Io/net.zig"); userdata: ?*anyopaque, vtable: *const VTable, +/// This is the global, process-wide protection to coordinate stderr writes. +/// +/// The primary motivation for recursive mutex here is so that a panic while +/// stderr mutex is held still dumps the stack trace and other debug +/// information. +pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; + pub const VTable = struct { /// If it returns `null` it means `result` has been already populated and /// `await` will be a no-op. @@ -733,6 +740,10 @@ pub const VTable = struct { netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, + + lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer, + tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer, + unlockStderrWriter: *const fn (?*anyopaque) void, }; pub const Cancelable = error{ @@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) { else => unreachable, } } + +/// For doing application-level writes to the standard error stream. +/// Coordinates also with debug-level writes that are ignorant of Io interface +/// and implementations. When this returns, `stderr_thread_mutex` will be +/// locked. +/// +/// See also: +/// * `tryLockStderrWriter` +pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer { + return io.vtable.lockStderrWriter(io.userdata, buffer); +} + +/// Same as `lockStderrWriter` but uncancelable and non-blocking. +pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer { + return io.vtable.tryLockStderrWriter(io.userdata, buffer); +} + +pub fn unlockStderrWriter(io: Io) void { + return io.vtable.unlockStderrWriter(io.userdata); +} diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 9c4a1f1df8..a9d28440eb 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -342,7 +342,7 @@ pub const Walker = struct { /// Recursively iterates over a directory. /// -/// `dir` must have been opened with `OpenOptions{.iterate = true}`. +/// `dir` must have been opened with `OpenOptions.iterate` set to `true`. /// /// `Walker.deinit` releases allocated memory and directory handles. /// @@ -350,7 +350,8 @@ pub const Walker = struct { /// /// `dir` will not be closed after walking it. /// -/// See also `walkSelectively`. +/// See also: +/// * `walkSelectively` pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { return .{ .inner = try walkSelectively(dir, allocator) }; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 639e5cf6cd..9d6d9f979e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default, use_copy_file_range: UseCopyFileRange = .default, use_fcopyfile: UseFcopyfile = .default, +stderr_writer: Io.Writer, + pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, @@ -9514,6 +9516,36 @@ fn netLookupFallible( return error.OptionUnsupported; } +fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + // Only global mutex since this is Threaded. + Io.stderr_thread_mutex.lock(); + if (is_windows) t.stderr_writer.file = .stderr(); + std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; + t.stderr_writer.flush() catch {}; + t.stderr_writer.buffer = buffer; + return &t.stderr_writer; +} + +fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + // Only global mutex since this is Threaded. + if (!Io.stderr_thread_mutex.tryLock()) return null; + std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {}; + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.flush() catch {}; + t.stderr_writer.buffer = buffer; + return &t.stderr_writer; +} + +fn unlockStderrWriter(userdata: ?*anyopaque) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + t.stderr_writer.flush() catch {}; + t.stderr_writer.end = 0; + t.stderr_writer.buffer = &.{}; + Io.stderr_thread_mutex.unlock(); +} + pub const PosixAddress = extern union { any: posix.sockaddr, in: posix.sockaddr.in, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index a839de8c4f..5528591620 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -13,16 +13,18 @@ const assert = std.debug.assert; const posix = std.posix; const Writer = std.Io.Writer; -/// `null` if the current node (and its children) should -/// not print on update() +/// Currently this API only supports this value being set to stderr, which +/// happens automatically inside `start`. terminal: Io.File, +io: Io, + terminal_mode: TerminalMode, -update_thread: ?std.Thread, +update_worker: ?Io.Future(void), /// Atomically set by SIGWINCH as well as the root done() function. -redraw_event: std.Thread.ResetEvent, +redraw_event: Io.ResetEvent, /// Indicates a request to shut down and reset global state. /// Accessed atomically. done: bool, @@ -95,9 +97,9 @@ pub const Options = struct { /// Must be at least 200 bytes. draw_buffer: []u8 = &default_draw_buffer, /// How many nanoseconds between writing updates to the terminal. - refresh_rate_ns: u64 = 80 * std.time.ns_per_ms, + refresh_rate_ns: Io.Duration = .fromMilliseconds(80), /// How many nanoseconds to keep the output hidden - initial_delay_ns: u64 = 200 * std.time.ns_per_ms, + initial_delay_ns: Io.Duration = .fromMilliseconds(200), /// If provided, causes the progress item to have a denominator. /// 0 means unknown. estimated_total_items: usize = 0, @@ -330,7 +332,7 @@ pub const Node = struct { } else { @atomicStore(bool, &global_progress.done, true, .monotonic); global_progress.redraw_event.set(); - if (global_progress.update_thread) |thread| thread.join(); + if (global_progress.update_worker) |worker| worker.await(global_progress.io); } } @@ -391,9 +393,10 @@ pub const Node = struct { }; var global_progress: Progress = .{ + .io = undefined, .terminal = undefined, .terminal_mode = .off, - .update_thread = null, + .update_worker = null, .redraw_event = .unset, .refresh_rate_ns = undefined, .initial_delay_ns = undefined, @@ -403,6 +406,7 @@ var global_progress: Progress = .{ .done = false, .need_clear = false, .status = .working, + .start_failure = .unstarted, .node_parents = &node_parents_buffer, .node_storage = &node_storage_buffer, @@ -411,6 +415,13 @@ var global_progress: Progress = .{ .node_end_index = 0, }; +pub const StartFailure = union(enum) { + unstarted, + spawn_ipc_worker: error{ConcurrencyUnavailable}, + spawn_update_worker: error{ConcurrencyUnavailable}, + parse_env_var: error{}, +}; + const node_storage_buffer_len = 83; var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; @@ -437,7 +448,7 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { /// Asserts there is only one global Progress instance. /// /// Call `Node.end` when done. -pub fn start(options: Options) Node { +pub fn start(options: Options, io: Io) Node { // Ensure there is only 1 global Progress object. if (global_progress.node_end_index != 0) { debug_start_trace.dump(); @@ -458,10 +469,10 @@ pub fn start(options: Options) Node { if (noop_impl) return Node.none; - const io = static_threaded_io.io(); + global_progress.io = io; if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { - global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{ + global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) { .int => ipc_fd, @@ -469,7 +480,7 @@ pub fn start(options: Options) Node { else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)), } }), }) catch |err| { - std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)}); + global_progress.start_failure = .{ .spawn_ipc_worker = err }; return Node.none; }; } else |env_err| switch (env_err) { @@ -502,17 +513,17 @@ pub fn start(options: Options) Node { if (switch (global_progress.terminal_mode) { .off => unreachable, // handled a few lines above - .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}), - .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable, - }) |thread| { - global_progress.update_thread = thread; + .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}), + .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable, + }) |future| { + global_progress.update_worker = future; } else |err| { - std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)}); + global_progress.start_failure = .{ .spawn_update_worker = err }; return Node.none; } }, else => |e| { - std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)}); + global_progress.start_failure = .{ .parse_env_var = e }; return Node.none; }, } @@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); - write(io, buffer) catch return; + if (io.tryLockStderrWriter(&.{})) |w| { + defer io.unlockStderrWriter(); global_progress.need_clear = true; + w.writeAll(buffer) catch return; } } @@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void { const resize_flag = wait(global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - stderr_mutex.lock(); - defer stderr_mutex.unlock(); - return clearWrittenWithEscapeCodes(io) catch {}; + const w = io.lockStderrWriter(&.{}) catch return; + defer io.unlockStderrWriter(); + return clearWrittenWithEscapeCodes(w) catch {}; } maybeUpdateSize(resize_flag); const buffer, _ = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); - write(io, buffer) catch return; + if (io.tryLockStderrWriter(&.{})) |w| { + defer io.unlockStderrWriter(); global_progress.need_clear = true; + w.writeAll(buffer) catch return; } } } @@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void { maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); + if (io.tryLockStderrWriter()) |w| { + defer io.unlockStderrWriter(); windowsApiWriteMarker(); - write(io, buffer) catch return; global_progress.need_clear = true; + w.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } @@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void { const resize_flag = wait(global_progress.refresh_rate_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - stderr_mutex.lock(); - defer stderr_mutex.unlock(); + _ = io.lockStderrWriter() catch return; + defer io.unlockStderrWriter(); return clearWrittenWindowsApi() catch {}; } maybeUpdateSize(resize_flag); const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (stderr_mutex.tryLock()) { - defer stderr_mutex.unlock(); + if (io.tryLockStderrWriter()) |w| { + defer io.unlockStderrWriter(); clearWrittenWindowsApi() catch return; windowsApiWriteMarker(); - write(io, buffer) catch return; global_progress.need_clear = true; + w.writeAll(buffer) catch return; windowsApiMoveToMarker(nl_n) catch return; } } } -/// Allows the caller to freely write to stderr until `unlockStdErr` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -/// -/// The lock is recursive; the same thread may hold the lock multiple times. -pub fn lockStdErr() void { - const io = stderr_file_writer.io; - stderr_mutex.lock(); - clearWrittenWithEscapeCodes(io) catch {}; -} - -pub fn unlockStdErr() void { - stderr_mutex.unlock(); -} - -/// Protected by `stderr_mutex`. -const stderr_writer: *Writer = &stderr_file_writer.interface; -/// Protected by `stderr_mutex`. -var stderr_file_writer: Io.File.Writer = .{ - .io = static_threaded_io.io(), - .interface = Io.File.Writer.initInterface(&.{}), - .file = if (is_windows) undefined else .stderr(), - .mode = .streaming, -}; -var static_threaded_io: Io.Threaded = .init_single_threaded; - -/// Allows the caller to freely write to the returned `Writer`, -/// initialized with `buffer`, until `unlockStderrWriter` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -/// -/// The lock is recursive; the same thread may hold the lock multiple times. -pub fn lockStderrWriter(buffer: []u8) *Io.Writer { - const io = stderr_file_writer.io; - stderr_mutex.lock(); - clearWrittenWithEscapeCodes(io) catch {}; - if (is_windows) stderr_file_writer.file = .stderr(); - stderr_writer.flush() catch {}; - stderr_writer.buffer = buffer; - return stderr_writer; -} - -pub fn unlockStderrWriter() void { - stderr_writer.flush() catch {}; - stderr_writer.end = 0; - stderr_writer.buffer = &.{}; - stderr_mutex.unlock(); -} - fn ipcThreadRun(io: Io, file: Io.File) anyerror!void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. @@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { } } -fn clearWrittenWithEscapeCodes(io: Io) anyerror!void { +fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void { if (noop_impl or !global_progress.need_clear) return; + try w.writeAll(clear ++ progress_remove); global_progress.need_clear = false; - try write(io, clear ++ progress_remove); } /// U+25BA or ► @@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) { error.WouldBlock => break, else => |e| { - std.log.debug("failed to read child progress data: {s}", .{@errorName(e)}); + std.log.debug("failed to read child progress data: {t}", .{e}); main_storage.completed_count = 0; main_storage.estimated_total_count = 0; continue :main_loop; @@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { return nl_n + 2 < p.rows; } -fn write(io: Io, buf: []const u8) anyerror!void { - try global_progress.terminal.writeStreamingAll(io, buf); -} - var remaining_write_trash_bytes: usize = 0; fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { @@ -1459,7 +1417,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi error.WouldBlock => return, error.BrokenPipe => return error.BrokenPipe, else => |e| { - std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)}); + std.log.debug("failed to send progress to parent process: {t}", .{e}); return error.BrokenPipe; }, } @@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi error.WouldBlock => {}, error.BrokenPipe => return error.BrokenPipe, else => |e| { - std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)}); + std.log.debug("failed to send progress to parent process: {t}", .{e}); return error.BrokenPipe; }, } @@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) { else => false, }; -/// The primary motivation for recursive mutex here is so that a panic while -/// stderr mutex is held still dumps the stack trace and other debug -/// information. -var stderr_mutex = std.Thread.Mutex.Recursive.init; - fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void { assert(dest.len == src.len); const chunked_len = dest.len / @sizeOf(usize); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7b111215f3..7e10c5af32 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -262,17 +262,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { else => true, }; -/// Allows the caller to freely write to stderr until `unlockStdErr` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -pub fn lockStdErr() void { - std.Progress.lockStdErr(); -} - -pub fn unlockStdErr() void { - std.Progress.unlockStdErr(); -} - /// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. @@ -281,17 +270,21 @@ pub fn unlockStdErr() void { /// times. The primary motivation is that this allows the panic handler to safely dump the stack /// trace and panic message even if the mutex was held at the panic site. /// -/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically -/// when the matching `unlockStderrWriter` call occurs. +/// The returned `Writer` does not need to be manually flushed: flushing is +/// performed automatically when the matching `unlockStderrWriter` call occurs. +/// +/// This is a low-level debugging primitive that bypasses the `Io` interface, +/// writing directly to stderr using the most basic syscalls available. This +/// function does not switch threads, switch stacks, or suspend. +/// +/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with +/// the application's chosen `Io` implementation. pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } { - const global = struct { - var conf: ?tty.Config = null; - }; + Io.stderr_thread_mutex.lock(); const w = std.Progress.lockStderrWriter(buffer); - const file_writer: *File.Writer = @fieldParentPtr("interface", w); // The stderr lock also locks access to `global.conf`. - if (global.conf == null) { - global.conf = .detect(file_writer.io, .stderr()); + if (StderrWriter.singleton.tty_config == null) { + StderrWriter.singleton.tty_config = .detect(io, .stderr()); } return .{ w, global.conf.? }; } @@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void { std.Progress.unlockStderrWriter(); } -/// Print to stderr, silently returning on failure. Intended for use in "printf -/// debugging". Use `std.log` functions for proper logging. +/// Writes to stderr, ignoring errors. +/// +/// This is a low-level debugging primitive that bypasses the `Io` interface, +/// writing directly to stderr using the most basic syscalls available. This +/// function does not switch threads, switch stacks, or suspend. /// /// Uses a 64-byte buffer for formatted printing which is flushed before this /// function returns. +/// +/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to +/// integrate with the application's chosen `Io` implementation. pub fn print(comptime fmt: []const u8, args: anytype) void { var buffer: [64]u8 = undefined; const bw, _ = lockStderrWriter(&buffer); @@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { nosuspend bw.print(fmt, args) catch return; } +const StderrWriter = struct { + interface: Writer, + tty_config: ?tty.Config, + + var singleton: StderrWriter = .{ + .interface = .{ + .buffer = &.{}, + .vtable = &.{ .drain = drain }, + }, + .tty_config = null, + }; + + fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + var n: usize = 0; + const header = w.interface.buffered(); + if (header.len != 0) n += try std.Io.Threaded.debugWrite(header); + for (data[0 .. data.len - 1]) |d| { + if (d.len != 0) n += try std.Io.Threaded.debugWrite(d); + } + const pattern = data[data.len - 1]; + if (pattern.len != 0) { + for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern); + } + return io_w.consume(n); + } +}; + /// Marked `inline` to propagate a comptime-known error to callers. pub inline fn getSelfDebugInfo() !*SelfInfo { if (SelfInfo == void) return error.UnsupportedTarget; @@ -767,7 +794,7 @@ pub const FormatStackTrace = struct { stack_trace: StackTrace, tty_config: tty.Config, - pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void { + pub fn format(context: @This(), writer: *Writer) Writer.Error!void { try writer.writeAll("\n"); try writeStackTrace(&context.stack_trace, writer, context.tty_config); } @@ -1608,7 +1635,7 @@ test "manage resources correctly" { const gpa = std.testing.allocator; var threaded: Io.Threaded = .init_single_threaded; const io = threaded.ioBasic(); - var discarding: Io.Writer.Discarding = .init(&.{}); + var discarding: Writer.Discarding = .init(&.{}); var di: SelfInfo = .init; defer di.deinit(gpa); try printSourceAtAddress( diff --git a/lib/std/log.zig b/lib/std/log.zig index 9568f9ba52..461dfca36e 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } +var static_threaded_io: std.Io.Threaded = .init_single_threaded; + /// The default implementation for the log function. Custom log functions may /// forward log messages to this function. /// @@ -91,9 +93,19 @@ pub fn defaultLog( comptime format: []const u8, args: anytype, ) void { + return defaultLogIo(level, scope, format, args, static_threaded_io.io()); +} + +pub fn defaultLogIo( + comptime level: Level, + comptime scope: @EnumLiteral(), + comptime format: []const u8, + args: anytype, + io: std.Io, +) void { var buffer: [64]u8 = undefined; - const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer); - defer std.debug.unlockStderrWriter(); + const stderr, const ttyconf = io.lockStderrWriter(&buffer); + defer io.unlockStderrWriter(); ttyconf.setColor(stderr, switch (level) { .err => .red, .warn => .yellow, |
