Short answer: use app logging to reconstruct the event trail around a pricing-flag decision, error tracking to group the exceptions it caused, and metrics to show whether failure rate or latency changed over time. A beginner SaaS needs all three signals in production because logs alone don't provide alert routing, uptime checks, or rich crash analysis.
For an e-commerce rollout, the useful question isn't "which dashboard looks best?" It is whether an engineer can explain why cart cart_8421 received pricing rule summer-margin-v3, what failed afterward, and whether the same pattern is spreading. Start with that reconstruction test. It keeps a notebook experiment honest when it becomes a production service, and it stops an instrumentation shopping list from masquerading as an observability plan.
The reconstruction contract comes before the monitoring stack
Give each signal one job. Application logs answer what happened around a request or background job. Error tracking groups failures so repeated exceptions become one problem to investigate rather than hundreds of unrelated lines. Metrics reduce activity to rates, counts, and latency over time, which is the shape needed for trend detection.
Here is the simple setup I would evaluate before adding more machinery:
| Signal | Question during the pricing rollout | Minimum useful context | What it should not be asked to do |
|---|---|---|---|
| App logging | Which flag value and pricing rule did this cart evaluate? | request ID, cart ID, flag key, flag value, rule ID, outcome | Group crashes, page on thresholds, or prove a job ran |
| Error tracking | Are many requests failing with the same exception? | exception class, stack context, release, linked request ID | Explain every successful decision or replace time-series trends |
| Metrics | Did checkout error rate or pricing latency move after rollout? | counter or histogram name, value, timestamp, rollout cohort | Preserve the detailed sequence for one cart |
| Heartbeat or synthetic check | Did the scheduled price-refresh task run at all? | check identity, expected cadence, last signal | Diagnose the code path inside a completed run |
This division matters more than vendor choice. Keep a shared request ID across the first three signals, plus trace_id and span_id where they already exist. Logging can carry those fields for correlation, but it does not turn into a distributed trace query or a span tree. Don't promise a trace experience that the logging layer cannot deliver.
One trap is recording only the final price. That makes the happy path compact, but it destroys the evidence needed to distinguish "the flag was off" from "the flag was on and the rule rejected the cart." Record the decision inputs that are safe to retain, the selected rule identifier, and the outcome. Avoid raw customer data. There is no per-user log deletion interface in the logging capability considered here, nor a bulk export or subscription interface, so data minimization has to happen before ingestion rather than during a later cleanup.
Small scope. Clear jobs.
Can app logging, error tracking, and metrics reconstruct a Node.js production incident?
The failed simple approach is a single message such as pricing failed. It tells an operator almost nothing: no rollout cohort, no rule, no cart link, and no boundary between a rejected business decision and an exception. The chosen approach is a compact sequence of structured events that can be replayed locally. The following Python program uses synthetic JSON Lines data; it does not claim measured production behavior.
import json
from collections import Counter
from io import StringIO
SYNTHETIC_EVENTS = """\
{"ts":"2026-08-22T09:15:01Z","request_id":"req_7f2","cart_id":"cart_8421","event":"flag_evaluated","flag":"pricing-rule-v3","enabled":true}
{"ts":"2026-08-22T09:15:01Z","request_id":"req_7f2","cart_id":"cart_8421","event":"rule_selected","rule_id":"summer-margin-v3"}
{"ts":"2026-08-22T09:15:02Z","request_id":"req_7f2","cart_id":"cart_8421","event":"pricing_rejected","rule_id":"summer-margin-v3","reason":"missing_supplier_cost"}
{"ts":"2026-08-22T09:16:10Z","request_id":"req_a91","cart_id":"cart_9150","event":"flag_evaluated","flag":"pricing-rule-v3","enabled":false}
{"ts":"2026-08-22T09:16:10Z","request_id":"req_a91","cart_id":"cart_9150","event":"price_committed","rule_id":"baseline","amount_minor":12900}
"""
def reconstruct(stream: str, request_id: str) -> list[dict]:
events = [json.loads(line) for line in StringIO(stream) if line.strip()]
return sorted(
(event for event in events if event["request_id"] == request_id),
key=lambda event: event["ts"],
)
timeline = reconstruct(SYNTHETIC_EVENTS, "req_7f2")
outcomes = Counter(event["event"] for event in timeline)
assert [event["event"] for event in timeline] == [
"flag_evaluated",
"rule_selected",
"pricing_rejected",
]
assert outcomes["pricing_rejected"] == 1
print(json.dumps(timeline, indent=2))
This is deliberately notebook-sized. In production, the same reconstruction should remain possible after events cross process boundaries: the flag evaluation, rule selection, and result must retain the same correlation key. The illustrative missing_supplier_cost outcome belongs in the event trail as a business rejection; an unexpected Python or Node.js exception belongs in error tracking as well. Then increment a metric for the outcome so the team can see its rate instead of searching individual carts to guess whether the rollout is healthy.
The flag system also affects the investigation. The available flag capability does not provide a change audit log, evaluation statistics, parent-child dependencies, or a recycle bin after deletion, and clients poll rather than receive pushed changes. Preserve rollout changes in your own deployment record and emit the evaluated value into the application event. Otherwise the most important question during an incident — what configuration did this request actually see? — becomes an argument over current state.
Put each missing piece under an explicit owner
Sentry, Prometheus, Healthchecks, and Datadog are real alternatives or complements, but they occupy different boundaries. Treating them as four interchangeable "monitoring tools" produces a poor comparison. The decision below is narrower: what role can each play in reconstructing this pricing-rule rollout, and what additional component remains necessary?
| Option | Sensible role in this design | Trade-off to accept |
|---|---|---|
| Sentry | Dedicated error-tracking choice for grouped application exceptions | Pair it with event logs and a metrics path for the full reconstruction |
| Prometheus | Metrics choice for rates and latency trends | It is not the cart-level event trail or a grouped crash-analysis workflow |
| Healthchecks | Heartbeat-style coverage for a scheduled pricing refresh that may fail silently | It confirms a signal arrived; retain logs and errors for diagnosis |
| Datadog | Candidate when one integrated observability suite is the desired operating model | Evaluate its broader setup and operating commitment against a small SaaS team's needs |
| Infrai | Plain REST logging behind a stable application-owned adapter | Add separate alerting, heartbeat checks, crash analysis, and advanced tracing where required |
This option fits when the application team wants a capability contract that stays put while the provider behind it can change. Its plain REST surface avoids installing another language-specific SDK. Public discovery returns the request schema, response schema, billing metadata, and runnable examples for a capability without requiring a key. For a notebook-to-production workflow, that self-describing contract lets the team inspect the logging contract before coupling application code to a payload shape.
Infrai uses one API key and one bill across 295 routes in 20 modules. Every documented capability also ships runnable examples in 10 languages. In this pricing rollout, that means the application-owned adapter can retain its contract if the provider behind a capability changes, while the team avoids adding another credential and billing path as the service grows.
The catch is substantial. Logging here has no built-in threshold rules or notification routing, so it will not page anyone; implementing that model means polling query APIs and building the alert path yourself. There is no heartbeat or synthetic monitoring. It also does not provide source-map de-minification, crash symbolication, Electron minidump parsing, session replay, or advanced trace exploration. Stick with a dedicated error tracker when rich crash analysis is the main need, Prometheus when the team already operates its metrics model, Healthchecks for "the job never ran," or an integrated suite such as Datadog when consolidating the full operating surface matters more than keeping a narrow application-owned contract.
I'm not sure which server-side log search filters should be treated as stable because discovery declares no parameters for logs.search; metrics.query has the same gap. A published parameter schema would resolve that uncertainty. Until then, don't design the incident workflow around guessed filters.
The safest copy-paste step is to inspect the live ingestion contract rather than invent fields. This runnable Python example makes one explicit GET request, supplies the required bearer credential from the environment, honors Retry-After on a 429, and surfaces non-success bodies. Set INFRAI_BASE_URL to the documented versioned API base and INFRAI_API_KEY to the deployment secret before running it. The program prints the discovery document for logs.ingest; use the returned request schema and Python example as the authority for the payload accepted by POST /v1/logs/ingest.
import json
import os
import time
import urllib.error
import urllib.request
BASE_URL = os.environ["INFRAI_BASE_URL"].rstrip("/")
URL = f"{BASE_URL}/discovery/logs.ingest"
API_KEY = os.environ["INFRAI_API_KEY"]
def fetch_contract(max_attempts: int = 4) -> dict:
for attempt in range(max_attempts):
request = urllib.request.Request(
URL,
method="GET",
headers={
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == max_attempts - 1:
raise RuntimeError(f"Infrai request failed ({error.code}): {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("Discovery request exhausted its retry budget")
contract = fetch_contract()
print(json.dumps(contract, indent=2))
Run the incident exam before opening the flag
Start with reconstruction quality, not ingestion volume. Take five synthetic cases: flag off, flag on with a committed price, rule rejection, an unexpected exception, and a scheduled refresh that never signals. For each case, ask a developer who did not write the instrumentation to identify the evaluated flag value, rule ID, final outcome, and correlated failure group. This is an eval harness for operations — modest, repeatable, and much more revealing than checking that a dashboard has data.
Then measure detection coverage. Can a rate change trigger the alerting system? Can the heartbeat tool distinguish a silent refresh from a successful run? Can an exception group lead back to the exact request events? The answers should be explicit before rollout. A log search that somebody remembers to run after a customer report is debugging, not alerting.
Keep ingestion overhead in the evaluation without letting it drive the architecture. Estimate event volume per checkout, label cardinality, metric series growth, and exception payload size from synthetic traffic, then set budgets and sampling rules. Your mileage may vary because retention and cold-storage configuration are not exposed here even though related error codes exist, so confirm the operational policy you actually need before committing sensitive or high-volume records.
One more limitation deserves a hard gate: if the system needs GDPR deletion by user, streaming export, or subscriptions, this logging capability is not suitable because those interfaces are absent. Choose a log system whose lifecycle controls match that requirement. No amount of tidy correlation IDs repairs a missing compliance operation.
The final decision rule is compact: select app logging for the explainable event trail, error tracking for exception groups, metrics for trends and alerts, and a heartbeat tool for silence. Choose vendors only after the synthetic incident can be reconstructed end to end and the missing operational functions have named owners.
Test the silence.
References
- Logback appenders
- Sentry issues
- Prometheus overview
- Healthchecks documentation
- Datadog getting started
Top comments (0)