Never sync stdout/stderr

Detecting if stdout/stderr is tty seems to cause issues when running in
Docker (in particular with fly.io)
This commit is contained in:
Bob Farrell 2025-04-19 21:41:27 +01:00
parent b7c3c0045a
commit dee5701b4a
3 changed files with 39 additions and 27 deletions

View File

@ -203,8 +203,8 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
const vars = try Vars.init(allocator, env_file); const vars = try Vars.init(allocator, env_file);
var launch_logger = LaunchLogger{ var launch_logger = LaunchLogger{
.stdout = stdout, .stdout = stdout.file,
.stderr = stderr, .stderr = stderr.file,
.silent = env_options.silent, .silent = env_options.silent,
}; };
@ -213,8 +213,8 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
.development_logger = jetzig.loggers.DevelopmentLogger.init( .development_logger = jetzig.loggers.DevelopmentLogger.init(
allocator, allocator,
resolveLogLevel(options.options.@"log-level", jetzig.environment), resolveLogLevel(options.options.@"log-level", jetzig.environment),
stdout, stdout.file,
stderr, stderr.file,
), ),
}, },
.production => jetzig.loggers.Logger{ .production => jetzig.loggers.Logger{
@ -304,23 +304,26 @@ pub fn deinit(self: Environment) void {
self.parent_allocator.destroy(self.arena); self.parent_allocator.destroy(self.arena);
} }
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File { fn getLogFile(stream: enum { stdout, stderr }, options: Options) !jetzig.loggers.LogFile {
const path = switch (stream) { const path = switch (stream) {
.stdout => options.log, .stdout => options.log,
.stderr => options.@"log-error", .stderr => options.@"log-error",
}; };
if (std.mem.eql(u8, path, "-")) return switch (stream) { if (std.mem.eql(u8, path, "-")) return switch (stream) {
.stdout => std.io.getStdOut(), .stdout => .{ .file = std.io.getStdOut(), .sync = false },
.stderr => if (std.mem.eql(u8, options.log, "-")) .stderr => if (std.mem.eql(u8, options.log, "-"))
std.io.getStdErr() .{ .file = std.io.getStdErr(), .sync = false }
else else
try std.fs.createFileAbsolute(options.log, .{ .truncate = false }), .{
.file = try std.fs.createFileAbsolute(options.log, .{ .truncate = false }),
.sync = true,
},
}; };
const file = try std.fs.createFileAbsolute(path, .{ .truncate = false }); const file = try std.fs.createFileAbsolute(path, .{ .truncate = false });
try file.seekFromEnd(0); try file.seekFromEnd(0);
return file; return .{ .file = file, .sync = true };
} }
fn getSecret( fn getSecret(

View File

@ -12,6 +12,11 @@ pub const NullLogger = @import("loggers/NullLogger.zig");
pub const LogQueue = @import("loggers/LogQueue.zig"); pub const LogQueue = @import("loggers/LogQueue.zig");
pub const LogFile = struct {
file: std.fs.File,
sync: bool = false,
};
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL }; pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
pub const LogFormat = enum { development, production, json, null }; pub const LogFormat = enum { development, production, json, null };

View File

@ -70,7 +70,11 @@ pub fn deinit(self: *LogQueue) void {
} }
/// Set the stdout and stderr outputs. Must be called before `print`. /// Set the stdout and stderr outputs. Must be called before `print`.
pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.File) !void { pub fn setFiles(
self: *LogQueue,
stdout_file: jetzig.loggers.LogFile,
stderr_file: jetzig.loggers.LogFile,
) !void {
self.writer = Writer{ self.writer = Writer{
.queue = self, .queue = self,
.mutex = std.Thread.Mutex{}, .mutex = std.Thread.Mutex{},
@ -80,11 +84,11 @@ pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.F
.stderr_file = stderr_file, .stderr_file = stderr_file,
.queue = self, .queue = self,
}; };
self.stdout_is_tty = stdout_file.isTty(); self.stdout_is_tty = stdout_file.file.isTty();
self.stderr_is_tty = stderr_file.isTty(); self.stderr_is_tty = stderr_file.file.isTty();
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color; self.stdout_colorize = std.io.tty.detectConfig(stdout_file.file) != .no_color;
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color; self.stderr_colorize = std.io.tty.detectConfig(stderr_file.file) != .no_color;
self.state = .ready; self.state = .ready;
} }
@ -147,8 +151,8 @@ pub const Writer = struct {
/// Reader for `LogQueue`. Reads log events from the queue and writes them to the designated /// Reader for `LogQueue`. Reads log events from the queue and writes them to the designated
/// target (stdout or stderr). /// target (stdout or stderr).
pub const Reader = struct { pub const Reader = struct {
stdout_file: std.fs.File, stdout_file: jetzig.loggers.LogFile,
stderr_file: std.fs.File, stderr_file: jetzig.loggers.LogFile,
queue: *LogQueue, queue: *LogQueue,
pub const PublishOptions = struct { pub const PublishOptions = struct {
@ -160,8 +164,8 @@ pub const Reader = struct {
pub fn publish(self: *Reader, options: PublishOptions) !void { pub fn publish(self: *Reader, options: PublishOptions) !void {
std.debug.assert(self.queue.state == .ready); std.debug.assert(self.queue.state == .ready);
const stdout_writer = self.stdout_file.writer(); const stdout_writer = self.stdout_file.file.writer();
const stderr_writer = self.stderr_file.writer(); const stderr_writer = self.stderr_file.file.writer();
while (true) { while (true) {
self.queue.condition_mutex.lock(); self.queue.condition_mutex.lock();
@ -181,13 +185,13 @@ pub const Reader = struct {
.stdout => { .stdout => {
stdout_written = true; stdout_written = true;
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
file = self.stdout_file; file = self.stdout_file.file;
} }
}, },
.stderr => { .stderr => {
stderr_written = true; stderr_written = true;
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
file = self.stderr_file; file = self.stderr_file.file;
} }
}, },
} }
@ -220,8 +224,8 @@ pub const Reader = struct {
} }
} }
if (stdout_written and !self.queue.stdout_is_tty) try self.stdout_file.sync(); if (stdout_written and self.stdout_file.sync) try self.stdout_file.file.sync();
if (stderr_written and !self.queue.stderr_is_tty) try self.stderr_file.sync(); if (stderr_written and self.stderr_file.sync) try self.stderr_file.file.sync();
if (options.oneshot) break; if (options.oneshot) break;
} }
@ -289,7 +293,7 @@ test "print to stdout and stderr" {
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true }); const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
defer stderr.close(); defer stderr.close();
try log_queue.setFiles(stdout, stderr); try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
try log_queue.print("foo {s}\n", .{"bar"}, .stdout); try log_queue.print("foo {s}\n", .{"bar"}, .stdout);
try log_queue.print("baz {s}\n", .{"qux"}, .stderr); try log_queue.print("baz {s}\n", .{"qux"}, .stderr);
try log_queue.print("quux {s}\n", .{"corge"}, .stdout); try log_queue.print("quux {s}\n", .{"corge"}, .stdout);
@ -333,7 +337,7 @@ test "long messages" {
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true }); const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
defer stderr.close(); defer stderr.close();
try log_queue.setFiles(stdout, stderr); try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
try log_queue.print("foo" ** buffer_size, .{}, .stdout); try log_queue.print("foo" ** buffer_size, .{}, .stdout);
try log_queue.reader.publish(.{ .oneshot = true }); try log_queue.reader.publish(.{ .oneshot = true });