x-varianz-id propagation in each instrumented service. This is a one-time setup per service; afterwards, every VPoint you add works with test sessions automatically.
The session ID must be:
- Extracted from incoming requests (HTTP header or gRPC metadata)
- Held in the execution context for the duration of the request
- Injected into all outgoing requests to downstream services
With OpenTelemetry Baggage
If your services already run OTel with the W3C Baggage propagator (most default setups include it), propagation across HTTP and gRPC hops is nearly free — carryx-varianz-id as a baggage entry and OTel’s instrumented clients and servers move it for you.
Varianz.set_session, varianz.WithSession, etc.) — the sections below show where.
Python
A gRPC server interceptor extracts the ID and activates session routing; handlers need no per-request code:@app.before_request in Flask, or an ASGI middleware in FastAPI that reads the x-varianz-id header and calls Varianz.set_session(...).
Outbound: forward the current session on downstream calls.
contextvar, so it copies across asyncio tasks automatically. Varianz and OTel interceptors coexist without conflict.
Go
Set the session on the requestcontext.Context with varianz.WithSession; both varianz.VPoint callables and proxy.Call read it from there.
TypeScript
The SDK holds the session inAsyncLocalStorage, exposed as VarianzContext. It survives await, timers, and event emitters.
onRequest/middleware hook and wrap the rest of the request in VarianzContext.session(...). For NestJS, nestjs-cls provides the same AsyncLocalStorage plumbing.
gRPC (@grpc/grpc-js): server interceptors return synchronously before the handler runs, so they can’t wrap the async handler in the storage scope. Bind the session inside the handler instead:
varianzId('paramName') in the @vpoint params and the SDK enters the scope automatically.
Java and Kotlin
The JVM SDK pulls the session ID on every VPoint invocation through a single static method you provide, annotated@VarianzId @Function(global = true). Your framework interceptor stashes the incoming header in a request-scoped carrier; the annotated method reads it back. This one pattern works for gRPC, Spring MVC, WebFlux, and virtual threads — only the carrier changes.
Step 1 — the binding class (ThreadLocal shown; see the carrier table below):
HandlerInterceptor (preHandle sets, afterCompletion clears) and register it via WebMvcConfigurer.
Step 3 — forward downstream:
RestTemplate, WebClient, OkHttp), an outbound interceptor reads sessionOverride() and sets the header the same way.
Pick the carrier for your concurrency model. Plain ThreadLocal is correct for thread-per-request servlet and gRPC work, and wrong for reactive code:
The annotated method runs synchronously on the VPoint caller thread, so it must read from a carrier that’s populated on that thread — don’t try to
block() on a reactive context inside it.
Services with explicit header-forwarding lists
Some codebases forward a fixed set of headers at the application level (agetForwardHeaders()-style function listing x-request-id, x-b3-traceid, …) instead of using interceptors. If yours does, add x-varianz-id to that list — a one-line change that carries the session through the whole chain:
forward.*header, getForward, or existing tracing headers in the codebase.
Verify before writing tests
- Start a local registry and your services.
- Send a request with a fake session ID:
- Check each downstream service’s logs to confirm the ID arrived at every hop.
