Skip to main content
More

OpenTelemetry

Export traces from Rivet Actors, add application spans, and follow work across actors with ray IDs.

Rivet gives you automatic instrumentation of actor actions, HTTP handlers, actor-to-actor calls, SQLite operations, queues, and scheduled actions.

This helps you investigate anything that looks slow, walk through complex actor-to-actor flows, and get end-to-end context into the life of your actor.

YOUR ACTOR PROCESSRivetKitautomatic actor spansYour codeyour application spansOTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTLP collectorJaeger, Tempo, Honeycomb

Quickstart

Set an OTLP exporter within the process that runs your actors, next to RIVET_ENDPOINT, and redeploy:

OTEL_SERVICE_NAME=my-rad-app
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://collector:4318/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf

OTEL_EXPORTER_OTLP_TRACES_ENDPOINT acts as a feature flag; setting it enables tracing. See Configure the exporter for more options.

You should now see spans named {actor}/{action} in your observability platform under OTEL_SERVICE_NAME.

What gets traced

Each span shows how long an operation took and whether it failed. RivetKit records:

  • Actions and HTTP handlers, with the operations they perform shown underneath
  • Calls through c.client(), including time spent routing, waking the other actor, and retrying
  • SQLite operations, without recording SQL text, bindings, or results
  • Queue sends and receipts, with a link from each receipt to its sender
  • Scheduled actions, in a new trace linked to the work that scheduled them

The full list of span names and attributes lives in telemetry.rs, and the SQLite operations in sqlite/mod.rs.

Add application spans

You can trace your own actor code with @opentelemetry/api. Your spans will nest with RivetKit spans in the same trace.

Here’s a quick guide on how to set this up:

Install the API and SDK

npm install @opentelemetry/api @opentelemetry/sdk-node

Start the SDK

Start the SDK before your RivetKit registry:

import { NodeSDK } from "@opentelemetry/sdk-node";

const sdk = new NodeSDK();
sdk.start();

Trace your code

Wrap the code you want to measure in startActiveSpan():

import { SpanStatusCode, trace } from "@opentelemetry/api";
import { actor } from "rivetkit";
import { db } from "rivetkit/db";

const tracer = trace.getTracer("counter");

export const counter = actor({
	state: {},
	db: db(),
	actions: {
		query: async (c) =>
			tracer.startActiveSpan("counter.query", async (span) => {
				try {
					return await c.db.execute("SELECT 1 AS value");
				} catch (error) {
					span.setStatus({ code: SpanStatusCode.ERROR });
					throw error;
				} finally {
					span.end();
				}
			}),
	},
});

Calling query produces this trace:

counter/query
└── counter.query             application span
    └── rivet.sqlite.execute  traced by RivetKit

Use a fixed span name, such as generate_response. Avoid building the name from changing values, like generate_response_${requestId}. Put those values in span attributes instead.

Follow work across actors with ray IDs

A ray ID follows a request across actor calls and scheduled work. Rivet supplies one automatically, or you can provide your own.

Each blue box shows an actor running an action. RivetKit records each execution as a span, with its duration and outcome.

ONE RAY IDCallerx-rivet-ray-idoptionalRivetAction: appMentionActor: slackThreadc.client()Action: summarizeActor: summarizerc.schedule.after(60s)Action: postDigestActor: slackThreadSame ray ID across this workray ID: my-rivet-ray-id
  • Search for rivet.ray.id in traces or rayId in actor logs to find related work.
  • To use your own ID, set rivet.ray.id in OpenTelemetry baggage for client calls, or send x-rivet-ray-id on raw HTTP requests.

A ray ID must be 1–30 characters long and may contain only letters, digits, hyphens (-), and underscores (_). Invalid values are ignored. Use ray IDs to find related traces, not to authenticate requests.

Configure the exporter

VariablePurpose
OTEL_SERVICE_NAMEService name reported with spans
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTTrace collector endpoint
OTEL_EXPORTER_OTLP_TRACES_PROTOCOLhttp/protobuf, http/json, or grpc
OTEL_EXPORTER_OTLP_HEADERSHeaders sent with every export, such as auth
OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARGSampling
OTEL_SDK_DISABLED=trueTurn telemetry off

For more settings, see the OTLP exporter configuration and SDK configuration.

RivetKit sends spans in the background. Actor requests continue if the collector is slow or unavailable. Spans can be lost when exports fail or the buffer fills; export failures and dropped spans are reported in warnings.

Known limitations

  • WebSocket handlers, lifecycle hooks, connection callbacks, KV operations, and actor state operations do not have dedicated spans
  • Actions called over .connect() start a new trace instead of joining the caller’s trace
  • Effect spans are not traced. Use @effect/opentelemetry
  • RivetKit does not add rivet.ray.id to your application spans
  • The Wasm runtime is not traced
  • A hard process exit can lose spans still waiting in the export queue