mockforge_observability/
tracing_integration.rs

1//! OpenTelemetry tracing integration for structured logging
2//!
3//! This module provides integration between the logging system and OpenTelemetry distributed tracing.
4//! It allows logs to be correlated with traces for better observability in distributed systems.
5
6use crate::logging::LoggingConfig;
7
8/// OpenTelemetry tracing configuration
9#[derive(Debug, Clone)]
10pub struct OtelTracingConfig {
11    /// Service name for traces
12    pub service_name: String,
13    /// Deployment environment (development, staging, production)
14    pub environment: String,
15    /// Jaeger endpoint for trace export
16    pub jaeger_endpoint: Option<String>,
17    /// OTLP endpoint (alternative to Jaeger)
18    pub otlp_endpoint: Option<String>,
19    /// Protocol: grpc or http
20    pub protocol: String,
21    /// Sampling rate (0.0 to 1.0)
22    pub sampling_rate: f64,
23}
24
25impl Default for OtelTracingConfig {
26    fn default() -> Self {
27        Self {
28            service_name: "mockforge".to_string(),
29            environment: "development".to_string(),
30            jaeger_endpoint: Some("http://localhost:14268/api/traces".to_string()),
31            otlp_endpoint: Some("http://localhost:4317".to_string()),
32            protocol: "grpc".to_string(),
33            sampling_rate: 1.0,
34        }
35    }
36}
37
38/// Initialize logging with OpenTelemetry tracing
39///
40/// This is a convenience function that integrates logging with OpenTelemetry when the
41/// mockforge-tracing crate is available. It initializes the OpenTelemetry tracer and
42/// sets up the logging system with a tracing layer.
43///
44/// # Arguments
45/// * `logging_config` - Logging configuration
46/// * `tracing_config` - OpenTelemetry tracing configuration
47///
48/// # Example
49/// ```no_run
50/// use mockforge_observability::tracing_integration::{init_with_otel, OtelTracingConfig};
51/// use mockforge_observability::logging::LoggingConfig;
52///
53/// let logging_config = LoggingConfig {
54///     level: "info".to_string(),
55///     json_format: true,
56///     ..Default::default()
57/// };
58///
59/// let tracing_config = OtelTracingConfig {
60///     service_name: "mockforge".to_string(),
61///     environment: "production".to_string(),
62///     ..Default::default()
63/// };
64///
65/// // init_with_otel(logging_config, tracing_config)
66/// //     .expect("Failed to initialize logging with OpenTelemetry");
67/// ```
68#[cfg(feature = "opentelemetry")]
69pub fn init_with_otel(
70    logging_config: LoggingConfig,
71    tracing_config: OtelTracingConfig,
72) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
73    // Try to initialize OpenTelemetry tracing if mockforge-tracing is available
74    #[cfg(feature = "mockforge-tracing")]
75    {
76        use mockforge_tracing::{init_tracer, ExporterType, TracingConfig};
77
78        // Map protocol string to ExporterType enum
79        let exporter_type = match tracing_config.protocol.as_str() {
80            "grpc" | "http" if tracing_config.otlp_endpoint.is_some() => ExporterType::Otlp,
81            _ => ExporterType::Jaeger, // Default to Jaeger
82        };
83
84        let tracing_cfg = TracingConfig {
85            service_name: tracing_config.service_name.clone(),
86            service_version: None, // Optional field
87            environment: tracing_config.environment.clone(),
88            jaeger_endpoint: tracing_config.jaeger_endpoint.clone(),
89            otlp_endpoint: tracing_config.otlp_endpoint.clone(),
90            exporter_type,
91            sampling_rate: tracing_config.sampling_rate,
92        };
93
94        match init_tracer(tracing_cfg) {
95            Ok(_) => {
96                tracing::info!("OpenTelemetry tracing initialized successfully");
97            }
98            Err(e) => {
99                tracing::warn!(
100                    "Failed to initialize OpenTelemetry tracing: {}. Using logging only.",
101                    e
102                );
103            }
104        }
105    }
106
107    #[cfg(not(feature = "mockforge-tracing"))]
108    {
109        tracing::warn!("OpenTelemetry feature enabled but mockforge-tracing crate not available. Using logging only.");
110    }
111
112    // Always initialize logging
113    crate::logging::init_logging(logging_config)?;
114
115    Ok(())
116}
117
118/// Shutdown OpenTelemetry tracer and flush pending spans
119#[cfg(feature = "opentelemetry")]
120pub fn shutdown_otel() {
121    #[cfg(feature = "mockforge-tracing")]
122    {
123        use mockforge_tracing::shutdown_tracer;
124        shutdown_tracer();
125        tracing::info!("OpenTelemetry tracer shut down");
126    }
127
128    #[cfg(not(feature = "mockforge-tracing"))]
129    {
130        tracing::debug!("OpenTelemetry shutdown called but mockforge-tracing not available");
131    }
132}
133
134#[cfg(not(feature = "opentelemetry"))]
135pub fn init_with_otel(
136    logging_config: LoggingConfig,
137    _tracing_config: OtelTracingConfig,
138) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
139    tracing::warn!("OpenTelemetry feature not enabled, using standard logging");
140    crate::logging::init_logging(logging_config)?;
141    Ok(())
142}
143
144#[cfg(not(feature = "opentelemetry"))]
145pub fn shutdown_otel() {
146    // No-op when OpenTelemetry is not enabled
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_default_otel_config() {
155        let config = OtelTracingConfig::default();
156        assert_eq!(config.service_name, "mockforge");
157        assert_eq!(config.environment, "development");
158        assert_eq!(config.sampling_rate, 1.0);
159        assert_eq!(config.protocol, "grpc");
160    }
161
162    #[test]
163    fn test_custom_otel_config() {
164        let config = OtelTracingConfig {
165            service_name: "test-service".to_string(),
166            environment: "production".to_string(),
167            jaeger_endpoint: Some("http://jaeger:14268/api/traces".to_string()),
168            otlp_endpoint: Some("http://otel:4317".to_string()),
169            protocol: "http".to_string(),
170            sampling_rate: 0.5,
171        };
172
173        assert_eq!(config.service_name, "test-service");
174        assert_eq!(config.environment, "production");
175        assert_eq!(config.sampling_rate, 0.5);
176    }
177}