fix(datum): surface fetch error cause + url in logs

Node's fetch wraps the underlying network error in `.cause`; the bare
`err.message` is just "fetch failed" which tells us nothing about
DNS vs connection refused vs network unreachable. Add formatErr() that
walks .cause and includes its .code, plus the url being attempted, so
logs distinguish between the actual failure modes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-05-06 16:37:08 +01:00
parent 8f7aeb88b7
commit 0fef84e3eb

View File

@@ -45,6 +45,16 @@ let lastSnapshot: DatumSnapshot = {
};
let timer: NodeJS.Timeout | null = null;
function formatErr(err: unknown): string {
if (!(err instanceof Error)) return String(err);
const cause = (err as Error & { cause?: unknown }).cause;
if (cause instanceof Error) {
const code = (cause as Error & { code?: string }).code;
return code ? `${err.message}: ${cause.message} (${code})` : `${err.message}: ${cause.message}`;
}
return err.message;
}
function abortableSignal(timeoutMs: number): { signal: AbortSignal; cancel: () => void } {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(new Error("upstream_timeout")), timeoutMs);
@@ -71,11 +81,8 @@ async function pollOnce(): Promise<DatumSnapshot> {
threadsTimeout.cancel();
if (clientsResSettled.status === "rejected") {
const reason =
clientsResSettled.reason instanceof Error
? clientsResSettled.reason.message
: "unknown";
logger.warn({ reason }, "datum_clients_fetch_failed");
const reason = formatErr(clientsResSettled.reason);
logger.warn({ reason, url: `${config.datum.url}/clients` }, "datum_clients_fetch_failed");
return {
...lastSnapshot,
ok: false,
@@ -110,12 +117,7 @@ async function pollOnce(): Promise<DatumSnapshot> {
threadsHtml = await threadsResSettled.value.text();
} else if (threadsResSettled.status === "rejected") {
logger.warn(
{
reason:
threadsResSettled.reason instanceof Error
? threadsResSettled.reason.message
: "unknown",
},
{ reason: formatErr(threadsResSettled.reason) },
"datum_threads_fetch_failed",
);
}
@@ -151,7 +153,7 @@ async function pollOnce(): Promise<DatumSnapshot> {
} catch (err) {
clientsTimeout.cancel();
threadsTimeout.cancel();
const reason = err instanceof Error ? err.message : "unknown";
const reason = formatErr(err);
logger.warn({ reason }, "datum_poll_unexpected_error");
return {
...lastSnapshot,