Skip to Content

How do I attach an end user to a trace with OpenInference instrumentors?

Rius reads the end user from the user.id attribute on each span, so you attach a user by setting that string on every span of the trace. The exporter wiring for OpenInference instrumentors sets no user, so you add user.id yourself, root span included. A span with no user attribute counts as anonymous on the Users page.

How it works

  • Rius groups traces by user from the user.id attribute. This is the key OpenInference uses for users, and Rius stores it as sent.
  • If user.id is missing, Rius falls back to enduser.id, then to enduser.pseudo.id. A span with none of the three is anonymous.
  • Rius reads the user per span, not per trace. Set the value on every span in scope, not only the root, or the spans without it count as anonymous.
  • The exporter wiring for OpenInference instrumentors, shown in step 1 below, carries no user setting. There is also no environment variable for the user, by design.
  • Rius normalizes the other OpenInference attributes (model, tokens, messages) onto gen_ai.* keys at ingest. The user needs no such mapping, because user.id is already the key Rius reads.
  • The Users page then lists each end user your agents served, with their traces, failures, cost and time to first token.

Set it up

  1. Wire the instrumentor to the Rius ingest endpoint. This is the standard OpenInference setup for Rius, and it raises the span attribute limit to 4096 so a long span keeps all its attributes, including the user. For the full wiring, see the OpenInference page.
from opentelemetry.sdk.trace import SpanLimits, TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from openinference.instrumentation.openai import OpenAIInstrumentor provider = TracerProvider(span_limits=SpanLimits(max_span_attributes=4096)) provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="https://ingest.eu.console.rius-glassflow.com/v1/traces", headers={"Authorization": "Bearer <your API key>"}, ) ) ) OpenAIInstrumentor().instrument(tracer_provider=provider)
  1. Pick an identifier for the person your request serves. Use an opaque id, such as an internal user id, in preference to an email address.
  2. Set that value as the string attribute user.id on every span of the request, including the root span. Wrap the request in OpenInference’s using_user(user_id) from openinference-instrumentation, which stamps user.id on the spans OpenInference creates. A root span you start from a plain OpenTelemetry tracer misses it, so set the attribute on that span yourself. The trace list shows each trace under the user of its root span.
from openinference.instrumentation import using_user tracer = provider.get_tracer(__name__) with using_user(user_id): with tracer.start_as_current_span("handle-turn", attributes={"user.id": user_id}): run_agent(message) # your agent's own code
  1. If you also group conversations, set session.id on every span in the same way. Without it, each trace is its own session.
  2. Send one request, open the trace in the console, and check that the user appears on the Users page. A trace that shows as anonymous means user.id, enduser.id and enduser.pseudo.id were all missing on the spans Rius read.

For the full list of attributes Rius reads, see the span attribute reference. The general wiring options for any OpenTelemetry sender are in Trace LLM calls with OpenTelemetry.

What you get

  • Each end user appears on the Users page with their traces, failures, cost and time to first token.
  • Cost per user is available because Rius computes cost on the model-call spans from the model and token counts. You never send a cost attribute.
  • Traces from the same person group together, and traces without a user stay visible as anonymous rather than disappearing.
  • Because user.id is stored as sent, you can search and filter on that exact key in the trace list.
  • The user rides alongside the model, token and message data that Rius normalizes from the OpenInference attributes, so one trace answers both what happened and who it happened to.

When something else fits

  • If your app is Python or TypeScript and you can use the Rius SDK, it ships the same OpenInference instrumentors as integrations. There you set the user with rius.user in Python or withUser in TypeScript, and the SDK stamps user.id for you.
  • If your spans already carry enduser.id or enduser.pseudo.id, you do not need to rename them. Rius reads those keys when user.id is absent.
  • If you send through a Collector, make sure no processor strips the user attribute on the way.

Still stuck? Get help

Last updated on