Skip to content

Commit 4c0ae1f

Browse files
committed
fix(logging): improve data quality across basket and API wide events
Add numeric duration_ms in drains, drop OPTIONS noise, return 204 for bots, normalize flag fallbacks to empty string, set mcp_auth on success, remove duplicate request_id, and add stack traces to process errors.
1 parent 349d2f3 commit 4c0ae1f

8 files changed

Lines changed: 64 additions & 19 deletions

File tree

apps/api/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ process.on("unhandledRejection", (reason, _promise) => {
5757
captureError(reason);
5858
log.error({
5959
process: "unhandledRejection",
60-
reason: reason instanceof Error ? reason.message : String(reason),
60+
error: reason instanceof Error ? reason.message : String(reason),
61+
error_stack: reason instanceof Error ? reason.stack : undefined,
62+
error_source: "process",
6163
});
6264
});
6365

@@ -66,6 +68,8 @@ process.on("uncaughtException", (error) => {
6668
log.error({
6769
process: "uncaughtException",
6870
error: error instanceof Error ? error.message : String(error),
71+
error_stack: error instanceof Error ? error.stack : undefined,
72+
error_source: "process",
6973
});
7074
});
7175

apps/api/src/lib/evlog-api.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,31 @@ const devFsDrain = useLocalEvlogFiles
3434
? createFsDrain({ dir: devFsLogsDir, pretty: false })
3535
: null;
3636

37+
const DURATION_REGEX = /^([\d.]+)(ms|s)$/;
38+
39+
function parseDurationMs(duration: unknown): number | undefined {
40+
if (typeof duration !== "string") {
41+
return undefined;
42+
}
43+
const match = duration.match(DURATION_REGEX);
44+
if (!match?.[1]) {
45+
return undefined;
46+
}
47+
return match[2] === "s"
48+
? Math.round(Number.parseFloat(match[1]) * 1000)
49+
: Math.round(Number.parseFloat(match[1]));
50+
}
51+
3752
/**
3853
* In development, writes NDJSON wide events to `apps/api/.evlog/logs/` (analyze-logs skill)
3954
* and still sends to Axiom via the batched pipeline. Production: Axiom only.
4055
*/
4156
export async function apiLoggerDrain(ctx: DrainContext): Promise<void> {
57+
const durationMs = parseDurationMs(ctx.event.duration);
58+
if (durationMs !== undefined) {
59+
ctx.event.duration_ms = durationMs;
60+
}
61+
4262
if (devFsDrain) {
4363
await devFsDrain(ctx);
4464
}

apps/api/src/routes/mcp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const mcp = new Elysia({ prefix: "/v1/mcp" })
7070
})
7171
.all("/", async ({ request, user, apiKey }) => {
7272
mergeWideEvent({
73+
mcp_auth: user ? "session" : "api_key",
7374
mcp_session: Boolean(user),
7475
mcp_api_key: Boolean(apiKey),
7576
});

apps/api/src/routes/public/flags.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -483,11 +483,11 @@ export const flagsRoute = new Elysia({ prefix: "/v1/flags" })
483483
"/evaluate",
484484
async function evaluateFlagEndpoint({ query, set }) {
485485
mergeWideEvent({
486-
flag_key: query.key || "missing",
487-
flag_client_id: query.clientId || "missing",
486+
flag_key: query.key || "",
487+
flag_client_id: query.clientId || "",
488488
flag_has_user_id: Boolean(query.userId),
489489
flag_has_email: Boolean(query.email),
490-
flag_environment: query.environment || "missing",
490+
flag_environment: query.environment || "",
491491
});
492492

493493
try {
@@ -557,10 +557,10 @@ export const flagsRoute = new Elysia({ prefix: "/v1/flags" })
557557
async function bulkEvaluateFlags({ query, set }) {
558558
mergeWideEvent({
559559
flag_bulk: true,
560-
flag_client_id: query.clientId || "missing",
560+
flag_client_id: query.clientId || "",
561561
flag_has_user_id: Boolean(query.userId),
562562
flag_has_email: Boolean(query.email),
563-
flag_environment: query.environment || "missing",
563+
flag_environment: query.environment || "",
564564
});
565565

566566
try {
@@ -633,8 +633,8 @@ export const flagsRoute = new Elysia({ prefix: "/v1/flags" })
633633
"/definitions",
634634
async function getDefinitionsEndpoint({ query, set }) {
635635
mergeWideEvent({
636-
flag_client_id: query.clientId || "missing",
637-
flag_environment: query.environment || "missing",
636+
flag_client_id: query.clientId || "",
637+
flag_environment: query.environment || "",
638638
});
639639

640640
try {

apps/api/src/routes/query.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,6 @@ export const query = new Elysia({ prefix: "/v1/query" })
973973
mergeWideEvent({
974974
websites_count: count,
975975
auth_method: ctx.authMethod,
976-
request_id: requestId,
977976
});
978977
return { success: true, requestId, websites: list, total: count };
979978
})()
@@ -1090,7 +1089,6 @@ export const query = new Elysia({ prefix: "/v1/query" })
10901089
mergeWideEvent({
10911090
query_is_batch: isBatch,
10921091
query_count: isBatch ? body.length : 1,
1093-
request_id: requestId,
10941092
});
10951093

10961094
if (isBatch) {

apps/basket/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ process.on("unhandledRejection", (reason, _promise) => {
3131
captureError(reason);
3232
log.error({
3333
process: "unhandledRejection",
34-
reason: reason instanceof Error ? reason.message : String(reason),
34+
error: reason instanceof Error ? reason.message : String(reason),
35+
error_stack: reason instanceof Error ? reason.stack : undefined,
36+
error_source: "process",
3537
});
3638
});
3739

@@ -40,6 +42,8 @@ process.on("uncaughtException", (error) => {
4042
log.error({
4143
process: "uncaughtException",
4244
error: error instanceof Error ? error.message : String(error),
45+
error_stack: error instanceof Error ? error.stack : undefined,
46+
error_source: "process",
4347
});
4448
});
4549

apps/basket/src/lib/evlog-basket.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,35 @@ const devFsDrain = useLocalEvlogFiles
3737
? createFsDrain({ dir: devFsLogsDir, pretty: false })
3838
: null;
3939

40+
const DURATION_MS_REGEX = /^([\d.]+)(ms|s)$/;
41+
42+
function parseDurationMs(duration: unknown): number | undefined {
43+
if (typeof duration !== "string") {
44+
return undefined;
45+
}
46+
const match = duration.match(DURATION_MS_REGEX);
47+
if (!match?.[1]) {
48+
return undefined;
49+
}
50+
return match[2] === "s"
51+
? Math.round(Number.parseFloat(match[1]) * 1000)
52+
: Math.round(Number.parseFloat(match[1]));
53+
}
54+
4055
/**
4156
* In development, writes NDJSON wide events to `apps/basket/.evlog/logs/` (analyze-logs skill)
4257
* and still sends to Axiom via the batched pipeline. Production: Axiom only.
4358
*/
4459
export async function basketLoggerDrain(ctx: DrainContext): Promise<void> {
60+
if (ctx.event.method === "OPTIONS") {
61+
return;
62+
}
63+
64+
const durationMs = parseDurationMs(ctx.event.duration);
65+
if (durationMs !== undefined) {
66+
ctx.event.duration_ms = durationMs;
67+
}
68+
4569
if (devFsDrain) {
4670
await devFsDrain(ctx);
4771
}

apps/basket/src/lib/request-validation.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,7 @@ export function checkForBot(
307307
);
308308

309309
return {
310-
error: new Response(JSON.stringify({ status: "ignored" }), {
311-
status: 200,
312-
headers: { "Content-Type": "application/json" },
313-
}),
310+
error: new Response(null, { status: 204 }),
314311
};
315312
}
316313

@@ -325,10 +322,7 @@ export function checkForBot(
325322
);
326323

327324
return {
328-
error: new Response(JSON.stringify({ status: "ignored" }), {
329-
status: 200,
330-
headers: { "Content-Type": "application/json" },
331-
}),
325+
error: new Response(null, { status: 204 }),
332326
};
333327
});
334328
}

0 commit comments

Comments
 (0)