Problem
The project has a structured logging helper, logEvent in lib/observability/events.ts, that emits a single JSON line with an event name and a level. It is used in only four places:
app/api/posts/route.ts
app/api/posts/[id]/vote/route.ts
app/api/teams/waitlist/route.ts
app/api/upload/presign/route.ts
Everything else logs with bare console.error and an ad hoc prefix string, including app/api/admin/posts/route.ts ([admin/posts] fetch error:), app/api/admin/posts/[id]/route.ts, app/api/newsletter/route.ts, app/api/newsletter/send/route.ts, app/api/posts/edit/[token]/route.ts, and app/api/upload/presign/route.ts (which does both, on the same error path).
Why it matters
Half the API surface is unqueryable in the Cloudflare logs. You cannot count "how many admin fetches failed today" or alert on it, because those lines are prose rather than JSON. It also means error objects are passed straight to console.error, which can serialize Supabase error payloads containing request context.
Suggested approach
- Extend
logEvent slightly: add an ISO ts, and a requestId field pulled from the incoming request where available.
- Replace every
console.error in app/api/** with a logEvent({ event: "<area>.<what_failed>", level: "error", ... }) call using a consistent <area>.<verb> naming convention. Document the convention in a short section of CONTRIBUTING.md.
- Never pass raw error objects. Pass
err instanceof Error ? err.message : "unknown" like app/api/upload/presign/route.ts already does.
- Remove the now duplicated
console.error in the presign catch block.
Done when
grep -rn "console.error" app/api returns nothing.
- Event names follow one documented convention.
- A short test asserts
logEvent output parses as JSON and contains event, level, and ts.
If you want to take this on, comment on the issue to claim it and it will be assigned. Please keep to a maximum of 2 open claims per person at a time so other contributors get a chance.
Problem
The project has a structured logging helper,
logEventinlib/observability/events.ts, that emits a single JSON line with aneventname and a level. It is used in only four places:app/api/posts/route.tsapp/api/posts/[id]/vote/route.tsapp/api/teams/waitlist/route.tsapp/api/upload/presign/route.tsEverything else logs with bare
console.errorand an ad hoc prefix string, includingapp/api/admin/posts/route.ts([admin/posts] fetch error:),app/api/admin/posts/[id]/route.ts,app/api/newsletter/route.ts,app/api/newsletter/send/route.ts,app/api/posts/edit/[token]/route.ts, andapp/api/upload/presign/route.ts(which does both, on the same error path).Why it matters
Half the API surface is unqueryable in the Cloudflare logs. You cannot count "how many admin fetches failed today" or alert on it, because those lines are prose rather than JSON. It also means error objects are passed straight to
console.error, which can serialize Supabase error payloads containing request context.Suggested approach
logEventslightly: add an ISOts, and arequestIdfield pulled from the incoming request where available.console.errorinapp/api/**with alogEvent({ event: "<area>.<what_failed>", level: "error", ... })call using a consistent<area>.<verb>naming convention. Document the convention in a short section ofCONTRIBUTING.md.err instanceof Error ? err.message : "unknown"likeapp/api/upload/presign/route.tsalready does.console.errorin the presign catch block.Done when
grep -rn "console.error" app/apireturns nothing.logEventoutput parses as JSON and containsevent,level, andts.If you want to take this on, comment on the issue to claim it and it will be assigned. Please keep to a maximum of 2 open claims per person at a time so other contributors get a chance.