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.
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:
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.
- Search for
rivet.ray.idin traces orrayIdin actor logs to find related work. - To use your own ID, set
rivet.ray.idin OpenTelemetry baggage for client calls, or sendx-rivet-ray-idon 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
| Variable | Purpose |
|---|---|
OTEL_SERVICE_NAME | Service name reported with spans |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | Trace collector endpoint |
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL | http/protobuf, http/json, or grpc |
OTEL_EXPORTER_OTLP_HEADERS | Headers sent with every export, such as auth |
OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG | Sampling |
OTEL_SDK_DISABLED=true | Turn 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.idto your application spans - The Wasm runtime is not traced
- A hard process exit can lose spans still waiting in the export queue