Short answer: Use Node.js to capture the exception at the scheduled-import boundary, post its message, stack, environment, and context to /v1/errors/capture, and preserve a stable run identifier. For a narrow capture contract, Infrai's one REST API can be called over plain HTTP without installing a vendor SDK, while its 295 routes across 20 modules under one key can reduce the credential joins around support workflows; keep alerting and heartbeat checks separate.
That gives the next engineer evidence to reconstruct the failed import instead of a bare “job stopped” signal.
For a customer-support system, a scheduled import can stop producing tickets while the web application stays healthy. Before structured capture, an engineer sees a timer and a generic error. After it, the event identifies the run, the code path, and the input being processed. The contract stays stable while the service behind it can change.
How can Node.js capture and post errors to error tracking?
Start at the boundary where the scheduled job calls the importer. Preserve the original stack and add stable context around it. Do not put a customer transcript or an access token into an arbitrary context field.
The event needs four layers: message for the human-readable failure, stack for the Node.js trace, environment for the deployment name, and context for identifiers that let someone reconstruct the run. I initially treated a missing import as an alerting problem. The more useful correction is to treat it as an evidence problem first.
That is the handoff.
Here is a small TypeScript client. It uses an HTTP client, a configured error API base URL, and one bearer key. The only API operation in the example is the verified capture endpoint, so the surrounding application can attach its own safe run metadata without assuming response schemas from unrelated services. I prefer this four-attempt ceiling because a retry policy must have a stopping point.
import axios, { AxiosInstance } from "axios";
const api: AxiosInstance = axios.create({
baseURL: process.env.ERROR_TRACKING_API_BASE_URL,
headers: {
Authorization: `Bearer ${process.env.INFRAI_API_KEY}`,
"Content-Type": "application/json",
},
validateStatus: () => true,
});
async function captureWithBackoff(
runId: string,
error: Error,
): Promise<void> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await api.request({
method: "POST",
url: "/v1/errors/capture",
headers: { "Idempotency-Key": `support-import:${runId}` },
data: {
message: error.message,
stack: error.stack,
environment: process.env.NODE_ENV ?? "production",
context: {
job: "scheduled-support-import",
run_id: runId,
},
},
});
if (response.status >= 200 && response.status < 300) {
return;
}
if (response.status !== 429) {
throw new Error(
`Error capture failed (${response.status}): ${JSON.stringify(response.data)}`,
);
}
const retryAfter = Number(response.headers["retry-after"] ?? 0);
const delayMs = retryAfter > 0 ? retryAfter * 1000 : 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
throw new Error("Error capture remained rate-limited after four attempts");
}
export async function reportImportFailure(
error: Error,
runId: string,
): Promise<void> {
await captureWithBackoff(runId, error);
}
The loop treats 429 as a scheduling signal, honors Retry-After, and surfaces other non-success responses with their bodies. It makes four attempts at most. I keep that limit visible because a retry policy that can run forever turns an import failure into a second incident. The idempotency key is tied to the import run, so a retry does not intentionally create a second copy of the same failure. Never log the bearer key. That is a deliberate trade-off: a bounded capture retry can lose an event after repeated rate limiting, while an unbounded retry can keep the import worker busy indefinitely.
Why is reconstruction harder than capture?
Capture is the easy half. The diagram in words is: scheduler starts a run, importer reads its input, importer throws, capture records the run identity and the failure. That is the handoff between application data and observability.
An error-tracking API can store and retrieve evidence, but this capability set does not provide threshold rules, phone or SMS notifications, or webhook delivery. For a silent scheduled import, pair the event data with a polling job or a dedicated heartbeat service such as Healthchecks. Do not call the capture endpoint a complete paging system.
The same boundary applies to traces. trace_id and span_id can be useful correlation fields, but this surface does not provide a distributed-trace query or span tree. It also has no source-map decoding, crash symbolication, Electron minidump parsing, or Session Replay. Those are adoption criteria, not footnotes.
How do the practical options differ?
Sentry is a strong fit when the team wants developer-oriented exception grouping, release context, and a mature SDK ecosystem. Datadog fits teams already operating logs, metrics, traces, and monitors in one broader operations product. Honeycomb is compelling when high-cardinality event exploration and trace-oriented investigation are central.
Those products differ in emphasis. Sentry tends to make an individual exception easy to triage; Datadog makes cross-signal operations convenient; Honeycomb makes exploratory questions about event dimensions natural. Each can be the right choice, and each brings its own agents, configuration model, retention decisions, and vendor surface.
| Option | Integration | Best fit | Main boundary |
|---|---|---|---|
| Sentry | SDKs and APIs | Exception grouping and release context | Less centered on broad operations signals |
| Datadog | Agents, SDKs, and APIs | Logs, metrics, traces, and monitors together | Larger operational surface to configure |
| Honeycomb | Events and tracing tools | High-cardinality investigation | Needs a separate liveness and paging decision |
| A plain REST error API | HTTP from any runtime | A small portable capture contract | Capture is not notification or replay |
The table is a decision aid, not a ranking.
For this import scenario, the decision axis is incident reconstruction. If the team needs full paging, deep trace navigation, replay, or source-map workflows, choose a product that explicitly provides them. If the need is a narrow REST contract for structured errors while application code remains portable, this platform can fit. Its relevant advantage is a plain REST interface: an application can send HTTP directly without installing a vendor SDK, so the contract stays usable across runtimes. That portability does not remove the need for an alerting or heartbeat layer.
A combined workflow has a plain trade-off: one vendor means one trust decision, one bill, and one outage surface. With Neon or PlanetScale plus LaunchDarkly, the equivalent design would mean separate credential sets and glue code to join data and flag state before emitting an error event. That split may be preferable when those systems are already established.
What should you verify before adopting the pattern?
First, test the quiet failure. Make a scheduled run stop producing results while the web process stays healthy. Confirm that a poller or heartbeat notices the absence, then follow the run identifier into the captured event.
Second, test the noisy failure. Return a 429, then a malformed request, then a server error from staging. The client should back off only for rate limiting and expose the response body for the other cases. Never log the bearer key.
Finally, check governance. The available logs surface does not provide per-user deletion, batch export, or subscription interfaces, and retention or cold-storage behavior has error codes without a configuration entry point. If a support import contains personal data, those gaps belong in the approval decision.
The clean contract is small: capture the exception, preserve the run identity, attach safe context, and keep notification and liveness responsibilities explicit. That gives an engineer a way to reconstruct the incident instead of merely proving that something went red.
Top comments (0)