aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2025-12-09 18:44:39 -0800
committerAndrew Kelley <andrew@ziglang.org>2025-12-23 22:15:09 -0800
commit78d262d96ee6200c7a6bc0a41fe536d263c24d92 (patch)
treecfc5dbe215a58d9001f90f1efc1e11d907d669f4 /lib
parent03526c59d4e2a00f83347cf06c741a3ed4fec520 (diff)
downloadzig-78d262d96ee6200c7a6bc0a41fe536d263c24d92.tar.gz
zig-78d262d96ee6200c7a6bc0a41fe536d263c24d92.zip
std: WIP: debug-level stderr writing
Diffstat (limited to 'lib')
-rw-r--r--lib/std/Io.zig31
-rw-r--r--lib/std/Io/Dir.zig5
-rw-r--r--lib/std/Io/Threaded.zig32
-rw-r--r--lib/std/Progress.zig149
-rw-r--r--lib/std/debug.zig73
-rw-r--r--lib/std/log.zig16
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,