Skip to content

Commit cf007db

Browse files
committed
fix(clk-gateway): log unhandled route errors, harden ZyFi failure paths
Builds on #118, which added ZyFi status and body logging. Three gaps remain. 1. The global Express error middleware returns a 500 to the client but never logs, so no unexpected error on any route leaves a server-side trace. Log the message and stack for 500s, and log handled HttpErrors at warn level with method and URL. 2. `fetchZyfiSponsored` did not distinguish a network-level failure (DNS, TLS, connection reset) from an HTTP rejection. The request never reaches ZyFi in that case, so there is no status or body to report; say so explicitly. 3. `await response.json()` ran before the `response.ok` check, so a non-JSON error body — an HTML or empty response from a proxy or load balancer in front of ZyFi — threw a SyntaxError that discarded the status and left the same blind spot #118 set out to close. Read the body as text first, then parse, so the status survives a non-JSON response. Keeps the `operation` label convention introduced in #118.
1 parent 73deb56 commit cf007db

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

‎clk-gateway/src/helpers.ts‎

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,50 @@ export async function fetchZyfiSponsored(
197197
operation = "unknown",
198198
): Promise<ZyfiSponsoredResponse> {
199199
console.log(`ZyFi request [${operation}]: ${JSON.stringify(request)}`);
200-
const response = await fetch(zyfiSponsoredUrl!, {
201-
method: "POST",
202-
headers: {
203-
"Content-Type": "application/json",
204-
"X-API-KEY": process.env.ZYFI_API_KEY!,
205-
},
206-
body: JSON.stringify(request),
207-
});
208200

209-
const responseBody = await response.json();
201+
// NB: `Response` in this module resolves to Express's type, so infer from fetch.
202+
let response: Awaited<ReturnType<typeof fetch>>;
203+
try {
204+
response = await fetch(zyfiSponsoredUrl!, {
205+
method: "POST",
206+
headers: {
207+
"Content-Type": "application/json",
208+
"X-API-KEY": process.env.ZYFI_API_KEY!,
209+
},
210+
body: JSON.stringify(request),
211+
});
212+
} catch (error) {
213+
// Network-level failure (DNS, TLS, connection reset). The request never
214+
// reached ZyFi, so there is no status or body to report.
215+
const cause = error instanceof Error ? error.message : String(error);
216+
console.error(`ZyFi network failure [${operation}]: ${cause}`);
217+
throw new Error(`Failed to reach zyfi sponsored [${operation}]: ${cause}`);
218+
}
219+
220+
// Read as text before parsing: an error emitted by a proxy or load balancer
221+
// in front of ZyFi may be HTML or empty, and parsing that as JSON throws a
222+
// SyntaxError that discards the status needed to diagnose the failure.
223+
const rawBody = await response.text();
224+
let responseBody: unknown;
225+
try {
226+
responseBody = JSON.parse(rawBody);
227+
} catch {
228+
if (!response.ok) {
229+
console.error(
230+
`ZyFi error [${operation}] ${response.status} ${response.statusText} (non-JSON body): ${rawBody}`,
231+
);
232+
throw new Error(
233+
`Failed to fetch zyfi sponsored [${operation}]: ${response.status} ${response.statusText} — ${rawBody}`,
234+
);
235+
}
236+
console.error(
237+
`ZyFi returned a non-JSON success body [${operation}]: ${rawBody}`,
238+
);
239+
throw new Error(
240+
`Failed to parse zyfi sponsored response [${operation}]: ${rawBody}`,
241+
);
242+
}
243+
210244
if (!response.ok) {
211245
console.error(
212246
`ZyFi error [${operation}] ${response.status} ${response.statusText}:`,

‎clk-gateway/src/index.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,11 +518,20 @@ app.use('/resolve', resolveRouter);
518518

519519
app.use((err: Error, req: Request, res: Response, next: NextFunction): void => {
520520
if (err instanceof HttpError) {
521+
console.warn(
522+
`${req.method} ${req.originalUrl} -> ${err.statusCode}: ${err.message}`
523+
);
521524
res.status(err.statusCode).json({ error: err.message });
522525
return;
523526
}
524527

525528
const message = err instanceof Error ? err.message : String(err);
529+
// Unexpected failures were previously returned to the client but never
530+
// logged, leaving no server-side trace to debug from.
531+
console.error(`${req.method} ${req.originalUrl} -> 500: ${message}`);
532+
if (err instanceof Error && err.stack) {
533+
console.error(err.stack);
534+
}
526535
res.status(500).json({ error: message });
527536
});
528537

0 commit comments

Comments
 (0)