Skip to main content

7.14 telemetry: OTLP export with GenAI conventions

// crates/telemetry/src/lib.rs — called from Loco's init_logger hook (§7.2)
use opentelemetry::{global, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

pub fn init(service: &str, endpoint: &str) -> anyhow::Result<SdkTracerProvider> {
let exporter = opentelemetry_otlp::SpanExporter::builder().with_tonic().with_endpoint(endpoint).build()?;
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(Resource::builder().with_attributes([KeyValue::new("service.name", service.to_string())]).build())
.build(); // VERIFY builder names for the pinned version
global::set_tracer_provider(provider.clone());
let tracer = opentelemetry::trace::TracerProvider::tracer(&provider, "bo");
tracing_subscriber::registry().with(EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer().json())
.with(tracing_opentelemetry::layer().with_tracer(tracer)).init();
Ok(provider)
}

/// Use on every LLM call. Prompt bodies are not recorded unless settings.telemetry.export_prompt_bodies is true.
#[macro_export]
macro_rules! llm_span { ($model:expr, $op:expr) => {
tracing::info_span!("gen_ai", "gen_ai.operation.name" = $op, "gen_ai.system" = "vllm",
"gen_ai.request.model" = $model, "gen_ai.usage.input_tokens" = tracing::field::Empty,
"gen_ai.usage.output_tokens" = tracing::field::Empty)
}}