This commit is contained in:
Bob Farrell 2024-05-09 19:53:29 +01:00
parent 6575add452
commit 0ff19d5d26
5 changed files with 129 additions and 32 deletions

View File

@ -75,10 +75,20 @@ pub fn start(self: App, routes_module: type, options: AppOptions) !void {
);
defer cache.deinit();
const server_options = try self.environment.getServerOptions();
var log_queue = try jetzig.loggers.LogQueue.init(self.allocator);
try log_queue.setFile(std.io.getStdOut());
const server_options = try self.environment.getServerOptions(&log_queue);
defer self.allocator.free(server_options.bind);
defer self.allocator.free(server_options.secret);
var log_thread = try std.Thread.spawn(
.{ .allocator = self.allocator },
jetzig.loggers.LogQueue.Reader.publish,
.{log_queue.reader},
);
defer log_thread.join();
if (server_options.detach) {
const argv = try std.process.argsAlloc(self.allocator);
defer std.process.argsFree(self.allocator, argv);

View File

@ -59,7 +59,10 @@ pub fn init(allocator: std.mem.Allocator) Environment {
}
/// Generate server initialization options using command line args with defaults.
pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
pub fn getServerOptions(
self: Environment,
log_queue: *jetzig.loggers.LogQueue,
) !jetzig.http.Server.ServerOptions {
const options = try args.parseForCurrentProcess(Options, self.allocator, .print);
defer options.deinit();
@ -72,22 +75,24 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
const environment = options.options.environment;
var logger = switch (options.options.@"log-format") {
.development => jetzig.loggers.Logger{
.development, .json => jetzig.loggers.Logger{
.development_logger = jetzig.loggers.DevelopmentLogger.init(
self.allocator,
resolveLogLevel(options.options.@"log-level", environment),
try getLogFile(.stdout, options.options),
try getLogFile(.stderr, options.options),
),
},
.json => jetzig.loggers.Logger{
.json_logger = jetzig.loggers.JsonLogger.init(
self.allocator,
resolveLogLevel(options.options.@"log-level", environment),
try getLogFile(.stdout, options.options),
try getLogFile(.stderr, options.options),
log_queue,
// try getLogFile(.stdout, options.options),
// try getLogFile(.stderr, options.options),
),
},
// TODO
// .json => jetzig.loggers.Logger{
// .json_logger = jetzig.loggers.JsonLogger.init(
// self.allocator,
// resolveLogLevel(options.options.@"log-level", environment),
// try getLogFile(.stdout, options.options),
// try getLogFile(.stderr, options.options),
// ),
// },
};
if (options.options.detach and std.mem.eql(u8, options.options.log, "-")) {

View File

@ -6,6 +6,7 @@ const Self = @This();
pub const DevelopmentLogger = @import("loggers/DevelopmentLogger.zig");
pub const JsonLogger = @import("loggers/JsonLogger.zig");
pub const LogQueue = @import("loggers/LogQueue.zig");
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
pub const LogFormat = enum { development, json };

View File

@ -8,28 +8,23 @@ const Timestamp = jetzig.types.Timestamp;
const LogLevel = jetzig.loggers.LogLevel;
allocator: std.mem.Allocator,
stdout: std.fs.File,
stderr: std.fs.File,
stdout_colorized: bool,
stderr_colorized: bool,
level: LogLevel,
mutex: std.Thread.Mutex,
log_queue: *jetzig.loggers.LogQueue,
/// Initialize a new Development Logger.
pub fn init(
allocator: std.mem.Allocator,
level: LogLevel,
stdout: std.fs.File,
stderr: std.fs.File,
log_queue: *jetzig.loggers.LogQueue,
) DevelopmentLogger {
return .{
.allocator = allocator,
.level = level,
.stdout = stdout,
.stderr = stderr,
.stdout_colorized = stdout.isTty(),
.stderr_colorized = stderr.isTty(),
.mutex = std.Thread.Mutex{},
.log_queue = log_queue,
.stdout_colorized = true, // TODO
.stderr_colorized = true, // TODO
};
}
@ -53,19 +48,13 @@ pub fn log(
.TRACE, .DEBUG, .INFO => self.stdout_colorized,
.WARN, .ERROR, .FATAL => self.stderr_colorized,
};
const file = switch (level) {
.TRACE, .DEBUG, .INFO => self.stdout,
.WARN, .ERROR, .FATAL => self.stderr,
const writer = switch (level) {
.TRACE, .DEBUG, .INFO => self.log_queue.writer,
.WARN, .ERROR, .FATAL => self.log_queue.writer,
};
const writer = file.writer();
const level_formatted = if (colorized) colorizedLogLevel(level) else @tagName(level);
@constCast(self).mutex.lock();
defer @constCast(self).mutex.unlock();
try writer.print("{s: >5} [{s}] {s}\n", .{ level_formatted, iso8601, output });
if (!file.isTty()) try file.sync();
}
/// Log a one-liner including response status code, path, method, duration, etc.

View File

@ -0,0 +1,92 @@
const std = @import("std");
const List = std.DoublyLinkedList([]const u8);
allocator: std.mem.Allocator,
list: *List,
read_write_mutex: *std.Thread.Mutex,
condition: *std.Thread.Condition,
condition_mutex: *std.Thread.Mutex,
writer: *Writer = undefined,
reader: *Reader = undefined,
const LogQueue = @This();
pub fn init(allocator: std.mem.Allocator) !LogQueue {
const list = try allocator.create(std.DoublyLinkedList([]const u8));
list.* = .{};
return .{
.allocator = allocator,
.list = list,
.condition = try allocator.create(std.Thread.Condition),
.condition_mutex = try allocator.create(std.Thread.Mutex),
.read_write_mutex = try allocator.create(std.Thread.Mutex),
};
}
pub fn setFile(self: *LogQueue, file: std.fs.File) !void {
self.writer = try self.allocator.create(Writer);
self.writer.* = Writer{ .file = file, .queue = self };
self.reader = try self.allocator.create(Reader);
self.reader.* = Reader{ .file = file, .queue = self };
}
pub const Writer = struct {
file: std.fs.File,
queue: *LogQueue,
pub fn isTty(self: Writer) bool {
return self.file.isTty();
}
pub fn print(self: *Writer, comptime message: []const u8, args: anytype) !void {
const output = try std.fmt.allocPrint(self.queue.allocator, message, args);
defer self.queue.allocator.free(output);
try self.queue.append(output);
}
};
pub const Reader = struct {
file: std.fs.File,
queue: *LogQueue,
pub fn publish(self: *Reader) !void {
const writer = self.file.writer();
while (true) {
self.queue.condition_mutex.lock();
defer self.queue.condition_mutex.unlock();
self.queue.condition.wait(self.queue.condition_mutex);
while (try self.queue.popFirst()) |message| {
defer self.queue.allocator.free(message);
try writer.writeAll(message);
}
if (!self.file.isTty()) try self.file.sync();
}
}
};
pub fn append(self: *LogQueue, message: []const u8) !void {
self.read_write_mutex.lock();
defer self.read_write_mutex.unlock();
const node = try self.allocator.create(List.Node);
node.* = .{ .data = try self.allocator.dupe(u8, message) };
self.list.append(node);
self.condition.signal();
}
pub fn popFirst(self: *LogQueue) !?[]const u8 {
self.read_write_mutex.lock();
defer self.read_write_mutex.unlock();
if (self.list.popFirst()) |node| {
const value = node.data;
self.allocator.destroy(node);
return value;
} else {
return null;
}
}