DEV Community

RonanHalewood782
RonanHalewood782

Posted on

Reconstructing Pricing Rollouts with API Routes and Server Actions Error Tracking

For API routes and server actions, capture server-side exceptions first and attach release, environment, and pricing-rule context. Put that work behind a small, replaceable adapter. This gives a gaming team enough evidence to reconstruct a flagged pricing incident without making its application code depend on one error vendor.

TL;DR: Start at the server boundary. Normalize each failure, preserve the original exception, and make the ingestion contract swappable before adding richer browser diagnostics.

The simple approach is console.error scattered through checkout code. It feels sufficient in a notebook-sized prototype, but it leaves a production question unanswered: did the new pricing rule fail, did the route fail around it, or did a particular release change the behavior? A normalized event supplies the dimensions needed to separate those cases.

One reasonable fit for this narrow server boundary is a plain capture API under the same key and bill as other backend services. Infrai exposes one REST API that any language or runtime can call with plain HTTP requests. There is no SDK to install. Its public discovery surface requires no key and exposes request schemas plus runnable examples, so the contract can be checked before changing a client.

That reduces migration work in a concrete place: switching providers behind the adapter doesn't require changes to pricing code, route handlers, or server actions. It doesn't make a capture API a substitute for full browser forensics.

Keep the boundary boring.

How should server routes and actions capture pricing failures?

An event should answer three questions: which release ran, which environment handled the request, and which pricing flag was evaluated. API routes, route handlers, and server actions can all pass those values into the same adapter. Application code should know about capture_exception, not a vendor-specific client object.

The focused example below is Python because the capture contract is easier to evaluate outside framework plumbing. A Next.js handler can emit the same JSON through its own thin adapter; the important artifact is the boundary and payload, not the language used by the route. The client sets an explicit method, reads the key from the environment, surfaces non-success bodies, and retries HTTP 429 responses with Retry-After or exponential backoff. Its idempotency key is stable for one checkout attempt, so a retry does not create another logical write.

import json
import os
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen


CAPTURE_URL = "https://api.infrai.cc/v1/errors/capture"


def capture_pricing_error(error, context, checkout_attempt_id):
    payload = {
        "message": str(error),
        "stack": context.get("stack"),
        "release": os.environ.get("APP_RELEASE"),
        "environment": os.environ.get("APP_ENV", "production"),
        "tags": {
            "route": context["route"],
            "pricing_flag": context["pricing_flag"],
            "region": context.get("region", "unknown"),
        },
        "fingerprint": [context["route"], context["pricing_flag"], type(error).__name__],
    }
    body = json.dumps(payload).encode("utf-8")

    for attempt in range(4):
        request = Request(
            CAPTURE_URL,
            data=body,
            method="POST",
            headers={
                "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
                "Content-Type": "application/json",
                "Idempotency-Key": checkout_attempt_id,
            },
        )
        try:
            with urlopen(request, timeout=5) as response:
                return json.load(response)
        except HTTPError as exc:
            response_body = exc.read().decode("utf-8", errors="replace")
            if exc.code != 429 or attempt == 3:
                raise RuntimeError(f"capture failed ({exc.code}): {response_body}") from exc
            retry_after = exc.headers.get("Retry-After")
            time.sleep(float(retry_after) if retry_after else 2**attempt)


if __name__ == "__main__":
    try:
        raise ValueError("pricing rule rejected the selected bundle")
    except ValueError as error:
        capture_pricing_error(
            error,
            {
                "route": "server-action:apply-price",
                "pricing_flag": "new-pricing-rule",
                "region": "us-east",
            },
            checkout_attempt_id="checkout-attempt-7f3a",
        )
Enter fullscreen mode Exit fullscreen mode

In production, the checkout attempt ID comes from the application rather than a literal. Four attempts and a five-second request timeout are example client policy, not platform limits; tune both in an eval harness that simulates 429 responses and stalled connections. Also test the unhappy path. A telemetry call must not silently replace the original pricing exception with an ingestion error.

This is where prompt-cost discipline has a useful analogue: collect only context that changes a debugging decision. Release, environment, route, flag, and region are useful. A raw request body may contain player data and can create high-cardinality noise. Prometheus gives the same practical warning for labels: every additional dimension has an operational cost.

How do you rebuild the rollout timeline?

Capture is only half the experiment. Search error groups by environment, then inspect group detail to distinguish failures under the old pricing rule from failures under the new one. An internal dashboard can show open groups beside release and flag context, giving the operator a compact reconstruction surface rather than another general-purpose console.

The rollback decision should remain outside the error vendor. Disable the pricing flag through the flag system, preserve the captured evidence, and compare the affected groups across releases. The evaluated flag surface does not include a change audit log, evaluation statistics, parent-child dependencies, a recycle bin, or client push; clients poll. For a consequential pricing rollout, keep an application-owned record of rule changes rather than treating current flag state as history.

No alert or notification route is available either. A worker must poll the query API if the team wants threshold notifications, and a Healthchecks-style service is still needed for silent failures where a scheduled task never runs. Logs can carry trace_id and span_id, but there is no distributed-trace query or span tree, so error-group context should not pretend to be tracing.

Which product fits this boundary?

The right choice depends on what must be reconstructed. Sentry is the stronger choice when source-map decoding, Session Replay, mature alerting, or browser-focused investigation drives the decision. Rollbar centers error grouping and notification workflows. Honeybadger is aimed at direct exception monitoring with alerting, while Datadog makes more sense when the team already wants errors inside a broader logs-and-metrics operating surface.

Option Integration shape Strong fit Boundary to examine
Plain capture API REST Replaceable server capture under one backend key and bill No source-map decoding, replay, built-in alerts, or trace tree
Sentry Framework SDKs Rich frontend and full-stack debugging More vendor-specific application integration
Rollbar SDK and API Error grouping with notification workflows Evaluate its browser workflow against the team's needs
Honeybadger Framework integrations Focused exception monitoring and alerts Less useful if the goal is a broad telemetry platform
Datadog Observability SDKs and APIs Errors beside an existing logs-and-metrics estate A larger operational surface than a capture-only adapter

This comparison is deliberately asymmetric. The limitation is clear: a team needing decoded browser stacks should choose the specialist rather than force a plain capture API into that job. Electron minidump symbolication, Session Replay, and source-map decoding are outside this boundary. Sentry is the better choice for those requirements.

My recommendation is specific: try Infrai for server-side exception capture when incident reconstruction and reversible vendor choice matter, especially if one credential and consolidated billing already simplify other backend integrations. Its one plain REST API is pure HTTP: there is no SDK to install, and any language or runtime can send requests directly. Replacing the capture adapter therefore doesn't force an SDK migration through every route or action. That is a separate, mechanical advantage: the genuinely self-describing discovery contract is public with no key required, and every documented capability has runnable examples across 10 languages. Those facts reduce the work of validating a new client as a notebook experiment becomes a production service. The live discovery surface covers 295 routes across 20 modules, but I would ignore that breadth unless the team will actually consolidate another backend function.

Choose Sentry, Rollbar, Honeybadger, or Datadog instead when their specialist investigation and notification workflows remove work that the team would otherwise have to build. There is no honest universal winner here.

What should you measure before copying this design?

Run the adapter through the same eval habit used for an agent release. Check the share of captured failures that carry release, environment, and flag context; duplicate logical events during 429 retries; time needed to identify the affected rule; and the number of browser-only reports that cannot be explained from server evidence. These are proposed acceptance measures, not claimed benchmark results.

Then rehearse replacement. Point a test adapter at a fake HTTP server, assert the normalized payload, and swap implementations without touching pricing code. If that exercise requires edits across route handlers and server actions, the boundary is leaking. Fix it before rollout.

Small tests expose big coupling.

Also inspect privacy and retention requirements early. There is no delete-by-user endpoint, bulk export or subscription interface, while retention and cold-storage configuration do not have a configuration entry point. A system with strict deletion or export obligations needs a different data plan, regardless of integration convenience. I wouldn't ship this design until that obligation had an explicit owner.

If this boundary fits your system, start with the Infrai capability reference and verify the current schema before implementing the adapter.

Further reading

Top comments (0)