Skip to content

Commit 413d653

Browse files
0x-SquidSolclaude
andcommitted
fix: add request ID tracking for production debugging
No request correlation existed — if a user reported "I got a 500 at 3:15pm", ops could not find that specific request in logs or Sentry. Adds Hono's built-in requestId() middleware which: - Generates a UUID per request (or respects incoming X-Request-Id) - Sets X-Request-Id response header on all responses - Available via c.get("requestId") throughout the middleware chain Integration points: - Global error handler: logs requestId, includes it in error JSON response - Sentry middleware: tags every scope with request_id for event correlation - Error responses now include requestId so clients can quote it in reports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2a9b9d2 commit 413d653

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Hono } from "hono";
33
import { cors } from "hono/cors";
44
import { compress } from "hono/compress";
55
import { bodyLimit } from "hono/body-limit";
6+
import { requestId } from "hono/request-id";
67
import { serve } from "@hono/node-server";
78
import { createLogger, sendInfoAlert, getSupabase, sendCriticalAlert, truncateErrorMessage } from "@percolator/shared";
89
import { initSentry, sentryMiddleware, flushSentry } from "./middleware/sentry.js";
@@ -92,6 +93,10 @@ app.use("*", bodyLimit({
9293
onError: (c) => c.json({ error: "Request body too large" }, 413),
9394
}));
9495

96+
// Request ID — generates a UUID per request for log correlation and debugging.
97+
// Respects incoming X-Request-Id headers (e.g., from load balancers).
98+
app.use("*", requestId());
99+
95100
// Default-deny for mutation methods. Until write endpoints are added,
96101
// reject any POST/PUT/DELETE/PATCH requests that reach the API.
97102
// When write routes are needed, apply requireApiKey() from middleware/auth.ts
@@ -193,13 +198,15 @@ app.get("/", (c) => c.json({
193198

194199
// Global error handler
195200
app.onError((err, c) => {
201+
const reqId = c.get("requestId");
196202
logger.error("Unhandled error", {
203+
requestId: reqId,
197204
error: truncateErrorMessage(err.message, 120),
198205
stack: truncateErrorMessage(err.stack ?? "", 500),
199206
endpoint: c.req.path,
200207
method: c.req.method
201208
});
202-
209+
203210
// Report to Sentry (sentryMiddleware may have already captured it,
204211
// but this ensures errors from middleware chain are also caught)
205212
try {
@@ -208,14 +215,16 @@ app.onError((err, c) => {
208215
endpoint: c.req.path,
209216
method: c.req.method,
210217
handler: "onError",
218+
request_id: reqId,
211219
},
212220
});
213221
} catch (_sentryErr) {}
214-
222+
215223
// Truncate error message for API response (details only in development)
216224
const showDetails = process.env.NODE_ENV !== "production";
217225
return c.json({
218226
error: "Internal server error",
227+
requestId: reqId,
219228
...(showDetails && { details: truncateErrorMessage(err.message, 200) })
220229
}, 500);
221230
});

src/middleware/sentry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export function sentryMiddleware(): MiddlewareHandler {
5353
// Set request context
5454
scope.setTag("http.method", c.req.method);
5555
scope.setTag("http.path", c.req.path);
56+
const reqId = c.get("requestId");
57+
if (reqId) scope.setTag("request_id", reqId);
5658

5759
// Set a hashed API key fingerprint as pseudonymous user ID.
5860
// Avoid sending any raw key material (even a prefix) to third-party services.

0 commit comments

Comments
 (0)