use std::sync::atomic::{AtomicBool, Ordering};
pub const GEN_AI_SYSTEM: &str = "gen_ai.system";
pub const GEN_AI_REQUEST_MODEL: &str = "gen_ai.request.model";
pub const GEN_AI_CONVERSATION_ID: &str = "gen_ai.conversation.id";
pub const GEN_AI_USAGE_INPUT_TOKENS: &str = "gen_ai.usage.input_tokens";
pub const GEN_AI_USAGE_OUTPUT_TOKENS: &str = "gen_ai.usage.output_tokens";
pub const GEN_AI_TOOL_NAME: &str = "gen_ai.tool.name";
pub const SYSTEM_NAME: &str = "smooth-operator";
pub const SPAN_CHAT: &str = "gen_ai.chat";
pub const SPAN_TOOL: &str = "gen_ai.tool";
pub const OTLP_ENDPOINT_ENV: &str = "OTEL_EXPORTER_OTLP_ENDPOINT";
static INITIALIZED: AtomicBool = AtomicBool::new(false);
pub fn init_telemetry() -> bool {
if INITIALIZED
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return false;
}
use tracing_subscriber::prelude::*;
use tracing_subscriber::{EnvFilter, Registry};
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,smooth_operator=info"));
let fmt_layer = tracing_subscriber::fmt::layer();
match std::env::var(OTLP_ENDPOINT_ENV) {
Ok(endpoint) if !endpoint.trim().is_empty() => {
match build_otlp_layer(&endpoint) {
Ok(otel_layer) => {
Registry::default()
.with(env_filter)
.with(fmt_layer)
.with(otel_layer)
.init();
tracing::info!(endpoint = %endpoint, "telemetry: OTLP exporter installed");
}
Err(e) => {
Registry::default().with(env_filter).with(fmt_layer).init();
tracing::warn!(
error = %e,
endpoint = %endpoint,
"telemetry: OTLP exporter init failed; using local-only logging"
);
}
}
}
_ => {
Registry::default().with(env_filter).with(fmt_layer).init();
tracing::debug!(
"telemetry: {OTLP_ENDPOINT_ENV} unset; local-only logging (no OTLP exporter)"
);
}
}
true
}
fn build_otlp_layer<S>(
endpoint: &str,
) -> anyhow::Result<Box<dyn tracing_subscriber::Layer<S> + Send + Sync>>
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a> + Send + Sync,
{
use opentelemetry::trace::TracerProvider as _;
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use tracing_subscriber::Layer;
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()?;
let resource = Resource::builder()
.with_attribute(KeyValue::new("service.name", SYSTEM_NAME))
.build();
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(resource)
.build();
let tracer = provider.tracer(SYSTEM_NAME);
let _ = opentelemetry::global::set_tracer_provider(provider);
Ok(tracing_opentelemetry::layer().with_tracer(tracer).boxed())
}