Skip to content

Commit b2f6872

Browse files
mortondevclaude
andcommitted
chore(web): stop logging healthy /api/health probe traffic
The k8s liveness/readiness probe hits /api/health every ~2s per pod, so the access log was ~40k "request completed" lines/day/tenant of pure noise (the dominant signal in Loki across the fleet). Skip the completion log line only for SUCCESSFUL health probes (pathname == /api/health && status < 400). Unhealthy probes (>= 400) and any thrown error still log, so probe failures remain visible. All other routes are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a803cdf commit b2f6872

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

apps/web/src/lib/server/middleware/__tests__/request-context.test.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,51 @@ describe('handleRequestWithContext', () => {
7070
expect(completed.request_id).toBeDefined()
7171
})
7272

73+
it('does NOT log completion for a healthy /api/health probe', async () => {
74+
const cap = capture()
75+
const request = new Request('http://localhost/api/health')
76+
77+
await handleRequestWithContext({
78+
request,
79+
log: cap.log,
80+
next: async () => ({ response: new Response('ok', { status: 200 }) }),
81+
})
82+
83+
expect(cap.records().find((r) => r.msg === 'request completed')).toBeUndefined()
84+
})
85+
86+
it('still logs /api/health when the probe is unhealthy (status >= 400)', async () => {
87+
const cap = capture()
88+
const request = new Request('http://localhost/api/health')
89+
90+
await handleRequestWithContext({
91+
request,
92+
log: cap.log,
93+
next: async () => ({ response: new Response('unhealthy', { status: 503 }) }),
94+
})
95+
96+
const completed = cap.records().find((r) => r.msg === 'request completed')
97+
expect(completed).toBeDefined()
98+
expect(completed.status).toBe(503)
99+
})
100+
101+
it('still logs failure when /api/health throws', async () => {
102+
const cap = capture()
103+
const request = new Request('http://localhost/api/health')
104+
105+
await expect(
106+
handleRequestWithContext({
107+
request,
108+
log: cap.log,
109+
next: async () => {
110+
throw new Error('probe boom')
111+
},
112+
})
113+
).rejects.toThrow('probe boom')
114+
115+
expect(cap.records().find((r) => r.msg === 'request failed')).toBeDefined()
116+
})
117+
73118
it('logs failure and rethrows when next() throws', async () => {
74119
const cap = capture()
75120
const request = new Request('http://localhost/boom')
@@ -82,7 +127,7 @@ describe('handleRequestWithContext', () => {
82127
next: async () => {
83128
throw boom
84129
},
85-
}),
130+
})
86131
).rejects.toThrow('kaboom')
87132

88133
const failed = cap.records().find((r) => r.msg === 'request failed')

apps/web/src/lib/server/middleware/request-context.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ import { createMiddleware } from '@tanstack/react-start'
1818
import { logger } from '@/lib/server/logger'
1919
import { runWithLogContext } from '@/lib/server/log-context'
2020

21+
/**
22+
* k8s liveness/readiness probe path. Hit every ~2s per pod, so a successful
23+
* probe is pure access-log noise (~40k lines/day/tenant). We skip the
24+
* completion line for healthy probes only — an unhealthy probe (status >= 400)
25+
* or a thrown error is still logged, since those are the signal we care about.
26+
*/
27+
const HEALTH_PATH = '/api/health'
28+
2129
function deriveRequestId(request: Request): string {
2230
const header = request.headers.get('x-request-id') ?? request.headers.get('x-correlation-id')
2331
// Cap to keep a malicious/huge header out of every log line.
@@ -44,7 +52,8 @@ export async function handleRequestWithContext<T extends NextResult>({
4452
log?: AppLogger
4553
}): Promise<T> {
4654
const requestId = deriveRequestId(request)
47-
const route = `${request.method} ${new URL(request.url).pathname}`
55+
const pathname = new URL(request.url).pathname
56+
const route = `${request.method} ${pathname}`
4857
const start = performance.now()
4958

5059
return runWithLogContext({ request_id: requestId, route }, async () => {
@@ -58,7 +67,12 @@ export async function handleRequestWithContext<T extends NextResult>({
5867
// Some responses have immutable headers; correlation still works
5968
// via the logged request_id.
6069
}
61-
log.info({ status: result.response.status, duration_ms: durationMs }, 'request completed')
70+
const status = result.response.status
71+
// Suppress the completion line for successful health probes (see
72+
// HEALTH_PATH). Everything else — and unhealthy probes — still logs.
73+
if (!(pathname === HEALTH_PATH && status < 400)) {
74+
log.info({ status, duration_ms: durationMs }, 'request completed')
75+
}
6276
return result
6377
} catch (err) {
6478
const durationMs = Math.round(performance.now() - start)

0 commit comments

Comments
 (0)