"Which variation of the flag did this request get?" is the question that ends up unanswered in many incident retros, because the flag evaluation and the trace that shows the failure live in two places that don't reference each other. Putting the evaluation result on the request's span closes that gap.
Here is exactly what rollfuse does and what stays with you. The SDKs evaluate flags locally and return the result, including the variation key and the reason; they don't write span attributes by themselves. Recording the result on a span is a few lines in your code. What the Node.js SDK does on its own is propagate W3C trace context on its calls to the rollfuse API.
Record the evaluation on the active span
With the Node.js SDK (@rollfuse/sdk-js) and @opentelemetry/api, read the result and write it onto whatever span is active for the request:
import { trace } from "@opentelemetry/api";
import { RollfuseClient } from "@rollfuse/sdk-js";
const client = new RollfuseClient({
baseUrl: "https://api.rollfuse.com",
credential: process.env.ROLLFUSE_SERVICE_CREDENTIAL!,
});
// Required: start() begins fetching the configuration. Awaiting it is optional.
void client.start();
export function isCheckoutRedesignOn(subjectKey: string): boolean {
const result = client.evaluate(subjectKey, "checkout-redesign", { fallback: false });
const span = trace.getActiveSpan();
span?.setAttribute("rollfuse.flag_key", "checkout-redesign");
span?.setAttribute("rollfuse.variation_key", result.variation_key);
span?.setAttribute("rollfuse.reason", result.reason);
return Boolean(result.value);
}
The attribute names are your choice; these are the ones used in the public example below. client.start() is required: it is what starts fetching the configuration, and without it every evaluation returns the fallback. Awaiting it is optional; await it at startup if you want the first configuration loaded before serving traffic. The Go SDK returns the same fields (VariationKey, Reason), so you can record them on a span the same way in a Go service. The Go SDK doesn't add trace headers to its own API calls.
What this makes possible
- A slow or failing span filters straight to its variation. Query traces where
rollfuse.flag_key = checkout-redesignandrollfuse.variation_key = on, and you see the requests that took the new code path, without matching a separate log by timestamp. - "Is this the new code or the old code" becomes a filter. During an incident, the answer is on the span instead of in someone's memory of what was supposed to be live.
- It works in the trace viewer you already use, because these are ordinary span attributes exported by your existing OpenTelemetry setup. There is no rollfuse exporter to install.
What the SDK does by itself: trace context on its own calls
@rollfuse/sdk-js adds W3C trace context (traceparent) to the calls it makes to the rollfuse API, such as fetching configuration and sending exposure batches. If @opentelemetry/api is installed and a span is active at that moment, the SDK injects that span's context; otherwise it generates a fresh traceparent. Exposure batches are sent from a background timer, so they usually start their own trace rather than joining a request's. Flag evaluation itself makes no network call, so it adds no span of its own.
Try it locally
The public examples-node-opentelemetry repository runs a flagged checkout service with OpenTelemetry and Jaeger in one docker compose up, against a local stand-in for the rollfuse API, so it needs no rollfuse account. Each trace shows the HTTP span, an evaluate-flag span carrying the attributes above, and the checkout work that followed. Tracing doesn't feed a guardrail. To make an OpenTelemetry metric drive an automatic rollback, its value has to reach rollfuse as observations, the same way the Prometheus page describes.