mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
WIP
This commit is contained in:
parent
bd02977600
commit
2fbd2f2f6a
@ -12,7 +12,7 @@ pub const jetzig_options = struct {
|
||||
pub const middleware: []const type = &.{
|
||||
// htmx middleware skips layouts when `HX-Target` header is present and issues
|
||||
// `HX-Redirect` instead of a regular HTTP redirect when `request.redirect` is called.
|
||||
// jetzig.middleware.HtmxMiddleware,
|
||||
jetzig.middleware.HtmxMiddleware,
|
||||
// Demo middleware included with new projects. Remove once you are familiar with Jetzig's
|
||||
// middleware system.
|
||||
// @import("app/middleware/DemoMiddleware.zig"),
|
||||
|
@ -4,48 +4,83 @@ const builtin = @import("builtin");
|
||||
|
||||
const types = @import("types.zig");
|
||||
|
||||
// Must be consistent with `std.io.tty.Color` for Windows compatibility.
|
||||
const codes = .{
|
||||
.escape = "\x1b[",
|
||||
.reset = "0;0",
|
||||
.black = "0;30",
|
||||
.red = "0;31",
|
||||
.green = "0;32",
|
||||
.yellow = "0;33",
|
||||
.blue = "0;34",
|
||||
.purple = "0;35",
|
||||
.cyan = "0;36",
|
||||
.white = "0;37",
|
||||
.black = "30m",
|
||||
.red = "31m",
|
||||
.green = "32m",
|
||||
.yellow = "33m",
|
||||
.blue = "34m",
|
||||
.magenta = "35m",
|
||||
.cyan = "36m",
|
||||
.white = "37m",
|
||||
.bright_black = "90m",
|
||||
.bright_red = "91m",
|
||||
.bright_green = "92m",
|
||||
.bright_yellow = "93m",
|
||||
.bright_blue = "94m",
|
||||
.bright_magenta = "95m",
|
||||
.bright_cyan = "96m",
|
||||
.bright_white = "97m",
|
||||
.bold = "1m",
|
||||
.dim = "2m",
|
||||
.reset = "0m",
|
||||
};
|
||||
|
||||
pub fn colorize(color: std.io.tty.Color, buf: []u8, input: []const u8, target: std.fs.File) ![]const u8 {
|
||||
const config = std.io.tty.detectConfig(target);
|
||||
/// Map color codes generated by `std.io.tty.Config.setColor` back to `std.io.tty.Color`. Used by
|
||||
/// `jetzig.loggers.LogQueue.writeWindows` to parse escape codes so they can be passed to
|
||||
/// `std.io.tty.Config.setColor` (using Windows API to set console color mode).
|
||||
pub const codes_map = std.StaticStringMap(std.io.tty.Color).initComptime(.{
|
||||
.{ "30", .black },
|
||||
.{ "31", .red },
|
||||
.{ "32", .green },
|
||||
.{ "33", .yellow },
|
||||
.{ "34", .blue },
|
||||
.{ "35", .magenta },
|
||||
.{ "36", .cyan },
|
||||
.{ "37", .white },
|
||||
.{ "90", .bright_black },
|
||||
.{ "91", .bright_red },
|
||||
.{ "92", .bright_green },
|
||||
.{ "93", .bright_yellow },
|
||||
.{ "94", .bright_blue },
|
||||
.{ "95", .bright_magenta },
|
||||
.{ "96", .bright_cyan },
|
||||
.{ "97", .bright_white },
|
||||
.{ "1", .bold },
|
||||
.{ "2", .dim },
|
||||
.{ "0", .reset },
|
||||
});
|
||||
|
||||
/// Colorize a log message. Note that we force `.escape_codes` when we are a TTY even on Windows.
|
||||
/// `jetzig.loggers.LogQueue` parses the ANSI codes and uses `std.io.tty.Config.setColor` to
|
||||
/// invoke the appropriate Windows API call to set the terminal color before writing each token.
|
||||
/// We must do it this way because Windows colors are set by API calls at the time of write, not
|
||||
/// encoded into the message string.
|
||||
pub fn colorize(color: std.io.tty.Color, buf: []u8, input: []const u8, is_colorized: bool) ![]const u8 {
|
||||
if (!is_colorized) return input;
|
||||
|
||||
const config: std.io.tty.Config = .escape_codes;
|
||||
var stream = std.io.fixedBufferStream(buf);
|
||||
const writer = stream.writer();
|
||||
try config.setColor(writer, color);
|
||||
try writer.writeAll(input);
|
||||
try config.setColor(writer, .white);
|
||||
try config.setColor(writer, .reset);
|
||||
|
||||
return stream.getWritten();
|
||||
}
|
||||
|
||||
fn wrap(comptime attribute: []const u8, comptime message: []const u8) []const u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return message;
|
||||
} else {
|
||||
return codes.escape ++ attribute ++ "m" ++ message ++ codes.escape ++ codes.reset ++ "m";
|
||||
}
|
||||
return codes.escape ++ attribute ++ message ++ codes.escape ++ codes.reset;
|
||||
}
|
||||
|
||||
fn runtimeWrap(allocator: std.mem.Allocator, attribute: []const u8, message: []const u8) ![]const u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return try allocator.dupe(u8, message);
|
||||
} else {
|
||||
return try std.mem.join(
|
||||
allocator,
|
||||
"",
|
||||
&[_][]const u8{ codes.escape, attribute, "m", message, codes.escape, codes.reset, "m" },
|
||||
);
|
||||
}
|
||||
return try std.mem.join(
|
||||
allocator,
|
||||
"",
|
||||
&[_][]const u8{ codes.escape, attribute, message, codes.escape, codes.reset },
|
||||
);
|
||||
}
|
||||
|
||||
pub fn black(comptime message: []const u8) []const u8 {
|
||||
@ -88,12 +123,12 @@ pub fn runtimeBlue(allocator: std.mem.Allocator, message: []const u8) ![]const u
|
||||
return try runtimeWrap(allocator, codes.blue, message);
|
||||
}
|
||||
|
||||
pub fn purple(comptime message: []const u8) []const u8 {
|
||||
return wrap(codes.purple, message);
|
||||
pub fn magenta(comptime message: []const u8) []const u8 {
|
||||
return wrap(codes.magenta, message);
|
||||
}
|
||||
|
||||
pub fn runtimePurple(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||
return try runtimeWrap(allocator, codes.purple, message);
|
||||
pub fn runtimeMagenta(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||
return try runtimeWrap(allocator, codes.magenta, message);
|
||||
}
|
||||
|
||||
pub fn cyan(comptime message: []const u8) []const u8 {
|
||||
@ -112,16 +147,26 @@ pub fn runtimeWhite(allocator: std.mem.Allocator, message: []const u8) ![]const
|
||||
return try runtimeWrap(allocator, codes.white, message);
|
||||
}
|
||||
|
||||
pub fn duration(buf: *[256]u8, delta: i64) ![]const u8 {
|
||||
const code = if (delta < 1000000)
|
||||
codes.green
|
||||
pub fn duration(buf: *[256]u8, delta: i64, is_colorized: bool) ![]const u8 {
|
||||
if (!is_colorized) {
|
||||
return try std.fmt.bufPrint(
|
||||
buf,
|
||||
"{}",
|
||||
.{std.fmt.fmtDurationSigned(delta)},
|
||||
);
|
||||
}
|
||||
|
||||
const color: std.io.tty.Color = if (delta < 1000000)
|
||||
.green
|
||||
else if (delta < 5000000)
|
||||
codes.yellow
|
||||
.yellow
|
||||
else
|
||||
codes.red;
|
||||
return try std.fmt.bufPrint(
|
||||
buf,
|
||||
"{s}{s}m{}{s}{s}m",
|
||||
.{ codes.escape, code, std.fmt.fmtDurationSigned(delta), codes.escape, codes.reset },
|
||||
.red;
|
||||
var duration_buf: [256]u8 = undefined;
|
||||
const formatted_duration = try std.fmt.bufPrint(
|
||||
&duration_buf,
|
||||
"{}",
|
||||
.{std.fmt.fmtDurationSigned(delta)},
|
||||
);
|
||||
return try colorize(color, buf, formatted_duration, true);
|
||||
}
|
||||
|
@ -107,6 +107,8 @@ fn processNextRequest(
|
||||
httpz_request: *httpz.Request,
|
||||
httpz_response: *httpz.Response,
|
||||
) !void {
|
||||
const start_time = std.time.nanoTimestamp();
|
||||
|
||||
const state = try self.allocator.create(jetzig.http.Request.CallbackState);
|
||||
const arena = try self.allocator.create(std.heap.ArenaAllocator);
|
||||
arena.* = std.heap.ArenaAllocator.init(self.allocator);
|
||||
@ -120,8 +122,6 @@ fn processNextRequest(
|
||||
|
||||
const allocator = state.arena.allocator();
|
||||
|
||||
const start_time = std.time.nanoTimestamp();
|
||||
|
||||
var response = try jetzig.http.Response.init(allocator, httpz_response);
|
||||
var request = try jetzig.http.Request.init(
|
||||
allocator,
|
||||
|
@ -44,10 +44,8 @@ pub fn log(
|
||||
var timestamp_buf: [256]u8 = undefined;
|
||||
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||
|
||||
var level_buf: [16]u8 = undefined;
|
||||
const formatted_level = try colorizedLogLevel(level, &level_buf, self.log_queue.reader.stdout_file);
|
||||
|
||||
const target = jetzig.loggers.logTarget(level);
|
||||
const formatted_level = colorizedLogLevel(level);
|
||||
|
||||
try self.log_queue.print(
|
||||
"{s: >5} [{s}] {s}\n",
|
||||
@ -59,14 +57,11 @@ pub fn log(
|
||||
/// Log a one-liner including response status code, path, method, duration, etc.
|
||||
pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request) !void {
|
||||
var duration_buf: [256]u8 = undefined;
|
||||
const formatted_duration = if (self.stdout_colorized)
|
||||
try jetzig.colors.duration(&duration_buf, jetzig.util.duration(request.start_time))
|
||||
else
|
||||
try std.fmt.bufPrint(
|
||||
&duration_buf,
|
||||
"{}",
|
||||
.{std.fmt.fmtDurationSigned(jetzig.util.duration(request.start_time))},
|
||||
);
|
||||
const formatted_duration = try jetzig.colors.duration(
|
||||
&duration_buf,
|
||||
jetzig.util.duration(request.start_time),
|
||||
self.stdout_colorized,
|
||||
);
|
||||
|
||||
const status: jetzig.http.status_codes.TaggedStatusCode = switch (request.response.status_code) {
|
||||
inline else => |status_code| @unionInit(
|
||||
@ -85,8 +80,7 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
||||
var timestamp_buf: [256]u8 = undefined;
|
||||
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||
|
||||
var level_buf: [16]u8 = undefined;
|
||||
const formatted_level = try colorizedLogLevel(.INFO, &level_buf, self.log_queue.reader.stdout_file);
|
||||
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
||||
|
||||
try self.log_queue.print("{s: >5} [{s}] [{s}/{s}/{s}] {s}\n", .{
|
||||
formatted_level,
|
||||
@ -98,13 +92,13 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
||||
}, .stdout);
|
||||
}
|
||||
|
||||
fn colorizedLogLevel(comptime level: LogLevel, buf: []u8, file: std.fs.File) ![]const u8 {
|
||||
inline fn colorizedLogLevel(comptime level: LogLevel) []const u8 {
|
||||
return switch (level) {
|
||||
.TRACE => jetzig.colors.colorize(.white, buf, @tagName(level), file),
|
||||
.DEBUG => jetzig.colors.colorize(.cyan, buf, @tagName(level), file),
|
||||
.INFO => jetzig.colors.colorize(.blue, buf, @tagName(level) ++ " ", file),
|
||||
.WARN => jetzig.colors.colorize(.yellow, buf, @tagName(level) ++ " ", file),
|
||||
.ERROR => jetzig.colors.colorize(.red, buf, @tagName(level), file),
|
||||
.FATAL => jetzig.colors.colorize(.red, buf, @tagName(level), file),
|
||||
.TRACE => jetzig.colors.white(@tagName(level)),
|
||||
.DEBUG => jetzig.colors.cyan(@tagName(level)),
|
||||
.INFO => jetzig.colors.blue(@tagName(level) ++ " "),
|
||||
.WARN => jetzig.colors.yellow(@tagName(level) ++ " "),
|
||||
.ERROR => jetzig.colors.red(@tagName(level)),
|
||||
.FATAL => jetzig.colors.red(@tagName(level)),
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
@ -81,8 +82,10 @@ pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.F
|
||||
};
|
||||
self.stdout_is_tty = stdout_file.isTty();
|
||||
self.stderr_is_tty = stderr_file.isTty();
|
||||
|
||||
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color;
|
||||
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color;
|
||||
|
||||
self.state = .ready;
|
||||
}
|
||||
|
||||
@ -164,6 +167,8 @@ pub const Reader = struct {
|
||||
|
||||
var stdout_written = false;
|
||||
var stderr_written = false;
|
||||
var file: std.fs.File = undefined;
|
||||
var colorize = false;
|
||||
|
||||
while (try self.queue.popFirst()) |event| {
|
||||
self.queue.writer.mutex.lock();
|
||||
@ -172,10 +177,18 @@ pub const Reader = struct {
|
||||
const writer = switch (event.target) {
|
||||
.stdout => blk: {
|
||||
stdout_written = true;
|
||||
if (builtin.os.tag == .windows) {
|
||||
file = self.stdout_file;
|
||||
colorize = self.queue.stdout_colorize;
|
||||
}
|
||||
break :blk stdout_writer;
|
||||
},
|
||||
.stderr => blk: {
|
||||
stderr_written = true;
|
||||
if (builtin.os.tag == .windows) {
|
||||
file = self.stderr_file;
|
||||
colorize = self.queue.stderr_colorize;
|
||||
}
|
||||
break :blk stderr_writer;
|
||||
},
|
||||
};
|
||||
@ -187,7 +200,11 @@ pub const Reader = struct {
|
||||
continue;
|
||||
}
|
||||
|
||||
try writer.writeAll(event.message[0..event.len]);
|
||||
if (builtin.os.tag == .windows and colorize) {
|
||||
try writeWindows(file, writer, event);
|
||||
} else {
|
||||
try writer.writeAll(event.message[0..event.len]);
|
||||
}
|
||||
|
||||
self.queue.writer.position -= 1;
|
||||
|
||||
@ -244,6 +261,35 @@ fn popFirst(self: *LogQueue) !?Event {
|
||||
}
|
||||
}
|
||||
|
||||
fn writeWindows(file: std.fs.File, writer: anytype, event: Event) !void {
|
||||
var info: std.os.windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
const config: std.io.tty.Config = if (std.os.windows.kernel32.GetConsoleScreenBufferInfo(
|
||||
file.handle,
|
||||
&info,
|
||||
) != std.os.windows.TRUE)
|
||||
.no_color
|
||||
else
|
||||
.{ .windows_api = .{
|
||||
.handle = file.handle,
|
||||
.reset_attributes = info.wAttributes,
|
||||
} };
|
||||
|
||||
var it = std.mem.tokenizeSequence(u8, event.message[0..event.len], "\x1b[");
|
||||
while (it.next()) |token| {
|
||||
if (std.mem.indexOfScalar(u8, token, 'm')) |index| {
|
||||
if (index > 0 and index + 1 < token.len) {
|
||||
if (jetzig.colors.codes_map.get(token[0..index])) |color| {
|
||||
try config.setColor(writer, color);
|
||||
try writer.writeAll(token[index + 1 ..]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback
|
||||
try writer.writeAll(token);
|
||||
}
|
||||
}
|
||||
|
||||
test "print to stdout and stderr" {
|
||||
var log_queue = LogQueue.init(std.testing.allocator);
|
||||
defer log_queue.deinit();
|
||||
|
@ -29,10 +29,9 @@ pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
_ = self;
|
||||
if (response.status_code != .moved_permanently and response.status_code != .found) return;
|
||||
if (request.headers.getFirstValue("HX-Request") == null) return;
|
||||
if (request.headers.get("HX-Request") == null) return;
|
||||
|
||||
if (response.headers.getFirstValue("Location")) |location| {
|
||||
response.headers.remove("Location");
|
||||
if (response.headers.get("Location")) |location| {
|
||||
response.status_code = .ok;
|
||||
request.response_data.reset();
|
||||
try response.headers.append("HX-Redirect", location);
|
||||
|
Loading…
x
Reference in New Issue
Block a user