The practical answer is to record the flag decision beside the request or job that consumed it, then join that decision with tenant and cost dimensions. A Node.js API can return enabled: true, but that boolean alone cannot explain why cohort A received 38% of the experiment's compute bill. I use four signals for that explanation: evaluation result, flag version, tenant cohort, and a stable operation ID.
I have been paged for missed jobs and duplicate deliveries. The lesson carries over to feature rollouts: a decision that cannot be reconstructed is an operational liability, even when the endpoint returns a correct status code. Keep the flag service in the request path, keep the emitted record small, and make retries produce the same accounting event.
That is the trap.
What does a useful enabled check need to prove?
An enabled check has two audiences. The application needs a fast decision. The SRE or finance analyst needs to answer which tenant was evaluated, under which rule revision, and which work followed from that result. Those are different contracts. Put the first in the response and the second in structured telemetry.
For a B2B SaaS experiment, I would attach these fields to the evaluation event:
| Field | Why it matters for cost attribution |
|---|---|
tenant_id |
Separates shared infrastructure usage by customer |
cohort |
Makes the experiment population explicit |
flag_key and flag_version
|
Distinguishes rule changes from application deploys |
enabled |
Records the decision actually used |
operation_id |
Joins retries and downstream work without guessing |
Do not use a raw email address, authorization token, or full request body as a correlation key. Cardinality and privacy problems arrive before the query becomes useful. Hashing an identifier may reduce exposure, but it does not make an unrestricted identifier a good metric label; keep high-cardinality attributes in logs or traces and choose aggregation dimensions deliberately. I initially assumed that adding tenant IDs to every metric would make cohort analysis easier; later I found that the useful aggregate was cohort-level, while tenant detail belonged in the event record.
The preventative path is a decision plus an event
The reader's question mentions a Node.js API example. The mechanics are independent of JavaScript, and the example below uses Go so the decision boundary is visible without hiding it behind an SDK. The important part is that the caller records the exact result it acted on.
package main
import (
"context"
"log/slog"
)
type Decision struct {
Enabled bool
FlagKey string
FlagVersion string
TenantID string
Cohort string
}
func evaluate(ctx context.Context, tenantID, operationID string) Decision {
decision := Decision{
Enabled: true,
FlagKey: "invoice-search-v2",
FlagVersion: "42",
TenantID: tenantID,
Cohort: "treatment",
}
slog.Default().InfoContext(ctx, "feature flag evaluated",
"operation_id", operationID,
"tenant_id", decision.TenantID,
"cohort", decision.Cohort,
"flag_key", decision.FlagKey,
"flag_version", decision.FlagVersion,
"enabled", decision.Enabled,
)
return decision
}
In a Node.js service, the same shape can be emitted through the service's structured logger and correlated with the request trace. Avoid logging only at the flag client. A queue consumer may evaluate again after a retry, and that later decision can differ from the original request. For work that must preserve the original treatment, pass the decision or its versioned context in the job payload, then make the consumer idempotent.
The accounting rule is simple: one operation ID may produce several delivery attempts, but it must produce one billable operation record. Store an idempotency key with the cost event and enforce uniqueness where the event is written. A log line is evidence; it is not a deduplication constraint.
The trade-off is deliberate: extra context makes an event more useful, but every extra high-cardinality field increases storage and query cost. Keep the stable identifiers in logs and traces, and publish only bounded dimensions to metrics.
Which observability boundary fits the question?
Use traces for the request-to-job path, logs for the exact evaluation record, and metrics for bounded aggregates such as enabled evaluations by cohort. OpenTelemetry describes logs as a signal that can be correlated with traces and resource context, which is the useful distinction here: the detailed decision belongs in a log or span event, while the dashboard should avoid an unbounded tenant-by-tenant label set.
The boundary also clarifies tool selection. OpenTelemetry provides portable signal semantics rather than a feature-flag control plane. LaunchDarkly is primarily a hosted flag-management service; Unleash exposes an open-source, self-hostable flag system; Flipt is an open-source, self-hostable option focused on feature evaluation. Those are different deployment and governance choices, not interchangeable observability guarantees. Whichever evaluation service is used, verify that its SDK or API exposes the rule version and that its failure behavior is compatible with the rollout's risk. Do not infer those properties from a product name.
I would test three cases before shipping: a disabled evaluation, a rule-version change between two requests, and a retry of the same operation ID. The assertions should cover both the application response and the telemetry record. A green API test with missing correlation fields is an incomplete test.
When this design is the wrong fit
Do not put a remote flag evaluation on a latency-critical path merely to obtain richer logs. For a hard real-time or fail-closed decision, a locally cached rule with an explicit freshness limit may be safer; record the cache age and fallback reason so an audit can distinguish a current rule from stale state.
This pattern also does not solve cost attribution when the underlying usage meter has no tenant boundary. Instrumenting a flag check cannot turn shared database I/O into precise customer billing. In that case, define an allocation rule first, such as proportional read volume or reserved capacity, and label the resulting estimate as an estimate. Precision should be earned by the measurement design, not implied by a boolean. A limitation is unavoidable here: if the meter cannot distinguish tenants, the result is an allocation model, not a direct measurement.
The operational review I want is short: can we identify the cohort, reproduce the decision, join downstream work, and suppress duplicates? If any answer is no, the endpoint may still be functional, but its rollout is not yet observable enough for a cost-sensitive experiment.
Top comments (0)