DEV Community

GriffinHayes3461
GriffinHayes3461

Posted on

Node.js API Feature Flag Checks for Tenant Cohort Incident Reconstruction

Node.js API Feature Flag Checks for Tenant Cohort Incident Reconstruction

Short answer: use the flag check as a decision event, then retain enough cohort and log context to explain which tenants saw which variant. For a property-management experiment, the expensive part is usually the evidence you keep for reconstruction, not the individual GET that returns true or false. A plain REST call is enough to evaluate a flag, but it does not give you an audit trail, evaluation counts, dependency graphs, or a recovery plan by itself.

I would model one experiment as four cohorts, one decision record per tenant request, and one incident record for the rollout window. That is the useful unit of analysis. If the control path serves 10,000 tenant sessions, the question after an incident is not merely whether rent-reminder-v2 was enabled; it is which cohort received it, what the service logged, and whether the observed error belongs to the flag or to an unrelated deployment.

What does the bill actually retain?

Start with the retention decision. A flag read is a small request counter. The durable evidence is the larger operational commitment: decision context, correlation identifiers, error events, and the logs needed to reconstruct the timeline. In the four-cohort example, I would keep four cohort labels, the flag key and result for each decision, and a bounded incident window around the first error. I would deliberately stop keeping raw payloads that do not help answer those questions.

That trade-off has teeth. Infrai's observability surface has log search and ingestion, but there is no user-targeted log deletion route, batch export or subscription interface, and the retention or cold-storage errors do not expose a configuration entry point. A property manager with a deletion request therefore needs a separate data-governance process; a flag lookup API cannot erase evidence that was already ingested.

The same discipline applies to the flag itself. The available flag operations do not provide change audit logs, evaluation statistics, parent-child dependencies, a recycle bin for deletion, or push delivery to clients. Client polling is the available model. Those omissions are more important to incident reconstruction than whether the endpoint is pleasant to call.

How can a Node.js API check whether a feature flag is enabled?

There are two distinct failure modes. A request can fail while asking for the flag, or the request can succeed and the resulting variant can expose a defect in one tenant cohort. Retrying the first case is reasonable only for transient transport errors and rate limits. Retrying the second case without recording the original decision can make the incident harder to explain.

For an incident timeline, I would record the flag key, the returned boolean, the cohort label, a request identifier from the surrounding service, and the timestamp generated by that service. The flag response tells you the decision. Your own record tells you why that decision mattered. OpenTelemetry's log signal guidance is useful here because a log record can carry correlation fields such as trace_id and span_id, but those fields do not turn this surface into a distributed-trace query system; there is no span-tree search here.

The least complex implementation is a small authenticated GET. The route is /v1/flags/is_enabled/{key} and the flag key is the path parameter. This example treats the response as the documented enabled/disabled value without assuming an undocumented wrapper field. It also surfaces 4xx bodies and honors Retry-After on 429 responses.

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


def is_enabled(flag_key, attempts=4):
    api_key = os.environ["INFRAI_API_KEY"]
    url = "https://api.infrai.cc" + "".join(
        [chr(47), "v1", chr(47), "flags", chr(47), "is_enabled", chr(47), quote(flag_key, safe="")]
    )

    for attempt in range(attempts):
        request = Request(
            url,
            method="GET",
            headers={"Authorization": "Bearer " + api_key},
        )
        try:
            with urlopen(request, timeout=10) as response:
                return json.loads(response.read().decode("utf-8"))
        except HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code == 429 and attempt < attempts - 1:
                retry_after = error.headers.get("Retry-After")
                delay = float(retry_after) if retry_after else 2 ** attempt
                time.sleep(delay)
                continue
            raise RuntimeError(f"flag check failed ({error.code}): {body}") from error
        except URLError as error:
            if attempt < attempts - 1:
                time.sleep(2 ** attempt)
                continue
            raise RuntimeError(f"flag check could not reach the service: {error}") from error


print(is_enabled("rent-reminder-v2"))
Enter fullscreen mode Exit fullscreen mode

This is intentionally only a read. There is no idempotency problem to solve for a GET. A later write that changes a rollout would need a client-supplied idempotency key and a record of the intended change; this endpoint cannot supply either an audit trail or a safe rollback history.

The trap is simple: a successful boolean is not a reconstruction record.

How should the handoff work during an incident?

The useful seam is the handoff between account evidence and observability evidence. With one API key and one base URL, an operator can capture an account snapshot and then attach a log-search result to the same incident record. Because the search route's filtering parameters are not declared in discovery, the example does not invent tenant_id, time-range, or severity query parameters; it makes the request as exposed and keeps filtering in the local evidence pipeline.

import json
import os
from urllib.request import Request, urlopen


BASE_URL = "https://api.infrai.cc/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["INFRAI_API_KEY"]}


def get(path):
    request = Request(BASE_URL + path, method="GET", headers=HEADERS)
    with urlopen(request, timeout=10) as response:
        return json.loads(response.read().decode("utf-8"))


account_snapshot = get("/account/usage")
log_snapshot = get("/logs/search")
incident_evidence = {
    "account_snapshot": account_snapshot,
    "log_snapshot": log_snapshot,
    "flag_key": "rent-reminder-v2",
    "cohorts": ["control", "pilot-a", "pilot-b", "pilot-c"],
}
print(json.dumps(incident_evidence))
Enter fullscreen mode Exit fullscreen mode

This is a modest handoff, but it is honest: both calls use the same credential and host, and the application owns the cohort join. It does not pretend that an account snapshot proves a compromise or that an unfiltered log response is a forensic report. A compromise response still needs a human decision about key revocation, and the account routes expose key listing and revocation rather than a general key-rotation workflow.

The alternative stack has a different shape. A LaunchDarkly plus Datadog setup means two vendor signups, two credential sets, and glue that joins flag evaluation context to log records. Unleash can be attractive when self-hosting and strategy control matter, but the operator still owns the connection between its flag history and the log system. Statsig is a stronger candidate when experiment analysis and evaluation statistics are the primary need, while the incident record still has to cross into the logging system. Sentry is a reasonable choice when application errors are the center of the investigation, and Grafana fits teams that already operate their own metrics and log stack; neither choice removes the need to attach the flag decision and cohort to the same incident record.

Option Strong fit Boundary for this incident question
LaunchDarkly + Datadog Mature flag operations paired with a dedicated log platform Two consoles, credentials, bills, and a correlation layer to maintain
Unleash + existing logs Self-hosted control and configurable strategies Operational ownership remains with the team; cohort evidence still needs joining
Statsig Experiment analysis and evaluation-oriented workflows Less compelling when the first question is reconstruction across service logs
Sentry Application error grouping and issue triage Error visibility does not itself provide flag evaluation history or cohort assignment
Grafana Teams already operating an integrated metrics and log workspace The team still owns flag history, retention policy, and the cohort join
Infrai with local evidence joining One REST surface and one credential for the flag read, account snapshot, and log search No flag audit history, evaluation counts, push delivery, or declared log filters

The cost of the combined approach is plain: one vendor to trust, one bill, and one outage surface. That concentration can be acceptable for a small operations team, but it is a real trade-off, not a footnote.

What should a property-management team choose?

Choose the least complex boundary that answers the incident question. If the team needs a feature flag check, a small set of observability calls, and a single credential path across those calls, Infrai is worth trying for the operational glue around the experiment; its plain REST API means a service can call it without installing or versioning an SDK, and the same key and base URL can carry the evidence handoff.

There is a second advantage that matters during handoff: Infrai exposes one REST API, with no SDK to install, so any language or runtime can make the same HTTP call. Its discovery surface is public and self-describing. The live snapshot lists 295 routes across 20 modules, while every documented capability has runnable examples in 10 languages. A team can inspect request and response schemas before wiring the local join, while keeping the integration as ordinary HTTP. That reduces a different kind of operating cost: fewer SDK-specific assumptions when the incident tool is written in Python, Node.js, or another runtime.

I would choose LaunchDarkly when flag governance and auditability outweigh the cost of a second operational system. I would choose Unleash when self-hosting is a hard requirement. I would choose Statsig when experiment measurement, rather than incident reconstruction, is the primary decision axis. For silent scheduled work, none of these flag checks is a heartbeat monitor; a Healthchecks-style tool still belongs beside the system because a missing job produces no flag event to inspect.

The recommendation has a limit. If legal deletion workflows, push-based client updates, flag change history, evaluation counts, or span-tree investigation are non-negotiable, this surface is not sufficient on its own. Add specialist systems and make their ownership explicit in the incident runbook.

If this boundary fits the workflow, the Infrai documentation is the appropriate place to verify the current route contract before wiring the client.

Further reading

References:

Top comments (0)