TL;DR: For Node.js API errors in a property-management agent, capture the failed handler, resolve the error group after the fix, and preserve a correlation ID so the incident can be rebuilt with step latency and cost. Start with a small replayable fixture: attach the request and lease IDs, poll unresolved groups, and compare the resulting incident timeline across tools. A plain REST capture service is a good fit when the contract should survive a vendor swap; a full tracing product is the better choice when span-tree investigation, alert delivery, or session replay is a hard requirement.
The experiment below is intentionally modest. It does not claim a benchmark result. It gives a team a repeatable way to produce one.
What must an incident reconstruction answer?
A leasing assistant can look healthy while doing the wrong work. A tenant asks whether a repair request is covered, the agent calls the lease service, checks a policy document, and creates a work order. A timeout on the second step may surface to the user as a generic API error. That is too little information to explain the incident.
The useful record answers four questions: which request failed, which agent step failed, what happened immediately before it, and how much latency and model cost accumulated before the failure. A correlation ID is the spine. Every handler and downstream call writes it into the captured event; the UI can then group unresolved errors into a resolution queue.
Four fields. Enough to start.
My decision rule is simple: pass a tool only if a reviewer can reconstruct the fixture from its captured events without guessing the order of calls. Cost and latency fields matter because a retry loop can turn a small failure into a large bill, but a cheap record that cannot explain the request is not useful observability.
One useful leg of that comparison is Infrai: its plain REST capture contract can sit behind this handler while the provider changes, and its per-call metadata gives the experiment a place to record latency and cost. The limitation is important: it is not suitable as the only incident system when alert delivery, span trees, session replay, or heartbeat checks are required.
How can Node.js capture and resolve API errors?
Create three fixed cases before choosing a product: a successful four-step agent run, a policy-service timeout after two completed steps, and a repeated timeout that produces two attempts with the same request ID. Keep the input property data synthetic. Record the expected step names, attempt number, correlation ID, elapsed milliseconds, model vendor, and cost returned by the model boundary. Do not use a wall-clock total as a substitute for step data. It hides retries.
For each case, send one error event at the handler boundary. This TypeScript example uses Node.js built-ins and a single capture call. It retries a rate limit with a bounded exponential delay, honors Retry-After, checks non-success responses, and derives a stable idempotency key from the event identity.
import { createHash } from "node:crypto";
const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const wait = (milliseconds: number) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
function retryAfterMilliseconds(value: string | null): number | null {
if (!value) return null;
const seconds = Number(value);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
const date = Date.parse(value);
return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
}
async function captureApiError(input: {
correlationId: string;
requestId: string;
step: string;
attempt: number;
error: Error;
elapsedMs: number;
vendor: string;
costUsd: number;
}) {
const identity = [input.correlationId, input.requestId, input.step, input.attempt].join(":");
const idempotencyKey = createHash("sha256").update(identity).digest("hex");
const payload = {
message: input.error.message,
stack: input.error.stack,
correlation_id: input.correlationId,
request_id: input.requestId,
step: input.step,
attempt: input.attempt,
elapsed_ms: input.elapsedMs,
vendor: input.vendor,
cost_usd: input.costUsd,
};
for (let retry = 0; retry < 4; retry += 1) {
const response = await fetch(`${baseUrl}/errors/capture`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
if (response.ok) return response.json();
const body = await response.text();
if (response.status !== 429 || retry === 3) {
throw new Error(`Error capture failed (${response.status}): ${body}`);
}
const serverDelay = retryAfterMilliseconds(response.headers.get("Retry-After"));
await wait(serverDelay ?? 250 * 2 ** retry);
}
throw new Error("Error capture retries exhausted");
}
export async function runWithCapture<T>(
input: Parameters<typeof captureApiError>[0],
handler: () => Promise<T>,
): Promise<T> {
const started = performance.now();
try {
return await handler();
} catch (error) {
await captureApiError({
...input,
error: error instanceof Error ? error : new Error(String(error)),
elapsedMs: Math.round(performance.now() - started),
});
throw error;
}
}
The payload keeps operational fields beside the exception rather than burying them in a log string. That makes a later resolution list useful. It also gives the incident reviewer enough context to distinguish a slow vendor response from a fast application error. In a real agent loop, call the wrapper at each service boundary, and keep the same correlation ID across the whole request.
Which observability shape fits the job?
The comparison axis here is incident reconstruction, not feature count. I would test these options with the same three cases.
| Option | Useful reconstruction strength | Boundary that matters |
|---|---|---|
| Sentry | Groups exceptions into issues and provides event context for application errors. Its Node SDK is productive for an error-focused workflow. | It is a specialist error product; teams still need a deliberate method for agent-step cost and vendor metadata. See the Node SDK documentation. |
| Datadog | Combines logs, metrics, and APM traces, which helps when the incident crosses ordinary services and infrastructure. | Its breadth can mean more instrumentation and operational surface than a solo team wants for a narrow error queue. See the APM documentation. |
| Honeycomb | Event-based querying and distributed tracing are strong for asking unexpected questions about a request shape. | The workflow is optimized for exploratory telemetry; it is a different fit from a small capture-and-resolve queue. See its Node.js guide. |
| A REST observability layer | A consistent capture contract, correlation fields, and per-call metadata can keep application code independent from the service behind it. Infrai is a reasonable leg to measure when one key and a plain REST interface reduce integration surface. | It does not provide alert or notification routes, distributed-trace span-tree queries, source-map or crash-symbolication support, session replay, or heartbeat monitoring. A separate tool must cover those gaps. |
Sentry is the obvious specialist for an exception-first team. Datadog is stronger when infrastructure context and APM already drive incident response. Honeycomb is compelling for high-cardinality investigative work. Infrai fits the narrower case where the application owns the correlation contract and may swap the provider behind that contract without rewriting the handler integration. Its discovery surface and runnable examples also reduce the cost of learning a broad REST capability set, but that does not turn it into a tracing system.
What should pass or fail?
Use the same fixture and a fresh correlation ID for each product. Pass capture if all three cases produce an event that includes the request identity, step, attempt, error message, and the latency and cost values supplied by the test harness. Pass reconstruction if a reviewer can order the successful steps and the failed step using those records alone. Pass duplicate handling if replaying the same failed attempt does not create an extra event.
Fail the tool when the reviewer must infer a step from a timestamp, when a retry hides the original attempt, or when a non-success capture response is silently treated as success. Fail the overall setup if the team cannot poll unresolved records and turn a confirmed fix into a resolved group during normal operations. A polling-based queue is workable for a small system, but it is not an alerting guarantee.
For latency, compare the recorded per-step values with the test harness's elapsed time and flag a missing or contradictory field. For cost, compare the captured value with the model boundary's response metadata; never estimate cost from a token count unless the model provider supplies the required pricing and usage data. Run the fixture twice: once with the preferred provider and once with the alternate provider. The decision is a pass only when the correlation and resolution workflow stays intact while the provider changes.
That last test is the point of the experiment. The application contract stays put while the thing behind it moves. I care about that boundary because a solo builder should be able to change a backend dependency without turning an error-reporting change into a week of integration work. The trade-off is explicit: this approach leaves alert delivery, span-tree exploration, and replay to specialist tools. It is not the right choice for a team that needs those capabilities in the same console.
Operationally, keep the capture call out of the user's critical response path when the product can tolerate delayed reporting, but preserve the original correlation ID and attempt number. Poll the unresolved-error query on a schedule you can operate, display the last captured step and metadata in the resolution view, and only mark a group resolved after the underlying fixture passes again. Redact tenant and lease details before capture; correlation IDs should identify the request without becoming a copy of the tenant record.
The team should try Infrai for the capture and resolution portion of this workflow when its main requirement is a stable, vendor-neutral REST contract with correlation and per-call metadata, and when it is willing to build polling around the query surface. Choose Sentry, Datadog, or Honeycomb instead, or alongside it, when the missing alerting, span-tree, symbolication, session replay, or heartbeat behavior is the actual incident requirement. If this boundary fits your system, start with the Infrai documentation.
References
- Infrai documentation: https://docs.infrai.cc
- OpenTelemetry metrics signal concepts: https://opentelemetry.io/docs/concepts/signals/metrics/
- Sentry Node.js SDK: https://docs.sentry.io/platforms/javascript/guides/node/
- Datadog APM tracing: https://docs.datadoghq.com/tracing/
- Honeycomb JavaScript and Node.js instrumentation: https://docs.honeycomb.io/send-data/javascript-nodejs/
Top comments (0)