A scheduled fintech import that stops producing results needs two signals: an explicit completion event from the Node.js worker and a deadline check outside that worker. That is the least complex design that catches silence. Error tracking still matters, but stack traces alone cannot report code that never ran.
TL;DR: choose an error-tracking service only after testing four things with your own Express failures: readable source-mapped stacks, stable grouping, field-level search, and controllable data handling. Keep missed-run detection independent. For rollback safety, emit a small, versioned event contract that both the old and new release understand.
| Choice | Finds thrown errors | Finds silent imports | Rollback risk | Best fit |
|---|---|---|---|---|
| Error tracking alone | Yes | No | Medium | Request and worker exceptions |
| Completion event plus deadline check | Only if wired separately | Yes | Low | Scheduled import liveness |
| Log search plus scheduled query | Sometimes | Yes | Medium | Existing structured-log estates |
| Metrics plus alert rule | No stack by itself | Yes | Low | Mature metric pipelines |
Recommendation: use completion events plus an external deadline check for the alert, then attach error-tracking issue IDs or trace context when an exception exists. This splits detection from diagnosis. It also makes a rollback boring: the monitor watches a stable contract instead of release-specific stack frames.
How should you choose an error tracking service for an Express API?
Start by separating thrown errors from missing work. An exception tracker observes exceptions that reach its SDK or ingestion path. A scheduler can fail before process startup. A queue can stop dispatching. Credentials can prevent the job from reaching the code that initializes reporting. None of those paths guarantees an exception event. Silence is the failure mode.
Model the business outcome instead. Suppose a bank-statement import is due by 02:00 UTC. On successful completion, the worker records import.completed with a tenant pseudonym, the scheduled period, a count, a release identifier, and a contract version. A separate process checks for the expected event after the deadline. Zero results can still be a valid completion; no completion event is not.
That distinction prevents a nasty false alarm. Do not use rows_imported > 0 as the liveness test. A valid empty file would page you, while a partially processed file could look healthy. The completion record should describe the terminal state explicitly.
Two criteria decide the service choice
The first criterion is rollback-safe diagnosis. Deploy a deliberately broken Express route in a disposable environment, capture it, roll back, and confirm that both releases remain searchable without changing the event contract. The useful stack should point to the TypeScript source through source maps, preserve the original error and cause chain, and show the release that produced it. Uploading source maps is part of the release process; serving them publicly is unnecessary. Grouping needs adversarial tests too. Send the same failure with different account identifiers and timestamps. It should remain one issue. Then send two failures from different call sites with the same message. They should not collapse into one. Automatic grouping is a starting hypothesis, not ground truth. Any candidate should let an operator inspect why events grouped and change the rule without destroying the underlying events. The second criterion is search with a small privacy surface. Before evaluating a UI, define the fields an incident actually needs: release, environment, import kind, pseudonymous tenant key, schedule window, outcome, and correlation ID. Search those fields in combinations that match an investigation. Free-text search is less valuable if a stack contains raw request bodies that should never have left the application.
For European workloads, make the data flow review concrete. List every captured field, where it is sent, how long it is retained, who can access it, and how deletion is handled. This is engineering input to a GDPR review, not legal advice. The safer default is allowlisting. Tokens, authorization headers, bank account details, imported row contents, and full request bodies should be excluded before serialization.
Seven fields beat a data lake when one person has to operate it.
I prefer the smaller contract because every extra captured field creates review work without necessarily shortening recovery. The trade-off is less ad hoc forensic data. When an investigation genuinely needs another field, add it deliberately and version the contract.
A small TypeScript contract survives rollbacks
The application contract can stay generic. Send the same sanitized envelope to logs, metrics adapters, or an error-event sink. The deadline checker depends only on the completion event, so changing diagnostic tooling does not change alert semantics.
interface ImportCompletion {
schemaVersion: 1;
event: "import.completed";
tenantKey: string; // Stable pseudonym; never a bank identifier.
period: string;
result: "success" | "partial" | "failed";
rowCount: number;
release: string;
correlationId: string;
completedAt: string;
}
interface EventSink {
write(event: ImportCompletion): Promise<void>;
}
async function recordCompletion(
sink: EventSink,
event: Omit<ImportCompletion, "schemaVersion" | "event">,
): Promise<void> {
await sink.write({ schemaVersion: 1, event: "import.completed", ...event });
}
Treat this schema like an API. During a rolling deploy or rollback, version N and version N-1 may both be active. Add optional fields first. Do not rename the event or change the meaning of result in the same release that changes the importer. The monitor should accept the current and previous schema while the deployment window is open.
There is another boundary worth keeping: an error event describes a failed operation, while a completion event describes the scheduled job's terminal outcome. A partial import may produce both. Link them with correlationId; do not make one impersonate the other.
For metrics derived from these events, follow a consistent naming scheme and keep labels bounded. A release label is manageable; a raw tenant or correlation ID as a metric label is not. Prometheus's naming guidance recommends a common application prefix and base units, and it warns that labels should represent the same logical quantity across dimensions. Put high-cardinality identifiers in searchable events instead.
Test the rollback, not the brochure
A feature checklist cannot prove that an incident remains diagnosable after a rollback. A one-hour bake-off can. Use a synthetic Express endpoint and a fake scheduled import; never replay production financial data.
Run five cases: a synchronous route throw, a rejected async handler, two same-message errors from different call sites, a completed import with zero rows, and a missing completion. Deploy release B, generate the cases, then return to release A. Confirm that stack frames resolve to the correct source for each release, grouping stays intelligible, searches return the expected structured fields, and the missing completion still alerts.
Also interrupt the reporting path. The import must not become successful merely because telemetry delivery succeeded, nor fail solely because a diagnostic sink is temporarily unavailable. Decide whether completion recording is transactional with the import state or written through an outbox. The right choice depends on the storage boundary, but the invariant is fixed: do not acknowledge the scheduled job until its durable outcome can be observed.
Use severity consistently. RFC 5424 defines syslog severity levels from Emergency through Debug, but a completion event is usually an informational state transition, not an error. A missed deadline is the alert condition. This keeps dashboards and pages from becoming a pile of red messages that carry no action.
Ship the monitor separately if that gives it a separate failure domain. In a one-person SaaS, I would spend complexity only where it buys recovery. Weekly shipping matters more than building a custom event platform, so outsource storage and presentation when useful, while keeping the event schema, redaction, and deadline rule under application control.
When is the runner-up a better fit?
Metrics plus an alert rule are the runner-up when the team already operates a dependable metric pipeline and only needs aggregate liveness. A counter for completions and a timestamp for the last successful period can make alert evaluation cheap and predictable. Keep the searchable completion event anyway; metrics will tell you that a run is missing, not which tenant, file, or release needs investigation.
Log-based scheduled queries fit an existing structured-log workflow, especially when operators already preserve logs across deployments. They become weaker when log retention is shorter than the investigation window, parsing rules drift between releases, or the query treats an unstructured message as a contract. If logs are the source, emit structured fields and test the exact query during every schema change.
Error tracking alone is appropriate only when the question is limited to thrown failures. It is not enough for the fintech job here. The final decision should favor evidence from the five-case rollback drill, a documented data flow, and an exportable event contract. Keep the mechanism small. Keep silence observable.
The limitation of the recommended completion-event design is that it adds a second operational path. A tiny system with no scheduler, no background worker, and only synchronous Express requests does not need it; exception tracking with structured search is the better fit there. Conversely, a team with an established metrics pipeline may prefer a last-completion timestamp and alert rule, accepting that it still needs searchable events for diagnosis.
Further reading
- Prometheus, "Metric and label naming": https://prometheus.io/docs/practices/naming/
- IETF, "RFC 5424: The Syslog Protocol": https://datatracker.ietf.org/doc/html/rfc5424
Top comments (0)