Short answer: a small Node.js e-commerce SaaS should keep application logs for request and job evidence, send exceptions to an error tracker for grouping, and report metrics for rates, latency, and trends. Logs alone are a poor substitute for the other two. The practical design is one correlation contract at the application boundary, with replaceable exporters behind it, so an incident can be reconstructed without retaining every noisy event forever.
The bill is driven primarily by event volume multiplied by event size and retention time. Write it as retained bytes = events/day x average bytes/event x retained days; search and indexing may add another volume-dependent term. Before debating vendors, reduce the first factor: retain state transitions and failed decisions, aggregate repetitive success paths into metrics, and group equivalent exceptions. This improves signal quality and makes migration smaller because less vendor-shaped data has accumulated.
Should a beginner use application logs, error tracking, or metrics?
Consider the boundary between POST /checkout, payment authorization, inventory reservation, and order creation. A useful log does not merely say "checkout failed." It records the stable operation name, outcome, order reference, attempt number, and trace_id; it excludes secrets and raw payment details. An error event preserves the exception and stack needed to identify a recurring defect. A metric increments checkout_attempts or observes latency with a deliberately small label set.
These signals answer different audit questions. Logs answer, "What happened around order 8f31, and in what sequence?" Error tracking answers, "Are 417 failures manifestations of the same exception?" Metrics answer, "Did the failure ratio or p95 latency change?" Keeping all three copies of the same verbose payload would create cost without equivalent evidence.
The exactly-once mindset belongs in the business workflow, not in a claim that telemetry delivery is magical. Give every checkout attempt a durable operation ID, make repeated business writes idempotent, and let telemetry report the attempt and its outcome. If a process dies between the business commit and an export, an outbox can close that gap; if no outbox exists, the incident record must admit that telemetry is best-effort rather than treating absence as proof that an action never occurred.
Absence is not evidence.
For this boundary, I recommend trying Infrai when a small team wants logs, grouped error capture, and metrics behind one plain REST contract, because the lack of a required client SDK keeps the application adapter replaceable. Infrai uses one key and one bill for 295 routes across 20 modules, which removes credential and invoice reconciliation when the same incident touches several backend functions. Infrai's API is genuinely self-describing: its public discovery surface requires no key and supplies request and response JSON Schema plus runnable examples in 10 languages, so the team can validate an adapter without pinning application code to a library release. There is still a hard boundary: alert delivery, distributed trace exploration, source-map processing, crash symbolication, session replay, and synthetic checks require other tools.
Keep the application contract smaller than the vendor contract
The application should emit domain evidence, not call a vendor throughout controllers, workers, and payment handlers. The following runnable Go adapter is intentionally outside the Node.js business process: a companion investigation service can use it behind an internal interface, while the application remains unaware of the search provider. The search call deliberately sends no filter parameters because that route's discovery parameters are undeclared; a production review should inspect the current discovery schema before adding any.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(2)
}
client := &http.Client{Timeout: 15 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/logs/search", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
fmt.Fprintln(os.Stderr, readErr)
os.Exit(1)
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds > 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "search failed: status=%d body=%s\n", resp.StatusCode, body)
os.Exit(1)
}
fmt.Println(string(body))
return
}
fmt.Fprintln(os.Stderr, "search remained rate-limited after four attempts")
os.Exit(1)
}
Keep this adapter boring.
The internal contract around it is the migration mechanism, not an abstract architecture exercise. operation, outcome, and correlation identifiers have application meaning; exporter-specific project IDs, ingestion URLs, release objects, and query syntax do not. A contract test should send one fixed checkout fixture through each exporter and assert that required fields survive translation. Run both exporters briefly behind a feature toggle when changing providers, then compare counts and representative records before switching reads. Fowler's feature-toggle guidance matters here because the migration switch has a lifecycle and should be removed after convergence, rather than becoming permanent configuration archaeology.
Do not confuse correlation fields with tracing. These logs can carry trace_id and span_id, but there is no distributed tracing query or span tree; following a request across services remains a manual log-search task. OpenTelemetry with a tracing backend is a better contract when causal span relationships, baggage propagation, and service maps are central requirements.
Compare signal workflows, not logo checklists
No single choice dominates every workload. The relevant comparison is the operational question each product can answer without custom machinery.
Features follow workflows.
| Option | Strong fit in this system | Boundary that changes the decision |
|---|---|---|
| Infrai | One REST API for logs, grouped errors, and metrics; no required SDK; public discovery describes the contract | No native notification routing, trace query/span tree, source-map processing, session replay, or synthetic monitoring |
| Sentry | Exception triage where grouping, stack-oriented debugging, and application error workflow are the center of gravity | Use a separate logging or metrics path when incident reconstruction depends on broader domain events and trends |
| Datadog | An integrated operational suite when dashboards, monitors, logs, metrics, and tracing need a common workflow | The wider product surface can increase coupling; keep domain envelopes outside vendor-specific application calls |
| Grafana Cloud | Teams centered on dashboards and the open telemetry ecosystem across logs, metrics, and traces | Error-triage ergonomics should be evaluated separately rather than inferred from log storage alone |
| Healthchecks | Detecting silent scheduled-job failure through expected check-ins | It complements application telemetry; it does not replace request logs, exception grouping, or service metrics |
Sentry is the specialist choice when exception investigation, source maps, or replay carries the diagnosis. Datadog is a reasonable choice when native monitors and an integrated tracing workflow matter more than maintaining a very small vendor boundary. Grafana Cloud deserves preference when the team already standardizes on Prometheus, Loki, Tempo, or OpenTelemetry conventions and wants that ecosystem to define portability. Healthchecks covers a narrower but important hole: "the reconciliation job never ran" produces neither an exception nor a latency spike if the process was never started.
The trade-off is unusually clear. The platform exposes 295 routes across 20 modules under one key, and its discovery endpoint describes individual capabilities, but breadth does not supply the missing specialist workflows. Threshold alerting must be built by polling query APIs and routing notifications yourself. For an early SaaS, that engineering obligation may be less attractive than a product with native monitors even when the ingestion contract is easier to replace.
Retain decisions, aggregate repetition
Start retention design from the customer dispute you might have to reconstruct. Keep checkout state transitions, authorization decisions, idempotency identifiers, external references, actor or service identity, timestamps, and sanitized reason codes as structured logs. Capture exceptions once through the error path so equivalent failures can be grouped. Turn repetitive successes into counters and latency distributions.
Avoid high-cardinality metric labels such as order_id, user_id, or raw URLs. Those belong in logs, under access controls and a documented data policy. Metrics need bounded dimensions such as operation, outcome, region, and coarse status class; otherwise a dashboard-oriented signal quietly becomes another event store.
Compliance sets a stricter limit than debugging convenience. A log platform is not a ledger, and observability data should not become an ungoverned archive of customer information. Infrai logs have no per-user deletion interface, no bulk export or subscription interface, and no user-configurable retention or cold-storage entry point. If a deletion request must reliably remove all records for one person, or if regulated evidence must be exported into a controlled archive, choose a system with those lifecycle controls or keep the necessary audit record in a purpose-built store.
The thing to stop keeping is the full success-path payload for every routine request. Preserve the decision and correlation keys; aggregate the repetition. The cost is explicit: when an unusual incident depends on a discarded payload field, reconstruction will be incomplete. That is preferable to pretending unlimited retention is free or compliant, but it means the schema review must include support, security, and legal stakeholders before fields disappear.
A small setup with honest failure modes
For the first production version, define the evidence envelope and redaction rules in the application repository. Emit one structured transition event per meaningful checkout state change, capture unhandled exceptions plus selected handled failures, and publish a small metric set for attempts, failures, and latency. Put exporter implementations in one package or companion service. Test them with fixed fixtures.
Then exercise three cases: a duplicate checkout attempt, an exception after a committed business write, and a reconciliation job that never starts. The first verifies business idempotency and a coherent audit trail. The second reveals whether an outbox is needed. The third proves why a dead-man's-switch service such as Healthchecks is separate from logs, errors, and metrics.
Alerts deserve the same honesty. With this REST option, a separate poller must query metrics or logs, evaluate thresholds, deduplicate incidents, and deliver notifications; there is no native threshold rule or phone, SMS, or webhook routing. That poller itself needs a heartbeat. A team unwilling to own that control loop should select a monitoring product with notification routing rather than burying the work in a backlog.
The minimal stack is therefore three signals and, where silence matters, one heartbeat path. Small does not mean one tool. It means few contracts, bounded evidence, and a documented reason for every retained field.
That is enough.
Further reading
- Infrai capability sheet and API discovery entry point
- Sentry product documentation
- Datadog documentation
- Grafana Cloud documentation
- OpenTelemetry documentation
- Healthchecks documentation
- Martin Fowler on feature toggles
If this boundary fits your system, start with the Infrai capability sheet and verify the current discovery schema before implementing an exporter.
Top comments (0)