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::OnceLock;
9use std::time::Duration;
10use tracing_subscriber::prelude::*;
11use tracing_subscriber::EnvFilter;
12
13pub use opentelemetry;
15use opentelemetry::trace::TracerProvider;
16pub use tracing;
17
18const DEFAULT_ENDPOINT: &'static str = "http://localhost:4317";
19
20pub struct LoggerContext {
21 pub logger_provider: SdkLoggerProvider,
22 pub tracer_provider: SdkTracerProvider,
23 pub meter_provider: SdkMeterProvider,
24}
25
26impl LoggerContext {
27 pub fn shudown(self) {
28 let _ = self.meter_provider.shutdown();
29 let _ = self.tracer_provider.shutdown();
30 let _ = self.logger_provider.shutdown();
31 }
32}
33
34fn get_resource(service_name: &'static str) -> Resource {
35 static RESOURCE: OnceLock<Resource> = OnceLock::new();
36 RESOURCE
37 .get_or_init(|| {
38 let mut builder = Resource::builder().with_service_name(service_name);
39
40 #[cfg(feature = "detect-host")]
41 {
42 builder =
43 builder.with_attribute(gen_host_info().expect("failed to build host info"));
44 }
45
46 builder.build()
47 })
48 .clone()
49}
50fn init_traces(endpoint: &str, service_name: &'static str) -> SdkTracerProvider {
51 let exporter = SpanExporter::builder()
52 .with_tonic()
53 .with_endpoint(endpoint)
54 .build()
55 .expect("Failed to create span exporter");
56 SdkTracerProvider::builder()
57 .with_resource(get_resource(service_name))
58 .with_batch_exporter(exporter)
59 .build()
60}
61
62fn init_metrics(endpoint: &str, service_name: &'static str) -> SdkMeterProvider {
63 let exporter = MetricExporter::builder()
64 .with_tonic()
65 .with_endpoint(endpoint)
66 .build()
67 .expect("Failed to create metric exporter");
68
69 let reader = PeriodicReader::builder(exporter)
70 .with_interval(Duration::from_secs(1))
71 .build();
72 SdkMeterProvider::builder()
73 .with_reader(reader)
74 .with_resource(get_resource(service_name))
75 .build()
76}
77
78fn init_logs(endpoint: &str, service_name: &'static str) -> SdkLoggerProvider {
79 let exporter = LogExporter::builder()
80 .with_tonic()
81 .with_endpoint(endpoint)
82 .build()
83 .expect("Failed to create log exporter");
84
85 SdkLoggerProvider::builder()
86 .with_resource(get_resource(service_name))
87 .with_batch_exporter(exporter)
88 .build()
89}
90
91fn init_tracer(
92 application_name: &'static str,
93 endpoint: &str,
94 provider: SdkTracerProvider,
95) -> SdkLoggerProvider {
96 let logger_provider = init_logs(endpoint, application_name);
97
98 let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
100
101 let filter_otel = EnvFilter::new("info")
112 .add_directive("hyper=off".parse().unwrap())
113 .add_directive("opentelemetry=off".parse().unwrap())
114 .add_directive("tonic=off".parse().unwrap())
115 .add_directive("h2=off".parse().unwrap());
116 let otel_layer = otel_layer.with_filter(filter_otel);
117
118 let filter = EnvFilter::from_default_env()
122 .add_directive(format!("{}=info", &application_name).parse().unwrap());
123 println!("filter={}", &filter);
124 let tracer = provider.tracer("trace_demo");
125 let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
126 let fmt_layer = tracing_subscriber::fmt::layer()
127 .with_thread_names(true)
128 .with_filter(filter);
129
130 tracing_subscriber::registry()
133 .with(otel_layer)
134 .with(telemetry)
135 .with(fmt_layer)
136 .init();
137 logger_provider
138}
139
140#[derive(Debug, Default)]
141pub struct LoggerConfig {
142 pub endpoint: Option<String>,
143}
144
145pub fn init_logger(service_name: &'static str, log_config: LoggerConfig) -> LoggerContext {
146 let endpoint = log_config
147 .endpoint
148 .as_ref()
149 .map(|v| v.as_str())
150 .unwrap_or(DEFAULT_ENDPOINT);
151 let tracer_provider = init_traces(endpoint, service_name);
153 let logger_provider = init_tracer(service_name, endpoint, tracer_provider.clone());
154 global::set_tracer_provider(tracer_provider.clone());
155 let meter_provider = init_metrics(endpoint, service_name);
156 global::set_meter_provider(meter_provider.clone());
157 LoggerContext {
158 logger_provider,
159 tracer_provider,
160 meter_provider,
161 }
162}
163
164#[cfg(feature = "detect-host")]
165fn gen_host_info() -> Result<KeyValue, std::io::Error> {
166 hostname::get().map(|val| {
167 KeyValue::new(
168 "host.name".to_string(),
169 val.into_string().unwrap_or_else(|_| "unknown".into()),
170 )
171 })
172}
173
174#[cfg(test)]
175mod test {
176 use std::time::Duration;
177
178 use opentelemetry::{global, KeyValue};
179 use tracing::info;
180 use tracing::instrument;
181
182 use crate::{init_logger, LoggerConfig};
183
184 #[instrument]
185 fn foo() {
186 info!("test");
189 bar();
190 }
191
192 #[instrument]
193 fn bar() {
194 info!("test2");
197 }
198
199 #[tokio::test]
200 async fn test_logger() {
201 init_logger("test_logger", LoggerConfig::default());
202 for _ in 0..100 {
203 foo();
206 }
207 tokio::time::sleep(Duration::from_secs(4)).await;
208 }
209
210 #[instrument]
211 fn add_counter() {
212 let counter = global::meter("aaa").f64_counter("testCounterF64").build();
213 counter.add(10f64, &[KeyValue::new("rate", "standard")]);
214 }
215}