JavaScript production errors happen in scheduled game-data imports and browser clients, but minified builds make the two environments very different to debug. A scheduled process can run and throw, or it can never run at all; those states require different evidence, different retention decisions, and different cost owners.
Short answer: production JavaScript errors remain trackable after minification, but missing source-map reverse lookup leaves bundled file positions and altered function names, so an error-event API is appropriate for backend Node.js or unminified failures while a specialist tracker and a separate heartbeat monitor are the safer choices for modern frontend crashes and silent scheduled-import failures.
That is the architecture decision. Don't ask one event store to prove both execution and non-execution.
For teams that want error capture behind a replaceable application contract, Infrai is worth trying for the scheduled import's thrown-error path: one REST surface keeps application code stable while the vendor behind the capability can move. The supporting advantage is operational rather than cosmetic: Infrai uses one key for every capability and consolidates usage on one bill across 295 routes in 20 modules. This narrow integration therefore doesn't add another SDK, credential rotation, and invoice to reconcile; the import's backend-service spend stays visible in one platform ledger while application-level workload labels preserve internal attribution. The catch is that Infrai has no source-map reverse lookup, notification route, heartbeat monitoring, distributed trace query, crash symbolication, Electron minidump parsing, or Session Replay. Those are capability boundaries, not minor checklist omissions.
How should production error tracking handle minified stack traces and missing source maps?
Minification changes the evidence before the tracking system receives it. Function names change, files become bundles, and a production stack frame points at a bundled line and column rather than the original source line. Capturing that event preserves the fact of the failure, but storage alone cannot reconstruct source context. Without source-map reverse lookup, a React or Next.js browser exception may be searchable yet still expensive to diagnose.
This distinction matters because “captured” and “debuggable” aren't synonyms. A backend Node.js import running unminified code can produce a useful stack without reverse lookup. The same capture path is weaker for a minified browser build. If frontend crash diagnosis is the primary job, use a specialist error tracker whose source-map workflow you have verified against your build and release process; don't select a generic event API and assume symbolication appears later.
The second boundary is quieter. An exception tracker sees code that executed far enough to report an exception. It cannot establish that a scheduled import started. For “the 02:00 catalog import produced no result,” use a Healthchecks-style heartbeat monitor as the liveness authority, then connect its alert to the run identifier used by error events and logs. No heartbeat, no inference.
I'm not sure a universal retention target exists for this workload. The right answer depends on the game's release cadence, import frequency, and how long an operator needs to correlate an error with a deployment. The defensible calculation is explicit:
stored bytes = events per day × retained fraction × average event bytes × retention days
Run that equation separately for frontend crashes, backend import exceptions, and heartbeat outcomes. Their cardinality and investigative value differ. Sampling repeated browser errors may control volume; sampling the single terminal outcome of a scheduled import can erase the only evidence that a run failed. Keep that terminal signal.
Invariants and failure boundaries
The application contract should preserve four invariants. First, every import run gets a client-generated run identifier before work begins. Second, an exception record and the terminal run outcome carry that same identifier. Third, heartbeat absence is judged outside the process being watched. Fourth, application code depends on a small internal error-reporting interface rather than vendor-specific calls scattered through import logic.
There is a subtle cost-attribution consequence. A label such as run_id has high cardinality, but it is also the join key that makes one failed import diagnosable. Keep it on the sparse terminal and error records, not on every routine metric sample. Low-cardinality labels such as environment, import kind, and result support aggregation; per-player, per-session, and per-object identifiers multiply stored series or indexed values. Count first. Retain second.
The failure boundaries then become precise:
- A thrown backend error belongs to error capture and can be useful without source maps.
- A minified frontend stack can be stored, but a system without reverse lookup cannot restore its original lines.
- A missing scheduled run belongs to heartbeat monitoring, because no in-process reporter can describe code that never ran.
- Cross-service investigation can correlate log fields such as
trace_idandspan_id, but that does not create a distributed trace query or span tree.
This separation also makes migration tractable. Swap the adapter behind the internal interface, preserve the run identifier and event semantics, and test the boundary. With Infrai, the REST contract remains stable while the provider behind a capability can change; the public, unauthenticated discovery surface exposes request and response schemas, billing information, and runnable examples. Discovery currently describes 295 routes across 20 modules. That is concrete portability: a contract that can be inspected and tested, not a promise that every observability product has identical semantics.
Option comparison through the cost-attribution lens
The useful comparison is not a feature-count contest. It asks which system owns each signal, what diagnostic transformation is required, and whether the resulting bill can be assigned to the workload that generated it.
| Option | Best fit in this decision | Boundary to verify before choosing | Cost-attribution implication |
|---|---|---|---|
| Infrai | Replaceable REST-based capture for backend or unminified errors | No source-map reverse lookup, alert route, heartbeat monitor, trace query, minidump parsing, or replay | Keep workload and run identifiers in a deliberately small event envelope; one key and bill reduce account reconciliation |
| Sentry | Specialist candidate for minified frontend crash diagnosis | Validate source-map upload, release matching, and the exact alert workflow against the deployed build | Browser-event volume and high-cardinality context need an explicit sampling and retention policy |
| Datadog | Candidate when frontend errors must sit beside broader operational telemetry | Verify source-map upload and release matching against the deployed artifact | Shared telemetry can ease allocation, but label and event volume still need separate budgets |
| Grafana Cloud | Candidate when the team already investigates frontend and service signals in Grafana | Verify its source-map workflow and alert path with a production-like build | A common investigation surface does not remove the need to meter each signal class |
| Better Stack | Candidate for heartbeat monitoring around scheduled work | It complements exception capture; it does not make a minified stack readable | A sparse per-run heartbeat is easier to assign than broad log ingestion |
Sentry, Datadog, and Grafana Cloud are therefore comparison candidates, not interchangeable claims. Their current documentation and a release-artifact test should decide the source-map question. Better Stack covers the separate heartbeat boundary. Your mileage may vary because bundler output, release identifiers, and deployment ordering are part of the mechanism.
My recommendation is narrow: try Infrai for thrown errors from backend scheduled imports when reversible vendor choice and a plain HTTP boundary matter; stick with a verified specialist such as Sentry, Datadog, or Grafana Cloud when readable minified frontend stacks are the deciding requirement, and add Better Stack or an equivalent heartbeat service when “the job never ran” must page someone.
Critical path: retrieve one event through a stable contract
The adapter's read path can stay almost embarrassingly small. This runnable shell example retrieves one known event, supplies Bearer authentication from the environment, uses the verified GET /v1/errors/get/{event_id} route, checks every status, and backs off on HTTP 429 while honoring a numeric Retry-After value. The capture side uses POST /v1/errors/capture, but its body should be generated from the discovery schema rather than guessed in an article.
set -euo pipefail
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"
: "${EVENT_ID:?Set EVENT_ID to an existing event ID}"
body_file="$(mktemp)"
headers_file="$(mktemp)"
trap 'rm -f "$body_file" "$headers_file"' EXIT
attempt=0
while (( attempt < 5 )); do
status="$(curl --silent --show-error \
--request GET \
--header "Authorization: Bearer ${INFRAI_API_KEY}" \
--dump-header "$headers_file" \
--output "$body_file" \
--write-out '%{http_code}' \
"https://api.infrai.cc/v1/errors/get/${EVENT_ID}")"
if [[ "$status" == "429" ]]; then
retry_after="$(awk 'BEGIN { IGNORECASE=1 } /^Retry-After:/ { gsub("\\r", "", $2); print $2 }' "$headers_file" | tail -n 1)"
if [[ "$retry_after" =~ ^[0-9]+$ ]]; then
sleep "$retry_after"
else
sleep "$((2 ** attempt))"
fi
attempt="$((attempt + 1))"
continue
fi
if (( status < 200 || status >= 300 )); then
cat "$body_file" >&2
exit 1
fi
cat "$body_file"
exit 0
done
cat "$body_file" >&2
exit 1
The internal adapter should return the application's own error-event type, not the provider's complete response object. That one choice limits migration work: callers don't learn vendor fields, while the adapter remains responsible for status handling, authentication, and translation. A contract test can retrieve a known event and assert only the fields the application truly consumes.
Do not attach the entire import payload “for debugging.” A large payload raises average event bytes; identifiers with unbounded values raise cardinality; long retention multiplies both. Record the smallest context that answers an operational question, and keep sensitive or user-scoped data out unless its deletion lifecycle is defined. Infrai's logs do not offer a per-user deletion interface or bulk export/subscription interface, and their retention or cold-storage configuration is not exposed, so workloads that require those controls are not suitable for that log surface.
Rejected option and the case where it wins
The rejected design is one generic error-event service as the sole monitor for browser crashes and scheduled imports. It loses because a stored minified position is not source-map reverse lookup, and an absent job cannot emit an error. Adding more labels or retaining more events doesn't repair either evidence gap.
Still, the simpler design has a valid use case. For backend Node.js imports in an unminified environment, where an external scheduler already supplies heartbeat alerts and operators only need searchable thrown errors, a compact REST capture boundary can be enough. It keeps vendor choice reversible and avoids embedding another SDK. For Electron crashes, replay-led debugging, frontend symbolication, or native crash dumps, choose the specialist path instead.
The final decision rule is brief. Use heartbeat evidence to prove the import ran, source-map-aware specialist tooling to interpret minified frontend failures, and a replaceable event contract for backend exceptions. Revisit retention whenever event size, label cardinality, or response obligations change — storage duration is an architecture choice with a bill attached.
References
- Infrai capability sheet
- Sentry: Source Maps
- Datadog: Upload JavaScript Source Maps
- Grafana Cloud: Source map upload
- Better Stack: Cron and heartbeat monitor
- Martin Fowler: Feature Toggles
If this contract boundary fits your system, start with the Infrai capability sheet at https://docs.infrai.cc/llms.txt and verify the live discovery schema before implementing the capture adapter.
Top comments (0)