mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-15 06:26:07 +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 = &.{
|
pub const middleware: []const type = &.{
|
||||||
// htmx middleware skips layouts when `HX-Target` header is present and issues
|
// 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.
|
// `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
|
// Demo middleware included with new projects. Remove once you are familiar with Jetzig's
|
||||||
// middleware system.
|
// middleware system.
|
||||||
// @import("app/middleware/DemoMiddleware.zig"),
|
// @import("app/middleware/DemoMiddleware.zig"),
|
||||||
|
@ -4,48 +4,83 @@ const builtin = @import("builtin");
|
|||||||
|
|
||||||
const types = @import("types.zig");
|
const types = @import("types.zig");
|
||||||
|
|
||||||
|
// Must be consistent with `std.io.tty.Color` for Windows compatibility.
|
||||||
const codes = .{
|
const codes = .{
|
||||||
.escape = "\x1b[",
|
.escape = "\x1b[",
|
||||||
.reset = "0;0",
|
.black = "30m",
|
||||||
.black = "0;30",
|
.red = "31m",
|
||||||
.red = "0;31",
|
.green = "32m",
|
||||||
.green = "0;32",
|
.yellow = "33m",
|
||||||
.yellow = "0;33",
|
.blue = "34m",
|
||||||
.blue = "0;34",
|
.magenta = "35m",
|
||||||
.purple = "0;35",
|
.cyan = "36m",
|
||||||
.cyan = "0;36",
|
.white = "37m",
|
||||||
.white = "0;37",
|
.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 {
|
/// Map color codes generated by `std.io.tty.Config.setColor` back to `std.io.tty.Color`. Used by
|
||||||
const config = std.io.tty.detectConfig(target);
|
/// `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);
|
var stream = std.io.fixedBufferStream(buf);
|
||||||
const writer = stream.writer();
|
const writer = stream.writer();
|
||||||
try config.setColor(writer, color);
|
try config.setColor(writer, color);
|
||||||
try writer.writeAll(input);
|
try writer.writeAll(input);
|
||||||
try config.setColor(writer, .white);
|
try config.setColor(writer, .reset);
|
||||||
|
|
||||||
return stream.getWritten();
|
return stream.getWritten();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap(comptime attribute: []const u8, comptime message: []const u8) []const u8 {
|
fn wrap(comptime attribute: []const u8, comptime message: []const u8) []const u8 {
|
||||||
if (builtin.os.tag == .windows) {
|
return codes.escape ++ attribute ++ message ++ codes.escape ++ codes.reset;
|
||||||
return message;
|
|
||||||
} else {
|
|
||||||
return codes.escape ++ attribute ++ "m" ++ message ++ codes.escape ++ codes.reset ++ "m";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtimeWrap(allocator: std.mem.Allocator, attribute: []const u8, message: []const u8) ![]const u8 {
|
fn runtimeWrap(allocator: std.mem.Allocator, attribute: []const u8, message: []const u8) ![]const u8 {
|
||||||
if (builtin.os.tag == .windows) {
|
return try std.mem.join(
|
||||||
return try allocator.dupe(u8, message);
|
allocator,
|
||||||
} else {
|
"",
|
||||||
return try std.mem.join(
|
&[_][]const u8{ codes.escape, attribute, message, codes.escape, codes.reset },
|
||||||
allocator,
|
);
|
||||||
"",
|
|
||||||
&[_][]const u8{ codes.escape, attribute, "m", message, codes.escape, codes.reset, "m" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn black(comptime message: []const u8) []const u8 {
|
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);
|
return try runtimeWrap(allocator, codes.blue, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn purple(comptime message: []const u8) []const u8 {
|
pub fn magenta(comptime message: []const u8) []const u8 {
|
||||||
return wrap(codes.purple, message);
|
return wrap(codes.magenta, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtimePurple(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
pub fn runtimeMagenta(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
return try runtimeWrap(allocator, codes.purple, message);
|
return try runtimeWrap(allocator, codes.magenta, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cyan(comptime message: []const u8) []const u8 {
|
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);
|
return try runtimeWrap(allocator, codes.white, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn duration(buf: *[256]u8, delta: i64) ![]const u8 {
|
pub fn duration(buf: *[256]u8, delta: i64, is_colorized: bool) ![]const u8 {
|
||||||
const code = if (delta < 1000000)
|
if (!is_colorized) {
|
||||||
codes.green
|
return try std.fmt.bufPrint(
|
||||||
|
buf,
|
||||||
|
"{}",
|
||||||
|
.{std.fmt.fmtDurationSigned(delta)},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const color: std.io.tty.Color = if (delta < 1000000)
|
||||||
|
.green
|
||||||
else if (delta < 5000000)
|
else if (delta < 5000000)
|
||||||
codes.yellow
|
.yellow
|
||||||
else
|
else
|
||||||
codes.red;
|
.red;
|
||||||
return try std.fmt.bufPrint(
|
var duration_buf: [256]u8 = undefined;
|
||||||
buf,
|
const formatted_duration = try std.fmt.bufPrint(
|
||||||
"{s}{s}m{}{s}{s}m",
|
&duration_buf,
|
||||||
.{ codes.escape, code, std.fmt.fmtDurationSigned(delta), codes.escape, codes.reset },
|
"{}",
|
||||||
|
.{std.fmt.fmtDurationSigned(delta)},
|
||||||
);
|
);
|
||||||
|
return try colorize(color, buf, formatted_duration, true);
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,8 @@ fn processNextRequest(
|
|||||||
httpz_request: *httpz.Request,
|
httpz_request: *httpz.Request,
|
||||||
httpz_response: *httpz.Response,
|
httpz_response: *httpz.Response,
|
||||||
) !void {
|
) !void {
|
||||||
|
const start_time = std.time.nanoTimestamp();
|
||||||
|
|
||||||
const state = try self.allocator.create(jetzig.http.Request.CallbackState);
|
const state = try self.allocator.create(jetzig.http.Request.CallbackState);
|
||||||
const arena = try self.allocator.create(std.heap.ArenaAllocator);
|
const arena = try self.allocator.create(std.heap.ArenaAllocator);
|
||||||
arena.* = std.heap.ArenaAllocator.init(self.allocator);
|
arena.* = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
@ -120,8 +122,6 @@ fn processNextRequest(
|
|||||||
|
|
||||||
const allocator = state.arena.allocator();
|
const allocator = state.arena.allocator();
|
||||||
|
|
||||||
const start_time = std.time.nanoTimestamp();
|
|
||||||
|
|
||||||
var response = try jetzig.http.Response.init(allocator, httpz_response);
|
var response = try jetzig.http.Response.init(allocator, httpz_response);
|
||||||
var request = try jetzig.http.Request.init(
|
var request = try jetzig.http.Request.init(
|
||||||
allocator,
|
allocator,
|
||||||
|
@ -44,10 +44,8 @@ pub fn log(
|
|||||||
var timestamp_buf: [256]u8 = undefined;
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
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 target = jetzig.loggers.logTarget(level);
|
||||||
|
const formatted_level = colorizedLogLevel(level);
|
||||||
|
|
||||||
try self.log_queue.print(
|
try self.log_queue.print(
|
||||||
"{s: >5} [{s}] {s}\n",
|
"{s: >5} [{s}] {s}\n",
|
||||||
@ -59,14 +57,11 @@ pub fn log(
|
|||||||
/// Log a one-liner including response status code, path, method, duration, etc.
|
/// Log a one-liner including response status code, path, method, duration, etc.
|
||||||
pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request) !void {
|
pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request) !void {
|
||||||
var duration_buf: [256]u8 = undefined;
|
var duration_buf: [256]u8 = undefined;
|
||||||
const formatted_duration = if (self.stdout_colorized)
|
const formatted_duration = try jetzig.colors.duration(
|
||||||
try jetzig.colors.duration(&duration_buf, jetzig.util.duration(request.start_time))
|
&duration_buf,
|
||||||
else
|
jetzig.util.duration(request.start_time),
|
||||||
try std.fmt.bufPrint(
|
self.stdout_colorized,
|
||||||
&duration_buf,
|
);
|
||||||
"{}",
|
|
||||||
.{std.fmt.fmtDurationSigned(jetzig.util.duration(request.start_time))},
|
|
||||||
);
|
|
||||||
|
|
||||||
const status: jetzig.http.status_codes.TaggedStatusCode = switch (request.response.status_code) {
|
const status: jetzig.http.status_codes.TaggedStatusCode = switch (request.response.status_code) {
|
||||||
inline else => |status_code| @unionInit(
|
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;
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||||
|
|
||||||
var level_buf: [16]u8 = undefined;
|
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
||||||
const formatted_level = try colorizedLogLevel(.INFO, &level_buf, self.log_queue.reader.stdout_file);
|
|
||||||
|
|
||||||
try self.log_queue.print("{s: >5} [{s}] [{s}/{s}/{s}] {s}\n", .{
|
try self.log_queue.print("{s: >5} [{s}] [{s}/{s}/{s}] {s}\n", .{
|
||||||
formatted_level,
|
formatted_level,
|
||||||
@ -98,13 +92,13 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
|||||||
}, .stdout);
|
}, .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) {
|
return switch (level) {
|
||||||
.TRACE => jetzig.colors.colorize(.white, buf, @tagName(level), file),
|
.TRACE => jetzig.colors.white(@tagName(level)),
|
||||||
.DEBUG => jetzig.colors.colorize(.cyan, buf, @tagName(level), file),
|
.DEBUG => jetzig.colors.cyan(@tagName(level)),
|
||||||
.INFO => jetzig.colors.colorize(.blue, buf, @tagName(level) ++ " ", file),
|
.INFO => jetzig.colors.blue(@tagName(level) ++ " "),
|
||||||
.WARN => jetzig.colors.colorize(.yellow, buf, @tagName(level) ++ " ", file),
|
.WARN => jetzig.colors.yellow(@tagName(level) ++ " "),
|
||||||
.ERROR => jetzig.colors.colorize(.red, buf, @tagName(level), file),
|
.ERROR => jetzig.colors.red(@tagName(level)),
|
||||||
.FATAL => jetzig.colors.colorize(.red, buf, @tagName(level), file),
|
.FATAL => jetzig.colors.red(@tagName(level)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const jetzig = @import("../../jetzig.zig");
|
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.stdout_is_tty = stdout_file.isTty();
|
||||||
self.stderr_is_tty = stderr_file.isTty();
|
self.stderr_is_tty = stderr_file.isTty();
|
||||||
|
|
||||||
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color;
|
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color;
|
||||||
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color;
|
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color;
|
||||||
|
|
||||||
self.state = .ready;
|
self.state = .ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +167,8 @@ pub const Reader = struct {
|
|||||||
|
|
||||||
var stdout_written = false;
|
var stdout_written = false;
|
||||||
var stderr_written = false;
|
var stderr_written = false;
|
||||||
|
var file: std.fs.File = undefined;
|
||||||
|
var colorize = false;
|
||||||
|
|
||||||
while (try self.queue.popFirst()) |event| {
|
while (try self.queue.popFirst()) |event| {
|
||||||
self.queue.writer.mutex.lock();
|
self.queue.writer.mutex.lock();
|
||||||
@ -172,10 +177,18 @@ pub const Reader = struct {
|
|||||||
const writer = switch (event.target) {
|
const writer = switch (event.target) {
|
||||||
.stdout => blk: {
|
.stdout => blk: {
|
||||||
stdout_written = true;
|
stdout_written = true;
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
file = self.stdout_file;
|
||||||
|
colorize = self.queue.stdout_colorize;
|
||||||
|
}
|
||||||
break :blk stdout_writer;
|
break :blk stdout_writer;
|
||||||
},
|
},
|
||||||
.stderr => blk: {
|
.stderr => blk: {
|
||||||
stderr_written = true;
|
stderr_written = true;
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
file = self.stderr_file;
|
||||||
|
colorize = self.queue.stderr_colorize;
|
||||||
|
}
|
||||||
break :blk stderr_writer;
|
break :blk stderr_writer;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -187,7 +200,11 @@ pub const Reader = struct {
|
|||||||
continue;
|
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;
|
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" {
|
test "print to stdout and stderr" {
|
||||||
var log_queue = LogQueue.init(std.testing.allocator);
|
var log_queue = LogQueue.init(std.testing.allocator);
|
||||||
defer log_queue.deinit();
|
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 {
|
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
if (response.status_code != .moved_permanently and response.status_code != .found) return;
|
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| {
|
if (response.headers.get("Location")) |location| {
|
||||||
response.headers.remove("Location");
|
|
||||||
response.status_code = .ok;
|
response.status_code = .ok;
|
||||||
request.response_data.reset();
|
request.response_data.reset();
|
||||||
try response.headers.append("HX-Redirect", location);
|
try response.headers.append("HX-Redirect", location);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user