mx-logging 0.1.0

Tracing and OpenTelemetry logging utilities for MultiversX Rust services.
Documentation
//! Logging and tracing utilities for `MultiversX` services.
//!
//! This crate provides a unified logging interface with optional OpenTelemetry
//! distributed tracing support.
//!
//! # Basic Usage
//!
//! ```rust,no_run
//! use mx_logging::init;
//!
//! // Initialize with default filter
//! init(None, "info");
//! ```
//!
//! # OpenTelemetry Integration
//!
//! When the `opentelemetry` feature is enabled, you can configure distributed tracing:
//!
//! ```rust,ignore
//! use mx_logging::{OtelConfig, init_with_otel};
//!
//! let config = OtelConfig {
//!     service_name: "mx-relayer".to_string(),
//!     otlp_endpoint: "http://localhost:4317".to_string(),
//!     ..Default::default()
//! };
//!
//! init_with_otel(&config, None, "info").expect("Failed to initialize tracing");
//! ```

use std::sync::OnceLock;

use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};

#[cfg(feature = "opentelemetry")]
mod otel;

#[cfg(feature = "opentelemetry")]
pub use otel::{OtelConfig, OtelError, init_with_otel, shutdown_otel};

#[cfg(test)]
mod tests;

static TRACING: OnceLock<()> = OnceLock::new();

/// Initialize a compact, human-friendly tracing subscriber.
///
/// Priority for the filter is:
/// 1. Explicit `filter` argument (if provided)
/// 2. `RUST_LOG` / default env filter
/// 3. `default_filter`
///
/// Also enables the log-to-tracing bridge so `log` crate messages (e.g., from yamux)
/// are filtered by the same rules.
pub fn init(filter: Option<&str>, default_filter: &str) {
    TRACING.get_or_init(|| {
        let env_filter = build_filter(filter, default_filter);
        let fmt_layer = build_fmt_layer();

        // Initialize with the log-to-tracing bridge enabled
        tracing_subscriber::registry()
            .with(env_filter)
            .with(fmt_layer)
            .init();
    });
}

/// Build an `EnvFilter` based on priority: explicit filter > `RUST_LOG` env > `default_filter`.
///
/// This function is public for testing purposes.
pub(crate) fn build_filter(filter: Option<&str>, default_filter: &str) -> EnvFilter {
    if let Some(explicit) = filter {
        EnvFilter::try_new(explicit).unwrap_or_else(|_| EnvFilter::new(default_filter))
    } else if let Ok(env_filter) = EnvFilter::try_from_default_env() {
        env_filter
    } else {
        EnvFilter::new(default_filter)
    }
}

/// Build a compact fmt layer for console output.
///
/// This function is extracted for testability.
pub(crate) fn build_fmt_layer<S>() -> impl tracing_subscriber::Layer<S>
where
    S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
    fmt::layer()
        .compact()
        .with_level(true)
        .with_target(false)
        .with_file(false)
        .with_line_number(false)
}

/// Check if tracing has been initialized.
///
/// This is useful for applications that need to conditionally initialize logging.
pub fn is_initialized() -> bool {
    TRACING.get().is_some()
}