1use std::path::Path;
8
9use eyre::Context as EyreContext;
10use opentelemetry::trace::TracerProvider;
11use opentelemetry_sdk::metrics::SdkMeterProvider;
12use opentelemetry_sdk::trace::SdkTracerProvider;
13use tracing::metadata::LevelFilter;
14
15use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
16use tracing_subscriber::{
17 EnvFilter, Layer, filter::FilterExt, prelude::__tracing_subscriber_SubscriberExt,
18};
19
20use tracing_subscriber::Registry;
21pub mod metrics;
22pub mod telemetry;
23
24pub fn set_up_tracing(name: &str) -> eyre::Result<()> {
30 TracingBuilder::new(name)
31 .with_stdout("warn", false)
32 .build()
33 .wrap_err(format!(
34 "failed to set tracing global subscriber for {name}"
35 ))?;
36 Ok(())
37}
38
39pub struct OtelGuard {
40 tracer_provider: SdkTracerProvider,
41 meter_provider: SdkMeterProvider,
42}
43
44#[must_use = "call `build` to finalize the tracing setup"]
45pub struct TracingBuilder {
46 name: String,
47 layers: Vec<Box<dyn Layer<Registry> + Send + Sync>>,
48 pub guard: Option<OtelGuard>,
49}
50
51impl TracingBuilder {
52 pub fn new(name: impl Into<String>) -> Self {
53 Self {
54 name: name.into(),
55 layers: Vec::new(),
56 guard: None,
57 }
58 }
59
60 pub fn with_stdout(mut self, filter: impl AsRef<str>, json: bool) -> Self {
66 let parsed = EnvFilter::builder()
67 .parse_lossy(filter)
68 .add_directive("hyper=off".parse().unwrap())
69 .add_directive("tonic=off".parse().unwrap())
70 .add_directive("h2=off".parse().unwrap())
71 .add_directive("reqwest=off".parse().unwrap());
72 let env_filter = EnvFilter::from_default_env().or(parsed);
73 let layer = tracing_subscriber::fmt::layer()
74 .compact()
75 .with_writer(std::io::stdout);
76
77 if json {
78 let layer = layer.json().with_filter(env_filter);
79 self.layers.push(layer.boxed());
80 } else {
81 let layer = layer.with_filter(env_filter);
82 self.layers.push(layer.boxed());
83 };
84 self
85 }
86
87 pub fn with_file(
89 mut self,
90 file_name: impl Into<String>,
91 filter: LevelFilter,
92 ) -> eyre::Result<Self> {
93 let file_name = file_name.into();
94 let out_dir = Path::new("out");
95 std::fs::create_dir_all(out_dir).context("failed to create `out` directory")?;
96 let path = out_dir.join(file_name).with_extension("txt");
97 let file = std::fs::OpenOptions::new()
98 .create(true)
99 .append(true)
100 .open(path)
101 .context("failed to create log file")?;
102 let layer = tracing_subscriber::fmt::layer()
103 .with_ansi(false)
104 .json()
105 .with_writer(file)
106 .with_filter(filter);
107 self.layers.push(layer.boxed());
108 Ok(self)
109 }
110
111 pub fn with_otlp_tracing(mut self) -> eyre::Result<Self> {
118 let endpoint = std::env::var("DORA_OTLP_ENDPOINT")
119 .or_else(|_| std::env::var("DORA_JAEGER_TRACING"))
120 .wrap_err("DORA_OTLP_ENDPOINT or DORA_JAEGER_TRACING environment variable not set")?;
121
122 let sdk_tracer_provider = crate::telemetry::init_tracing(&self.name, &endpoint);
124 let meter_provider = metrics::init_meter_provider();
125
126 let tracer = sdk_tracer_provider.tracer("tracing-otel-subscriber");
130
131 let guard = OtelGuard {
132 tracer_provider: sdk_tracer_provider,
133 meter_provider: meter_provider.clone(),
134 };
135
136 self.guard = Some(guard);
137 self.layers.push(MetricsLayer::new(meter_provider).boxed());
138 let filter_otel = EnvFilter::new("trace")
139 .add_directive("hyper=off".parse().unwrap())
140 .add_directive("tonic=off".parse().unwrap())
141 .add_directive("h2=off".parse().unwrap())
142 .add_directive("reqwest=off".parse().unwrap());
143 self.layers.push(
144 OpenTelemetryLayer::new(tracer)
145 .with_filter(filter_otel)
146 .boxed(),
147 );
148 Ok(self)
149 }
150
151 #[deprecated(since = "0.4.0", note = "Use `with_otlp_tracing` instead")]
153 pub fn with_jaeger_tracing(self) -> eyre::Result<Self> {
154 self.with_otlp_tracing()
155 }
156
157 pub fn add_layer<L>(mut self, layer: L) -> Self
158 where
159 L: Layer<Registry> + Send + Sync + 'static,
160 {
161 self.layers.push(layer.boxed());
162 self
163 }
164
165 pub fn with_layers<I, L>(mut self, layers: I) -> Self
166 where
167 I: IntoIterator<Item = L>,
168 L: Layer<Registry> + Send + Sync + 'static,
169 {
170 for layer in layers {
171 self.layers.push(layer.boxed());
172 }
173 self
174 }
175
176 pub fn build(self) -> eyre::Result<()> {
177 let registry = Registry::default().with(self.layers);
178
179 tracing::subscriber::set_global_default(registry).context(format!(
181 "failed to set tracing global subscriber for {}",
182 self.name
183 ))
184 }
185}
186
187impl Drop for OtelGuard {
188 fn drop(&mut self) {
189 self.meter_provider.force_flush().ok();
190 self.meter_provider.shutdown().ok();
191 self.tracer_provider.force_flush().ok();
192 self.tracer_provider.shutdown().ok();
193 }
194}