#[cfg(feature = "metrics")]
use std::time::Duration;
use thiserror::Error;
#[cfg(feature = "metrics")]
use opentelemetry::global;
#[cfg(feature = "metrics")]
use opentelemetry_otlp::WithExportConfig;
#[cfg(feature = "metrics")]
use opentelemetry_sdk::runtime;
#[derive(Debug, Clone)]
pub struct TelemetryConfig {
pub endpoint: Option<String>,
pub service_name: String,
pub environment: String,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
endpoint: std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(),
service_name: "communitas".to_string(),
environment: std::env::var("OTEL_ENVIRONMENT")
.unwrap_or_else(|_| "development".to_string()),
}
}
}
#[derive(Debug, Error)]
pub enum TelemetryError {
#[error("Failed to initialize OTLP exporter: {0}")]
ExporterInit(String),
#[error("Failed to initialize meter provider: {0}")]
MeterProviderInit(String),
#[error("No OTLP endpoint configured")]
NoEndpoint,
}
#[cfg(feature = "metrics")]
pub fn init_telemetry(config: TelemetryConfig) -> Result<bool, TelemetryError> {
let endpoint = match config.endpoint {
Some(ep) => ep,
None => {
tracing::info!("Telemetry disabled: no OTEL_EXPORTER_OTLP_ENDPOINT configured");
return Ok(false);
}
};
tracing::info!(
endpoint = %endpoint,
service = %config.service_name,
environment = %config.environment,
"Initializing OpenTelemetry OTLP exporter"
);
let exporter = opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(&endpoint)
.with_timeout(Duration::from_secs(10));
let meter_provider = opentelemetry_otlp::new_pipeline()
.metrics(runtime::Tokio)
.with_exporter(exporter)
.with_period(Duration::from_secs(30))
.build()
.map_err(|e| TelemetryError::MeterProviderInit(e.to_string()))?;
global::set_meter_provider(meter_provider);
tracing::info!("OpenTelemetry initialized successfully");
Ok(true)
}
#[cfg(not(feature = "metrics"))]
pub fn init_telemetry(_config: TelemetryConfig) -> Result<bool, TelemetryError> {
tracing::debug!("Telemetry disabled: 'metrics' feature not enabled");
Ok(false)
}
#[cfg(feature = "metrics")]
pub fn shutdown_telemetry() {
tracing::info!("Shutting down OpenTelemetry provider");
}
#[cfg(not(feature = "metrics"))]
pub fn shutdown_telemetry() {
}
#[cfg(feature = "metrics")]
pub fn meter(scope: &'static str) -> opentelemetry::metrics::Meter {
global::meter(scope)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = TelemetryConfig::default();
assert_eq!(config.service_name, "communitas");
}
#[test]
fn test_init_without_endpoint() {
let config = TelemetryConfig {
endpoint: None,
service_name: "test".to_string(),
environment: "test".to_string(),
};
let result = init_telemetry(config);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_shutdown_noop() {
shutdown_telemetry();
}
}