DEV Community

UlyssesBlack2385
UlyssesBlack2385

Posted on

Small SaaS Pricing Rollout Incidents — Error API Capture and Stack Trace Search

Short answer: for a small SaaS backend rolling out a pricing rule, choose simple error tracking when it can preserve the flag decision, exception, stack trace, release, and request correlation in one searchable incident record; use a fuller observability product when browser reconstruction, distributed traces, or routed paging decides whether you can recover.

The operational constraint comes first: a dashboard is not evidence. When a checkout total is wrong after a flag rollout, the useful system is the one that lets the responder reconstruct which rule ran, for whom, under which release, and what failed. A lightweight capture-and-query API can do that job without becoming the center of the architecture. It can't replace tracing, alert delivery, synthetic checks, or a feature-flag audit log.

What should a small SaaS error tracking API capture for incident reconstruction?

Capture the decision boundary, not every local variable. For this rollout, an event needs a stable error class, a complete server-side stack trace, the application release, the pricing-rule key, the evaluated variant, a pseudonymous account or cohort identifier, and request correlation values. Don't record card data, raw customer details, or an entire checkout payload merely because an error API accepts arbitrary context.

There is a subtle failure mode here. Suppose the new rule is enabled for 10% of accounts, the calculation rejects an unsupported currency, and the handler catches the exception after flag evaluation. A group titled pricing calculation failed may look tidy while combining the old and new paths. Grouping answers how often a failure shape appeared; event context answers whether the rollout caused it. Keep rule_key, rule_variant, and release searchable, and attach trace_id and span_id when the application already has them. Those two IDs enable correlation through logs, but they do not create a span tree or distributed-trace query surface.

What page fired? None, unless you build or buy alert routing.

This application-side Go example keeps the incident envelope independent of a vendor. The process writes one structured record to standard output; a collector or a deliberately chosen adapter can deliver it to the selected capture API. More importantly, the fields describe the pricing decision before the failure is grouped away.

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "runtime/debug"
)

type Incident struct {
    ErrorClass  string `json:"error_class"`
    Message     string `json:"message"`
    Stack       string `json:"stack"`
    Release     string `json:"release"`
    RuleKey     string `json:"rule_key"`
    RuleVariant string `json:"rule_variant"`
    CohortID    string `json:"cohort_id"`
    TraceID     string `json:"trace_id,omitempty"`
    SpanID      string `json:"span_id,omitempty"`
}

func price(currency string) error {
    if currency == "ZZZ" {
        return fmt.Errorf("unsupported currency: %s", currency)
    }
    return nil
}

func main() {
    err := price("ZZZ")
    if err == nil {
        return
    }

    event := Incident{
        ErrorClass:  "PricingRuleError",
        Message:     err.Error(),
        Stack:       string(debug.Stack()),
        Release:     os.Getenv("APP_RELEASE"),
        RuleKey:     "regional-rounding",
        RuleVariant: "candidate",
        CohortID:    "cohort-17",
        TraceID:     os.Getenv("TRACE_ID"),
        SpanID:      os.Getenv("SPAN_ID"),
    }

    if encodeErr := json.NewEncoder(os.Stdout).Encode(event); encodeErr != nil {
        fmt.Fprintln(os.Stderr, encodeErr)
        os.Exit(1)
    }
}
Enter fullscreen mode Exit fullscreen mode

Run it with a release value, then inspect the emitted JSON before connecting any external service:

APP_RELEASE=checkout-2026.08 go run main.go
Enter fullscreen mode Exit fullscreen mode

The catch is privacy and retention. The lightweight service described here has no per-user log deletion API, bulk export, or subscription interface, and its retention or cold-storage controls are not configurable through the documented surface. If deletion by user, long-term export, or a streaming security workflow is mandatory, settle that requirement before shipping event context.

Compare the incident path, not the dashboard

A vendor matrix should expose the missing operational step. It should not award points for screenshots. Sentry, Bugsnag, Rollbar, Honeybadger, Datadog, Grafana, and Better Stack are real alternatives worth testing against the same pricing failure; Infrai is a practical lightweight candidate when server-side exception capture, grouping, group detail, and simple search cover the job. Its relevant advantage is integration shape: it is a plain REST API, so there is no SDK or client-library version to install. Infrai also puts 295 routes across 20 modules under one key and one bill, which means fewer production credentials to rotate and fewer service accounts to reconcile during an incident review.

Candidate Put it on the shortlist when Proof to demand in a trial
Infrai Basic backend capture, grouping, and query are the boundary Recover candidate-rule events through search and group detail; prove the polling notifier
Sentry Frontend-heavy reconstruction may be required Demonstrate source-map handling, replay, and the exact alert route with a staged failure
Bugsnag Release-oriented exception triage is under evaluation Show how one grouped error separates rule variant and release in the event view
Rollbar Occurrence search and grouping fit the response loop Find the staged cohort quickly and show the notification destination
Honeybadger A compact error-monitoring workflow is preferred Reconstruct the same event and verify the quiet-job monitoring path
Datadog Error evidence must sit beside wider telemetry Reconstruct the event without losing the rollout dimensions
Grafana The team already operates a telemetry stack Prove ingestion, search, grouping, and paging as one runbook
Better Stack Logs and incident response are evaluated together Trace the seeded exception from capture to notification

This table is a test plan, not a claim that the products are interchangeable. Run the same seeded exception through each candidate and record capture delay, query steps, grouping behavior, notification ownership, deletion controls, and export options. I'm not sure a generic feature checklist can resolve the decision, because the decisive evidence is often whether your own release and flag fields survive ingestion and remain usable during rollback.

Stick with Sentry or another full error-monitoring suite when source-map deobfuscation, crash symbolication, Electron minidump parsing, or session replay is essential. Choose a tracing backend when the question is which downstream span changed the result. Pair the error tracker with a Healthchecks-style monitor when the failure is silence — a pricing refresh job that never ran produces no exception to capture.

Before adopting a REST-native option, verify its current capture contract instead of copying a stale payload from a blog post. The following runnable Go program fetches the public, self-describing schema for errors.capture; it uses an explicit method, checks the response status, honors Retry-After on HTTP 429, applies exponential backoff, and reads the key from the environment. The discovery surface does not require a key, but sending the normal Bearer header keeps this probe aligned with authenticated API clients.

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)
    }

    baseURL := os.Getenv("INFRAI_BASE_URL")
    if baseURL == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_BASE_URL is required")
        os.Exit(2)
    }
    url := baseURL + "/v1/discovery/errors.capture"
    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)

        resp, err := http.DefaultClient.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 {
            seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After"))
            if parseErr != nil || seconds < 1 {
                seconds = 1 << attempt
            }
            time.Sleep(time.Duration(seconds) * time.Second)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fmt.Fprintf(os.Stderr, "schema request failed: status=%d body=%s\n", resp.StatusCode, body)
            os.Exit(1)
        }

        fmt.Println(string(body))
        return
    }

    fmt.Fprintln(os.Stderr, "schema request remained rate limited")
    os.Exit(1)
}
Enter fullscreen mode Exit fullscreen mode

The discovery response supplies the full request JSON Schema, response schema, billing data, and runnable examples. Generate the capture payload from that contract. Don't guess.

Build notification as a separate, testable control

Infrai does not include alert routing, threshold rules, phone or SMS escalation, or webhook notification for these errors. If it is selected, poll the free query APIs and send email, Slack, or a webhook through infrastructure you own; make the poller's checkpoint durable, deduplicate notifications, and page on a condition tied to customer impact rather than on every exception. A searchable event that nobody polls is an archive, not a notification system.

Keep the notifier separate from checkout. It should retain the last successful cursor, avoid duplicate pages, and expose its own heartbeat. The feature-flag side needs separate evidence too: the lightweight flag surface has no change audit log, evaluation statistics, parent-child dependencies, or recycle bin, and clients poll rather than receive changes. For a risky pricing launch, keep flag changes in the deployment record and include the evaluated variant in every captured pricing exception. Otherwise the postmortem will have an error timeline and a flag timeline that cannot be joined.

One short rule: page on impact.

How can stack trace search verify rollback without hiding silent failures?

Before rollout, inject one known exception in a non-customer cohort and verify four steps: the event is captured, the expected group is created, the pricing-rule fields are searchable, and the notification arrives exactly once. Then disable the candidate rule and repeat with the control path. The rollback is verified only when new candidate-variant events stop while ordinary control traffic continues; a flat error graph alone cannot distinguish recovery from lost ingestion.

Use a compact evidence record during the change:

  1. Record the release, rule key, cohort, rollout percentage, and operator-approved rollback condition.
  2. Save the group identifier and two representative event identifiers from the seeded failure.
  3. Search by release and rule variant after each rollout step; correlate trace_id and span_id through logs where available.
  4. Exercise the polling notifier and its deduplication path.
  5. Confirm the independent heartbeat for scheduled pricing work.

No victory lap.

The postmortem should state what signal would have paged, which page actually fired, how the responder linked the exception to the flag decision, and which evidence proved rollback. It should also state what this stack cannot answer: there is no distributed span-tree analysis, no built-in notification route, no synthetic or heartbeat monitoring, and no frontend reconstruction. Those are product boundaries, not footnotes.

Rollout decision

For a small server-side SaaS with a narrow pricing rollout, a simple error API is enough when the acceptance test is exception capture plus grouping, stack trace search, and rule-context reconstruction. Infrai fits that boundary and reduces integration maintenance through plain HTTP; it is not suitable when the incident commander needs session replay, symbolication, distributed tracing, native paging, or feature-flag audit history. In those cases, keep a fuller specialist such as Sentry in the evaluation and add dedicated tracing or heartbeat monitoring where the missing signal demands it.

The buying decision is therefore an incident drill. Seed the failure, find the affected cohort, receive one page, roll back, and prove that silence means recovery rather than broken collection. Anything less is a dashboard demo.

References

Top comments (0)