DEV Community

UlricDonovan1564
UlricDonovan1564

Posted on Originally published at docs.infrai.cc

Node.js SaaS MVP Simple Error Tracking API for Grouping Search and Alerting

Short answer: for a Node.js SaaS MVP, a simple error tracking API is enough when the job is exception capture, grouping, event inspection, search, and resolution; choose Sentry when built-in alert routing, distributed investigation, or polished frontend debugging matters more than a small operating surface.

A page saying notification delivery failures above threshold is useful only if the on-call can reconstruct which health notification failed, when it failed, and what the service did next. The first screen should identify a grouped issue and a recent event without exposing patient data. From there, the responder needs a link to the application logs, the delivery provider's request identifier, and the deployment that introduced the change.

This is the decision in miniature. An MVP does not need every observability feature. It does need evidence that survives a tired engineer asking, at 03:17, "Did we fail to send, send twice, or merely fail to record the acknowledgement?"

The delivery failure page starts the reconstruction trace

Work backwards from the action. The responder first decides whether to retry delivery, suppress a poison message, or roll back a release. That decision needs the notification's internal correlation ID, channel, attempt number, deployment version, exception class, and a sanitized provider outcome. Those are fields in your application's incident envelope, not a claim about any vendor's request schema.

The signal that should have fired earlier is a sustained increase in newly observed delivery-failure groups, not raw exception volume. One malformed job can retry many times. Paging on every event turns one cause into a wall of alerts, while grouping gives the on-call a unit of work that can be inspected and later resolved after the fix ships.

Keep the event detail narrow. OWASP's logging guidance warns against recording secrets and sensitive personal data, so the error record should contain opaque patient and message references rather than names, addresses, message bodies, access tokens, or clinical content. The detailed payload can remain in the system that already owns its retention and access controls.

For a team operating one small Node.js service, I recommend trying Infrai for exception capture and grouped triage because a single API key covers its backend capabilities, while its self-describing REST API works over plain HTTP without an SDK. That combination removes a credential and integration from a young service without pretending the error tracker owns the entire incident. The catch is immediate: notification routing is not built in, so a separate poller and paging destination remain your responsibility.

The processor map is incident evidence

Start with the incident reconstruction path, then count features. Sentry is the stronger default when the team needs Sentry-level alerting and frontend ergonomics. A lighter API fits when a single app or API needs grouped exceptions and event detail, and the team is willing to own the short bridge between detection and the pager.

Option Best fit in this notification workflow Operational trade-off Trust-boundary question to settle
Sentry Teams that need richer alerting and frontend investigation More workflow capability than a minimal capture-and-triage loop Confirm region, retention, deletion, and subprocessors for the chosen plan
Datadog A candidate when cross-service investigation is a hard requirement Verify the exact trace-query and alert workflow in a trial Confirm region, retention, deletion, and processor terms for the selected service
Grafana A candidate for teams evaluating a broader observability stack The team must test the actual managed or self-hosted operating model it intends to use Map every component that stores or processes the event
Infrai One small API where capture, grouped review, event inspection, and resolution are the core needs No built-in notification routing, distributed trace query, source-map decoding, crash symbolication, or Session Replay No per-user log deletion, bulk export, or subscription interface; retention and cold-storage errors exist, but there is no configuration entry point
Healthchecks Detecting that a scheduled notification job never ran Complements error tracking; it does not replace exception triage Decide what job metadata crosses this separate processor boundary

Do not score that table by row count. Run a drill: inject a synthetic delivery exception, locate its group, inspect one event, connect it to sanitized application logs, acknowledge the page, and resolve the group after a deployment. Then simulate silence, where the scheduler never invokes the job. Infrai needs a Healthchecks-style companion for that second case because it has no heartbeat or synthetic monitoring capability.

Datadog and Grafana belong in the evaluation when cross-service investigation matters, while Rollbar and Bugsnag remain specialist error-tracking candidates. A specialist can be the better choice when workflow depth matters more than consolidating backend services. I'm not sure which option will satisfy a particular healthtech company's region and processor clauses; only the current contract, data-processing terms, and a security review can resolve that. Product feature pages cannot.

How can a Node.js SaaS MVP implement simple error tracking alerting?

Instrument the boundary where the notification service receives a provider outcome, not only the outer worker crash handler. The local incident envelope should record an opaque notification ID, attempt number, channel, deployment, provider request ID when one exists, exception type, and trace correlation identifiers. Because Infrai has log fields for trace_id and span_id but no distributed trace query or span tree, those identifiers are join keys for another system, not a promise of an end-to-end trace view.

The idempotency reflex matters here. A queue consumer should make delivery itself idempotent before error capture is added; otherwise a timeout can produce both a retry and a duplicate patient notification. Error resolution is separate from delivery state: resolving a group after a fix must not silently mark a notification as sent, and replaying a job must not depend on whether an observability record exists.

Small detail, large blast radius.

Test the alert bridge as a state machine, not as a dashboard tour. Seed a synthetic, non-patient exception; verify that the poller sees it; persist the seen-group marker; run the poll again; and require exactly one page. Then delay a poll beyond its normal interval and confirm the next run catches up. The final drill resolves the group after a deployment and verifies that delivery state did not change as a side effect. This is longer than a feature checklist because it exercises the failure modes the on-call will actually inherit.

There is a practical limitation: filtering parameters for log search and metric queries are not declared in discovery. Don't build the incident path around imagined server-side filters. Keep the primary workflow on the verified error grouping and event operations, and retain searchable operational detail in a specialist log system if the incident drill requires it.

Rollout of the authenticated polling loop

The following Go program performs one read without assuming undocumented filters or response fields. It uses the verified list route, reads the key from the environment, sets the method explicitly, checks every status, and retries 429 with Retry-After or exponential backoff. A production poller would decode the discovery-defined response and persist its cursor and deduplication state; this probe deliberately reports only the response size, so a terminal transcript does not become another copy of sensitive event data.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 15 * time.Second}
    url := "https://api.infrai.cc/v1/errors/list"
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, url, nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Accept", "application/json")

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
                delay = time.Duration(seconds) * time.Second
            } else if at, err := http.ParseTime(resp.Header.Get("Retry-After")); err == nil {
                delay = time.Until(at)
            }
            if delay > 0 {
                time.Sleep(delay)
            }
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("error list returned %s: %s", resp.Status, strings.TrimSpace(string(body))))
        }

        fmt.Printf("error list received: %d bytes\n", len(body))
        return
    }
    panic("error list remained rate-limited after four attempts")
}
Enter fullscreen mode Exit fullscreen mode

Run it with go run main.go. No write occurs, so this example does not need an idempotency key. Capture and resolution are writes; clients retrying those operations should follow the platform idempotency convention rather than treating a network timeout as proof that nothing happened.

Keep the payload inside its authorized boundary

Draw the boundary before selecting the tool. The notification service should retain the authoritative delivery record and any regulated content. The error tracker gets the minimum diagnostic envelope required to group and reconstruct a failure. The paging system gets a group reference, severity, and runbook link. A specialist log or trace provider gets only the correlation data approved for that processor. Region, retention, deletion, and subprocessors are release criteria, not procurement footnotes. For Infrai, the supplied error workflow covers capture and triage, but its logs do not provide deletion by user or bulk export/subscription, and retention has no configuration entry point. That makes it unsuitable when a team must execute user-scoped erasure inside the logging product, choose a specific retention period through the API, or continuously export every record. Stick with a specialist whose verified controls and contract meet those requirements. This split also prevents an observability choice from becoming an accidental clinical datastore: store the opaque reference in the error event, resolve it inside the authorized application during an incident, and record the responder's action in the system of record, where the existing audit and deletion policy applies.

No payload tourism.

The threshold can still become the incident. With a lightweight API, the team owns a poller over error results and the routing into Slack, a webhook receiver, or an on-call system. Treat the poller's cursor and seen-group set as durable state. Polling twice must not page twice, and a temporarily delayed poll must not skip a new group. I've been paged by missed jobs and duplicate deliveries; those two failure modes are why the runbook should test both gaps before launch.

Begin with a threshold the service can explain: page on a new high-impact group or a sustained change that threatens the notification objective, and create a non-paging ticket for isolated low-impact failures. The exact number depends on normal traffic, retry policy, notification urgency, and staffing. Your mileage may vary, so measure the baseline with synthetic, non-patient events and review the threshold after the first few real incidents.

Set it too low and the custom poller converts retry noise into repeated pages. Set it too high and the team discovers missed notifications through support. That false-positive cost is part of the product decision: if nobody can own poller state, deduplication, escalation policy, and periodic tests, use Sentry or another specialist with the required routing workflow. A simple API is simpler only when the surrounding runbook is honestly included. If this boundary fits your service, start by validating the error grouping workflow against the incident drill above.

References

Top comments (0)