1use opentelemetry::{global, KeyValue};
2use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
3use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter, WithExportConfig};
4use opentelemetry_sdk::logs::SdkLoggerProvider;
5use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
6use opentelemetry_sdk::trace::SdkTracerProvider;
7use opentelemetry_sdk::Resource;
8use std::sync::{Arc, OnceLock};
9use std::time::Duration;
10use tracing_subscriber::filter::filter_fn;
11use tracing_subscriber::prelude::*;
12use tracing_subscriber::EnvFilter;
13
14pub use opentelemetry;
16use opentelemetry::trace::TracerProvider;
17pub use tracing;
18
19const DEFAULT_ENDPOINT: &'static str = "http://localhost:4317";
20
21pub struct LoggerContext {
22 pub logger_provider: SdkLoggerProvider,
23 pub tracer_provider: SdkTracerProvider,
24 pub meter_provider: SdkMeterProvider,
25}
26
27impl LoggerContext {
28 pub fn shudown(self) {
29 let _ = self.meter_provider.shutdown();
30 let _ = self.tracer_provider.shutdown();
31 let _ = self.logger_provider.shutdown();
32 }
33}
34
35fn get_resource(service_name: &'static str) -> Resource {
36 static RESOURCE: OnceLock<Resource> = OnceLock::new();
37 RESOURCE
38 .get_or_init(|| {
39 let mut builder = Resource::builder().with_service_name(service_name);
40
41 #[cfg(feature = "detect-host")]
42 {
43 builder =
44 builder.with_attribute(gen_host_info().expect("failed to build host info"));
45 }
46
47 builder.build()
48 })
49 .clone()
50}
51fn init_traces(endpoint: &str, service_name: &'static str) -> SdkTracerProvider {
52 let exporter = SpanExporter::builder()
53 .with_tonic()
54 .with_endpoint(endpoint)
55 .build()
56 .expect("Failed to create span exporter");
57 SdkTracerProvider::builder()
58 .with_resource(get_resource(service_name))
59 .with_batch_exporter(exporter)
60 .build()
61}
62
63fn init_metrics(endpoint: &str, service_name: &'static str) -> SdkMeterProvider {
64 let exporter = MetricExporter::builder()
65 .with_tonic()
66 .with_endpoint(endpoint)
67 .build()
68 .expect("Failed to create metric exporter");
69
70 let reader = PeriodicReader::builder(exporter)
71 .with_interval(Duration::from_secs(1))
72 .build();
73 SdkMeterProvider::builder()
74 .with_reader(reader)
75 .with_resource(get_resource(service_name))
76 .build()
77}
78
79fn init_logs(endpoint: &str, service_name: &'static str) -> SdkLoggerProvider {
80 let exporter = LogExporter::builder()
81 .with_tonic()
82 .with_endpoint(endpoint)
83 .build()
84 .expect("Failed to create log exporter");
85
86 SdkLoggerProvider::builder()
87 .with_resource(get_resource(service_name))
88 .with_batch_exporter(exporter)
89 .build()
90}
91
92fn target_in_whitelist(target: &str, whitelist: &[String]) -> bool {
93 whitelist.iter().any(|crate_name| {
94 if target == crate_name {
95 true
96 } else if target.starts_with(crate_name) {
97 target[crate_name.len()..].starts_with("::")
98 } else {
99 false
100 }
101 })
102}
103
104fn init_tracer(
105 application_name: &'static str,
106 endpoint: &str,
107 provider: SdkTracerProvider,
108 crate_whitelist: Vec<String>,
109) -> SdkLoggerProvider {
110 let logger_provider = init_logs(endpoint, application_name);
111
112 let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
114
115 let filter_otel = EnvFilter::new("info")
126 .add_directive("hyper=off".parse().unwrap())
127 .add_directive("opentelemetry=off".parse().unwrap())
128 .add_directive("tonic=off".parse().unwrap())
129 .add_directive("h2=off".parse().unwrap());
130 let otel_layer = otel_layer.with_filter(filter_otel);
131
132 let filter = EnvFilter::from_default_env()
136 .add_directive(format!("{}=info", &application_name).parse().unwrap());
137 println!(
138 "filter={} crate_whitelist={}",
139 &filter,
140 &crate_whitelist.join(",")
141 );
142 let tracer = provider.tracer("trace_demo");
143 let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
144 let fmt_layer = tracing_subscriber::fmt::layer()
145 .with_thread_names(true)
146 .with_filter(filter);
147
148 let crate_whitelist: Vec<String> = crate_whitelist
149 .into_iter()
150 .filter_map(|entry| {
151 let trimmed = entry.trim();
152 if trimmed.is_empty() {
153 None
154 } else {
155 Some(trimmed.to_string())
156 }
157 })
158 .collect();
159 let whitelist = Arc::new(crate_whitelist);
160 let whitelist_active = !whitelist.is_empty();
161 let whitelist_filter = filter_fn({
162 let whitelist = Arc::clone(&whitelist);
163 move |metadata| {
164 if whitelist_active {
165 target_in_whitelist(metadata.target(), whitelist.as_slice())
166 } else {
167 true
168 }
169 }
170 });
171 let otel_layer = otel_layer.with_filter(whitelist_filter.clone());
172 let telemetry = telemetry.with_filter(whitelist_filter.clone());
173 let fmt_layer = fmt_layer.with_filter(whitelist_filter);
174
175 tracing_subscriber::registry()
178 .with(otel_layer)
179 .with(telemetry)
180 .with(fmt_layer)
181 .init();
182 logger_provider
183}
184
185#[derive(Debug, Default)]
186pub struct LoggerConfig {
187 pub endpoint: Option<String>,
188 pub crate_whitelist: Vec<String>,
190}
191
192impl LoggerConfig {
193 pub fn builder() -> LoggerConfigBuilder {
195 LoggerConfigBuilder::default()
196 }
197}
198
199#[derive(Debug, Default)]
200pub struct LoggerConfigBuilder {
201 endpoint: Option<String>,
202 crate_whitelist: Vec<String>,
203}
204
205impl LoggerConfigBuilder {
206 #[must_use]
208 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
209 self.endpoint = Some(endpoint.into());
210 self
211 }
212
213 #[must_use]
215 pub fn add_whitelist_crate(mut self, crate_name: impl Into<String>) -> Self {
216 let name = crate_name.into();
217 if !name.trim().is_empty() {
218 self.crate_whitelist.push(name);
219 }
220 self
221 }
222
223 #[must_use]
225 pub fn add_whitelist_crates<I, S>(mut self, crates: I) -> Self
226 where
227 I: IntoIterator<Item = S>,
228 S: Into<String>,
229 {
230 for entry in crates {
231 self = self.add_whitelist_crate(entry);
232 }
233 self
234 }
235
236 pub fn build(self) -> LoggerConfig {
238 LoggerConfig {
239 endpoint: self.endpoint,
240 crate_whitelist: self.crate_whitelist,
241 }
242 }
243}
244
245pub fn init_logger(service_name: &'static str, log_config: LoggerConfig) -> LoggerContext {
246 let LoggerConfig {
247 endpoint,
248 crate_whitelist,
249 } = log_config;
250 let endpoint = endpoint
251 .as_ref()
252 .map(|v| v.as_str())
253 .unwrap_or(DEFAULT_ENDPOINT);
254 let tracer_provider = init_traces(endpoint, service_name);
256 let logger_provider = init_tracer(
257 service_name,
258 endpoint,
259 tracer_provider.clone(),
260 crate_whitelist,
261 );
262 global::set_tracer_provider(tracer_provider.clone());
263 let meter_provider = init_metrics(endpoint, service_name);
264 global::set_meter_provider(meter_provider.clone());
265 LoggerContext {
266 logger_provider,
267 tracer_provider,
268 meter_provider,
269 }
270}
271
272#[cfg(feature = "detect-host")]
273fn gen_host_info() -> Result<KeyValue, std::io::Error> {
274 hostname::get().map(|val| {
275 KeyValue::new(
276 "host.name".to_string(),
277 val.into_string().unwrap_or_else(|_| "unknown".into()),
278 )
279 })
280}
281
282#[cfg(test)]
283mod test {
284 use std::time::Duration;
285
286 use opentelemetry::{global, KeyValue};
287 use tracing::info;
288 use tracing::instrument;
289
290 use crate::{init_logger, LoggerConfig};
291
292 #[instrument]
293 fn foo() {
294 info!("test");
297 bar();
298 }
299
300 #[instrument]
301 fn bar() {
302 info!("test2");
305 }
306
307 #[tokio::test]
308 async fn test_logger() {
309 init_logger("test_logger", LoggerConfig::default());
310 for _ in 0..100 {
311 foo();
314 }
315 tokio::time::sleep(Duration::from_secs(4)).await;
316 }
317
318 #[instrument]
319 fn add_counter() {
320 let counter = global::meter("aaa").f64_counter("testCounterF64").build();
321 counter.add(10f64, &[KeyValue::new("rate", "standard")]);
322 }
323
324 #[test]
325 fn target_whitelist_matching() {
326 let whitelist = vec!["crate_a".to_string(), "crate_b".to_string()];
327 assert!(super::target_in_whitelist("crate_a", &whitelist));
328 assert!(super::target_in_whitelist(
329 "crate_a::module::sub",
330 &whitelist
331 ));
332 assert!(super::target_in_whitelist("crate_b::something", &whitelist));
333 assert!(!super::target_in_whitelist("crate", &whitelist));
334 assert!(!super::target_in_whitelist("crate_c::foo", &whitelist));
335 }
336}