use {tracing::Level,
tracing_subscriber::{EnvFilter,
fmt,
layer::SubscriberExt,
util::SubscriberInitExt}};
#[derive(Debug, Clone)]
pub struct TracingConfig {
pub level: String,
pub format: String,
pub env_filter_conf: Vec<String>,
pub jaeger_enabled: bool,
pub jaeger_endpoint: String,
pub service_name: String,
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
format: "pretty".to_string(),
env_filter_conf: Vec::new(),
jaeger_enabled: false,
jaeger_endpoint: "localhost:6831".to_string(),
service_name: "at-jet-service".to_string(),
}
}
}
pub struct TracingGuard {
_has_jaeger: bool,
}
impl Drop for TracingGuard {
fn drop(&mut self) {
#[cfg(feature = "tracing-otel")]
if self._has_jaeger {
opentelemetry::global::shutdown_tracer_provider();
}
}
}
pub fn init_tracing(config: &TracingConfig) -> TracingGuard {
let filter = build_filter(&config.level, &config.env_filter_conf);
#[cfg(feature = "tracing-otel")]
{
let jaeger_tracer = if config.jaeger_enabled {
match init_jaeger(&config.jaeger_endpoint, &config.service_name) {
| Ok(tracer) => Some(tracer),
| Err(e) => {
eprintln!("Failed to initialize Jaeger: {}", e);
None
}
}
} else {
None
};
let has_jaeger = jaeger_tracer.is_some();
if config.format == "json" {
if let Some(tracer) = jaeger_tracer {
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().json().with_target(true).with_thread_ids(true))
.with(telemetry)
.init();
} else {
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().json().with_target(true).with_thread_ids(true))
.init();
}
} else if let Some(tracer) = jaeger_tracer {
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().with_target(true).with_thread_ids(true))
.with(telemetry)
.init();
} else {
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().with_target(true).with_thread_ids(true))
.init();
}
TracingGuard {
_has_jaeger: has_jaeger,
}
}
#[cfg(not(feature = "tracing-otel"))]
{
if config.format == "json" {
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().json().with_target(true).with_thread_ids(true))
.init();
} else {
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer().with_target(true).with_thread_ids(true))
.init();
}
TracingGuard { _has_jaeger: false }
}
}
fn build_filter(level: &str, env_filter_conf: &[String]) -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
let base_level = match level.to_lowercase().as_str() {
| "trace" => Level::TRACE,
| "debug" => Level::DEBUG,
| "info" => Level::INFO,
| "warn" | "warning" => Level::WARN,
| "error" => Level::ERROR,
| _ => Level::INFO,
};
let mut filter_parts = vec![base_level.to_string()];
if !env_filter_conf.is_empty() {
filter_parts.extend(env_filter_conf.iter().cloned());
} else {
filter_parts.extend([
"sqlx=warn".to_string(),
"hyper=warn".to_string(),
"reqwest=warn".to_string(),
"h2=warn".to_string(),
"tower=warn".to_string(),
"rustls=warn".to_string(),
]);
}
EnvFilter::new(filter_parts.join(","))
})
}
#[cfg(feature = "tracing-otel")]
fn init_jaeger(
endpoint: &str,
service_name: &str,
) -> std::result::Result<opentelemetry_sdk::trace::Tracer, opentelemetry::trace::TraceError> {
if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
opentelemetry_jaeger::new_collector_pipeline()
.with_service_name(service_name)
.with_endpoint(endpoint)
.with_reqwest()
.install_batch(opentelemetry_sdk::runtime::Tokio)
} else {
opentelemetry_jaeger::new_agent_pipeline()
.with_service_name(service_name)
.with_endpoint(endpoint)
.with_auto_split_batch(true)
.install_batch(opentelemetry_sdk::runtime::Tokio)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_tracing_config_default() {
let config = TracingConfig::default();
assert_eq!(config.level, "info");
assert_eq!(config.format, "pretty");
assert!(config.env_filter_conf.is_empty());
assert!(!config.jaeger_enabled);
assert_eq!(config.service_name, "at-jet-service");
}
#[test]
fn test_build_filter_default_noisy_crate_suppression() {
let filter = build_filter("info", &[]);
let filter_str = format!("{}", filter);
assert!(filter_str.contains("info"));
}
#[test]
fn test_build_filter_custom_modules() {
let custom = vec!["myapp=debug".to_string(), "sqlx=error".to_string()];
let filter = build_filter("warn", &custom);
let filter_str = format!("{}", filter);
assert!(filter_str.contains("warn"));
}
#[test]
fn test_build_filter_level_parsing() {
let _ = build_filter("trace", &[]);
let _ = build_filter("debug", &[]);
let _ = build_filter("info", &[]);
let _ = build_filter("warn", &[]);
let _ = build_filter("warning", &[]);
let _ = build_filter("error", &[]);
let _ = build_filter("invalid", &[]);
}
}