log slow requests
This commit is contained in:
parent
b422718f3a
commit
297c1c8bb8
1 changed files with 79 additions and 0 deletions
79
src/main.zig
79
src/main.zig
|
|
@ -22,16 +22,26 @@ const App = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
config: zfin.Config,
|
config: zfin.Config,
|
||||||
svc: zfin.DataService,
|
svc: zfin.DataService,
|
||||||
|
/// Threshold in milliseconds above which a request is logged
|
||||||
|
/// as slow. Tunable via `ZFIN_SERVER_SLOW_MS` env var; defaults
|
||||||
|
/// to 500ms. Captured once at App.init so the dispatch hot path
|
||||||
|
/// doesn't re-parse on every request.
|
||||||
|
slow_threshold_ms: u64,
|
||||||
|
|
||||||
fn init(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process.Environ.Map) App {
|
fn init(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process.Environ.Map) App {
|
||||||
const config = zfin.Config.fromEnv(io, allocator, environ);
|
const config = zfin.Config.fromEnv(io, allocator, environ);
|
||||||
const svc = zfin.DataService.init(io, allocator, config);
|
const svc = zfin.DataService.init(io, allocator, config);
|
||||||
|
const slow_threshold_ms = if (environ.get("ZFIN_SERVER_SLOW_MS")) |s|
|
||||||
|
std.fmt.parseInt(u64, s, 10) catch 500
|
||||||
|
else
|
||||||
|
500;
|
||||||
return .{
|
return .{
|
||||||
.io = io,
|
.io = io,
|
||||||
.environ = environ,
|
.environ = environ,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.config = config,
|
.config = config,
|
||||||
.svc = svc,
|
.svc = svc,
|
||||||
|
.slow_threshold_ms = slow_threshold_ms,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +49,46 @@ const App = struct {
|
||||||
self.svc.deinit();
|
self.svc.deinit();
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// httpz dispatch hook: every request flows through here so we
|
||||||
|
/// have a single place to wrap timing and error logging without
|
||||||
|
/// modifying every handler. Slow requests (above
|
||||||
|
/// `slow_threshold_ms`) and error responses (status >= 400)
|
||||||
|
/// emit a structured stderr line; everything else stays silent.
|
||||||
|
pub fn dispatch(self: *App, action: httpz.Action(*App), req: *httpz.Request, res: *httpz.Response) !void {
|
||||||
|
// wall-clock required: per-request elapsed for slow-request
|
||||||
|
// logging. `.awake` (monotonic) avoids spurious negatives
|
||||||
|
// on system clock skew.
|
||||||
|
const start_ns = std.Io.Timestamp.now(self.io, .awake).nanoseconds;
|
||||||
|
try action(self, req, res);
|
||||||
|
|
||||||
|
// using defer here so we execute unconditionally
|
||||||
|
defer {
|
||||||
|
const elapsed_ns = std.Io.Timestamp.now(self.io, .awake).nanoseconds - start_ns;
|
||||||
|
const elapsed_ms: u64 = @intCast(@divTrunc(elapsed_ns, std.time.ns_per_ms));
|
||||||
|
if (shouldLogRequest(elapsed_ms, res.status, self.slow_threshold_ms)) {
|
||||||
|
// wall-clock required: ts in stderr line lets the
|
||||||
|
// operator correlate slow requests with cron / system
|
||||||
|
// events using `date -d @<ts>`.
|
||||||
|
const ts = std.Io.Timestamp.now(self.io, .real).toSeconds();
|
||||||
|
log.warn("ts={d} elapsed_ms={d} status={d} method={s} path={s}", .{
|
||||||
|
ts,
|
||||||
|
elapsed_ms,
|
||||||
|
res.status,
|
||||||
|
@tagName(req.method),
|
||||||
|
req.url.path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Pure predicate: should this request emit a stderr log line?
|
||||||
|
/// Logs slow successes (above threshold) and any error response.
|
||||||
|
fn shouldLogRequest(elapsed_ms: u64, status: u16, threshold_ms: u64) bool {
|
||||||
|
return elapsed_ms > threshold_ms or status >= 400;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Route handlers ───────────────────────────────────────────
|
// ── Route handlers ───────────────────────────────────────────
|
||||||
|
|
||||||
fn handleIndex(_: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
fn handleIndex(_: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||||
|
|
@ -814,3 +862,34 @@ test "upperDupe" {
|
||||||
defer std.testing.allocator.free(result);
|
defer std.testing.allocator.free(result);
|
||||||
try std.testing.expectEqualStrings("AAPL", result);
|
try std.testing.expectEqualStrings("AAPL", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "shouldLogRequest: fast 2xx is silent" {
|
||||||
|
try std.testing.expect(!shouldLogRequest(10, 200, 500));
|
||||||
|
try std.testing.expect(!shouldLogRequest(499, 200, 500));
|
||||||
|
try std.testing.expect(!shouldLogRequest(0, 204, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shouldLogRequest: slow 2xx logs" {
|
||||||
|
try std.testing.expect(shouldLogRequest(501, 200, 500));
|
||||||
|
try std.testing.expect(shouldLogRequest(2000, 200, 500));
|
||||||
|
// Boundary: == threshold is NOT logged (strict >).
|
||||||
|
try std.testing.expect(!shouldLogRequest(500, 200, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shouldLogRequest: any error response logs regardless of timing" {
|
||||||
|
try std.testing.expect(shouldLogRequest(1, 400, 500));
|
||||||
|
try std.testing.expect(shouldLogRequest(1, 404, 500));
|
||||||
|
try std.testing.expect(shouldLogRequest(1, 500, 500));
|
||||||
|
try std.testing.expect(shouldLogRequest(1, 503, 500));
|
||||||
|
// 3xx is not flagged as error.
|
||||||
|
try std.testing.expect(!shouldLogRequest(1, 301, 500));
|
||||||
|
try std.testing.expect(!shouldLogRequest(1, 304, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shouldLogRequest: custom threshold respected" {
|
||||||
|
try std.testing.expect(!shouldLogRequest(50, 200, 100));
|
||||||
|
try std.testing.expect(shouldLogRequest(150, 200, 100));
|
||||||
|
// Higher threshold (e.g. user sets ZFIN_SERVER_SLOW_MS=2000).
|
||||||
|
try std.testing.expect(!shouldLogRequest(1500, 200, 2000));
|
||||||
|
try std.testing.expect(shouldLogRequest(2500, 200, 2000));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue