API Logs: All Responses Show statusCode: null
Status: ✅ Fixed (2026-05-05) — custom res serializer added to packages/logger/src/logger.ts
File: packages/logger/src/logger.ts — createLogger() serializers
Symptom
Every single API response in logs/api.log shows "statusCode": null, regardless of the actual HTTP response code:
[09:00:01] INFO [api] request completed
res: {
"statusCode": null,
"headers": { ... },
"content-type": "application/json; charset=utf-8",
"content-length": "767"
}
responseTime: 601.83This affects every route — auth, tenant, admin — including both successful and potentially error responses. You cannot tell a 200 from a 400 or 500 from the logs alone.
Root Cause (Confirmed)
Three-component chain:
-
Fastify v4 passes its
Replywrapper (notreply.raw) to the logger inonResponse:reply.log.info({ res: reply, responseTime }, 'request completed') -
pino-std-serializers@7.1.0conditionally setsstatusCode = null:function resSerializer(res) { _res.statusCode = res.headersSent ? res.statusCode : null // ... } -
Fastify’s
Replywrapper does not exposeheadersSent— onlyreply.raw(the underlyinghttp.ServerResponse) has it. Sores.headersSentreads asundefined(falsy) →statusCode = nullfor every single response.
Fix Applied
Replaced pino.stdSerializers.res with a custom serializer in packages/logger/src/logger.ts that resolves headersSent from reply.raw when not present on the outer object:
res(reply: any) {
// FastifyReply doesn't expose headersSent; fall back to reply.raw.
const raw = reply.raw ?? reply;
const headersSent = raw.headersSent ?? true;
return {
statusCode: headersSent
? (reply.statusCode ?? raw.statusCode ?? null)
: null,
headers: typeof reply.getHeaders === "function"
? reply.getHeaders()
: typeof raw.getHeaders === "function"
? raw.getHeaders()
: {},
};
},Two regression tests added to packages/logger/src/__tests__/logger.test.ts covering Fastify Reply and raw http.ServerResponse input shapes.
Impact
- All API responses now log the correct HTTP status code
- APM / log aggregation (e.g. Loki) can now alert on 5xx rates
- Auth failures (401) are distinguishable from successes (200) in logs