backbone_observability/
lib.rs1#![warn(missing_docs)]
6#![warn(clippy::all)]
7
8pub mod config;
9pub mod logging;
10pub mod metrics;
11pub mod middleware;
12pub mod tracing;
13
14#[cfg(feature = "audit")]
15pub mod audit;
16
17#[cfg(test)]
18mod tests;
19
20pub use config::{MetricsConfig, MetricsExporterType, ObservabilityConfig};
22pub use config::LoggingConfig as StructuredLoggingConfig;
23pub use logging::*;
24pub use metrics::*;
25pub use middleware::{ObservabilityLayer, ObservabilityMiddleware};
26
27pub fn init_structured_logging(
38 config: &StructuredLoggingConfig,
39 service_name: &str,
40 environment: &str,
41 log_level: &str,
42) -> Result<LogBuffer, anyhow::Error> {
43 use crate::logging::{LogBuffer, LoggingConfig};
45
46 let logging_config = LoggingConfig {
48 json_format: config.json_format,
49 include_timestamp: config.include_timestamp,
50 include_hostname: config.include_hostname,
51 include_spans: config.include_spans,
52 extract_fields: config.extract_fields.clone(),
53 buffer_size: config.buffer_size,
54 };
55
56 let mut buffer_config = logging_config.clone();
57 buffer_config.extract_fields.push("service".to_string());
58 buffer_config.extract_fields.push("environment".to_string());
59
60 let buffer = LogBuffer::new(buffer_config);
61
62 crate::logging::init_structured_logging(
64 &logging_config,
65 service_name,
66 environment,
67 log_level,
68 )?;
69
70 Ok(buffer)
71}
72
73pub fn get_aggregated_logs(buffer: &LogBuffer) -> Vec<LogEntry> {
77 buffer.drain()
78}
79
80pub enum OtelShutdownGuard {
87 #[cfg(feature = "otel-tracing")]
89 Active(opentelemetry_sdk::trace::TracerProvider),
90 Inactive,
92}
93
94impl Drop for OtelShutdownGuard {
95 fn drop(&mut self) {
96 match self {
97 #[cfg(feature = "otel-tracing")]
98 Self::Active(_provider) => {
99 ::tracing::debug!("OtelShutdownGuard dropped — tracer provider shutdown");
100 }
101 Self::Inactive => {}
102 }
103 }
104}
105
106pub fn init_observability(
122 logging_config: &StructuredLoggingConfig,
123 otel_config: Option<&ObservabilityConfig>,
124 service_name: &str,
125 environment: &str,
126 log_level: &str,
127) -> Result<(LogBuffer, OtelShutdownGuard), anyhow::Error> {
128 use ::tracing_subscriber::{Registry, EnvFilter, layer::SubscriberExt};
129
130 let filter = EnvFilter::try_from_default_env()
131 .unwrap_or_else(|_| EnvFilter::new(log_level));
132
133 #[cfg(feature = "otel-tracing")]
137 let (otel_tracer, guard) = match otel_config {
138 Some(config) if config.tracing_enabled => {
139 let (tracer, provider) = crate::tracing::build_otel_tracer(config)?;
140 (Some(tracer), OtelShutdownGuard::Active(provider))
141 }
142 _ => (None, OtelShutdownGuard::Inactive),
143 };
144 #[cfg(not(feature = "otel-tracing"))]
145 let (otel_tracer, guard): (Option<()>, _) = { let _ = otel_config; (None, OtelShutdownGuard::Inactive) };
146
147 macro_rules! set_subscriber {
151 ($filter:expr, $fmt:expr, $otel_tracer:expr) => {{
152 #[cfg(feature = "otel-tracing")]
153 {
154 let otel = $otel_tracer
155 .map(|t| tracing_opentelemetry::layer().with_tracer(t));
156 let subscriber = Registry::default().with($filter).with($fmt).with(otel);
157 ::tracing::subscriber::set_global_default(subscriber)
158 .map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {e}"))?;
159 }
160 #[cfg(not(feature = "otel-tracing"))]
161 {
162 let _ = $otel_tracer;
163 let subscriber = Registry::default().with($filter).with($fmt);
164 ::tracing::subscriber::set_global_default(subscriber)
165 .map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {e}"))?;
166 }
167 }};
168 }
169
170 if logging_config.json_format {
171 let json = ::tracing_subscriber::fmt::layer()
172 .json()
173 .with_target(true)
174 .with_thread_ids(false)
175 .with_file(false)
176 .with_line_number(false)
177 .with_span_events(::tracing_subscriber::fmt::format::FmtSpan::CLOSE);
178 set_subscriber!(filter, json, otel_tracer);
179 } else {
180 let pretty = ::tracing_subscriber::fmt::layer()
181 .with_target(true)
182 .with_thread_ids(false)
183 .with_span_events(::tracing_subscriber::fmt::format::FmtSpan::CLOSE);
184 set_subscriber!(filter, pretty, otel_tracer);
185 }
186
187 let buffer_config = crate::logging::LoggingConfig {
189 json_format: logging_config.json_format,
190 include_timestamp: logging_config.include_timestamp,
191 include_hostname: logging_config.include_hostname,
192 include_spans: logging_config.include_spans,
193 extract_fields: logging_config.extract_fields.clone(),
194 buffer_size: logging_config.buffer_size,
195 };
196 let buffer = LogBuffer::new(buffer_config);
197
198 ::tracing::info!(
199 service = service_name,
200 environment = environment,
201 format = if logging_config.json_format { "json" } else { "pretty" },
202 otel = if matches!(guard, OtelShutdownGuard::Inactive) { "disabled" } else { "enabled" },
203 "Observability initialized (single subscriber)"
204 );
205
206 Ok((buffer, guard))
207}