Skip to content

Export to an OTLP collector

OtlpSink turns each event into one OpenTelemetry log record. It needs the remote-sinks feature.

[dependencies]
rtb-telemetry = { version = "0.7", features = ["remote-sinks"] }

Use a gRPC endpoint

use std::sync::Arc;
use std::time::Duration;
use rtb_telemetry::{OtlpSink, OtlpSinkConfig};

let sink = Arc::new(OtlpSink::new(OtlpSinkConfig {
    endpoint: "grpc://collector.internal:4317".into(),
    headers: vec![],
    timeout: Duration::from_secs(10),
    resource_attrs: vec![("service.name".into(), "mytool".into())],
})?);

Use grpc:// or grpcs://, or an http(s):// URL containing :4317. Do not use an OTLP/HTTP endpoint: in 0.7.3 that transport panics inside the SDK's exporter thread and exports nothing. The failure and the versions it was tested against are in Sinks. Almost every collector accepts OTLP/gRPC on 4317, so this is usually a one-word change.

Transport is inferred from the endpoint — there is no explicit setting. The full rule is in Sinks.

Authenticate, or address a tenant

use secrecy::SecretString;

headers: vec![
    ("authorization".into(), SecretString::from(format!("Bearer {token}"))),
    ("x-scope-orgid".into(), SecretString::from("team-a".to_string())),
],

Header values are SecretString, so they stay out of Debug output. On the gRPC transport they become request metadata; an invalid header name or value is rejected when the sink is built, as TelemetryError::Otlp.

Check that the export actually worked

emit returns Ok(()) whatever happens — including when the collector is refusing connections. The only way to find out is to flush:

telemetry.record("command.invoke").await?;

if let Err(err) = telemetry.flush().await {
    tracing::warn!(%err, "telemetry export failed");
}

A dead collector produces something like:

OTLP telemetry sink error: flush: Operation failed: errs: [Err(InternalFailure("TonicLogsClient export failed with gRPC code: Unavailable: transport error: tcp connect error: tcp connect error: Connection refused (os error 111)"))]

Flush before the process exits, or the batch the SDK is holding goes with it.

Find your events in the collector

Each event arrives as a log record with event name rtb.telemetry.event, a severity of INFO or ERROR, the redacted event as a JSON string body, and attributes tool, tool.version and event.name.

The resource's service.name is the constant "rtb-telemetry" unless you set it in resource_attrs, so query by the tool attribute — or set service.name yourself, as in the first example, if your backend's navigation is built around services.

Do not expect traces or metrics

OtlpSink emits log records only. This crate is product analytics about a user's tool, not instrumentation of a running service; if you want spans and metrics, wire the OpenTelemetry SDK up directly alongside it.