1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3#[cfg(feature = "clap")]
4use clap_verbosity_flag::{InfoLevel, LogLevel, Verbosity};
5#[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
6use enumset::EnumSet;
7use std::sync::LazyLock;
8use tracing::Level;
9use tracing_indicatif::{
10 IndicatifLayer, IndicatifWriter, filter::IndicatifFilter, style::ProgressStyle,
11 util::FilteredFormatFields, writer,
12};
13use tracing_subscriber::{
14 EnvFilter, Layer, Registry,
15 layer::{Identity, SubscriberExt},
16 util::SubscriberInitExt as _,
17};
18
19#[cfg(feature = "otlp")]
20use opentelemetry_sdk::{
21 Resource, propagation::TraceContextPropagator, resource::SdkProvidedResourceDetector,
22};
23#[cfg(feature = "tracy")]
24use tracing_tracy::TracyLayer;
25
26pub mod propagate;
27
28pub static PB_PROGRESS_STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
30 ProgressStyle::with_template(
31 "{span_child_prefix} {wide_msg} {bar:10} ({elapsed}) {pos:>7}/{len:7}",
32 )
33 .expect("invalid progress template")
34});
35
36pub static PB_TRANSFER_STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
38 ProgressStyle::with_template(
39 "{span_child_prefix} {wide_msg} {binary_bytes:>7}/{binary_total_bytes:7}@{decimal_bytes_per_sec} ({elapsed}) {bar:10} "
40 )
41 .expect("invalid progress template")
42});
43pub static PB_SPINNER_STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
44 ProgressStyle::with_template(
45 "{span_child_prefix}{spinner} {wide_msg} ({elapsed}) {pos:>7}/{len:7}",
46 )
47 .expect("invalid progress template")
48});
49
50pub static PB_SPINNER_LONG_STYLE: LazyLock<ProgressStyle> = LazyLock::new(|| {
53 ProgressStyle::with_template("{span_child_prefix}{spinner} {wide_msg} {pos:>7}/?")
54 .expect("invalid progress template")
55});
56
57#[derive(thiserror::Error, Debug)]
58pub enum Error {
59 #[error(transparent)]
60 Init(#[from] tracing_subscriber::util::TryInitError),
61
62 #[cfg(feature = "otlp")]
63 #[error(transparent)]
64 OTEL(#[from] opentelemetry_sdk::error::OTelSdkError),
65}
66
67#[derive(Clone)]
68pub struct TracingHandle {
69 stdout_writer: IndicatifWriter<writer::Stdout>,
70 stderr_writer: IndicatifWriter<writer::Stderr>,
71
72 #[cfg(feature = "chrome")]
73 #[allow(dead_code)]
74 chrome_guard: Option<std::rc::Rc<tracing_chrome::FlushGuard>>,
75
76 #[cfg(feature = "otlp")]
77 meter_provider: Option<opentelemetry_sdk::metrics::SdkMeterProvider>,
78
79 #[cfg(feature = "otlp")]
80 tracer_provider: Option<opentelemetry_sdk::trace::SdkTracerProvider>,
81}
82
83impl TracingHandle {
84 pub fn get_stdout_writer(&self) -> IndicatifWriter<writer::Stdout> {
89 self.stdout_writer.clone()
91 }
92
93 pub fn get_stderr_writer(&self) -> IndicatifWriter<writer::Stderr> {
98 self.stderr_writer.clone()
100 }
101
102 pub async fn flush(&self) -> Result<(), Error> {
107 #[cfg(feature = "otlp")]
108 {
109 if let Some(tracer_provider) = &self.tracer_provider {
110 tracer_provider.force_flush()?;
111 }
112 if let Some(meter_provider) = &self.meter_provider {
113 meter_provider.force_flush()?;
114 }
115 }
116 Ok(())
117 }
118
119 pub async fn shutdown(&mut self) -> Result<(), Error> {
124 self.flush().await?;
125 #[cfg(feature = "otlp")]
126 {
127 use tokio::task::spawn_blocking;
128 if let Some(tracer_provider) = self.tracer_provider.take() {
129 spawn_blocking(move || tracer_provider.shutdown())
130 .await
131 .map_err(|err| {
132 Error::OTEL(opentelemetry_sdk::error::OTelSdkError::InternalFailure(
133 err.to_string(),
134 ))
135 })??;
136 }
137 if let Some(meter_provider) = self.meter_provider.take() {
138 spawn_blocking(move || meter_provider.shutdown())
139 .await
140 .map_err(|err| {
141 Error::OTEL(opentelemetry_sdk::error::OTelSdkError::InternalFailure(
142 err.to_string(),
143 ))
144 })??;
145 }
146 }
147 #[cfg(feature = "tracy")]
148 {
149 if tracing_tracy::client::Client::is_running() {
150 unsafe { tracing_tracy::client::sys::___tracy_shutdown_profiler() }
151 }
152 }
153
154 Ok(())
155 }
156}
157
158#[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
159#[derive(enumset::EnumSetType, Debug)]
160#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
161pub enum Tracer {
162 #[cfg(feature = "otlp")]
163 Otlp,
164 #[cfg(feature = "tracy")]
165 Tracy,
166 #[cfg(feature = "chrome")]
167 ChromeStyle,
168}
169
170#[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
171impl Tracer {
172 pub fn as_str(&self) -> &'static str {
174 match self {
175 #[cfg(feature = "otlp")]
176 Tracer::Otlp => "otlp",
177 #[cfg(feature = "tracy")]
178 Tracer::Tracy => "tracy",
179 #[cfg(feature = "chrome")]
180 Tracer::ChromeStyle => "chrome-style",
181 }
182 }
183}
184
185#[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
186impl std::fmt::Display for Tracer {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 f.write_str(self.as_str())
189 }
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
194enum ChosenLevel {
195 Unset(Level),
197 NoOutput,
199 Level(Level),
201}
202
203#[must_use = "Don't forget to call build() to enable tracing."]
204pub struct TracingBuilder {
205 disable_progress_bars: bool,
208
209 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
210 tracers: EnumSet<Tracer>,
211
212 level: ChosenLevel,
214}
215
216impl Default for TracingBuilder {
217 fn default() -> Self {
218 Self {
219 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
220 tracers: Default::default(),
221 level: ChosenLevel::Unset(Level::INFO),
222 disable_progress_bars: false,
223 }
224 }
225}
226
227impl TracingBuilder {
228 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
229 pub fn enable_tracer(mut self, tracer: Tracer) -> TracingBuilder {
231 self.tracers.insert(tracer);
232 self
233 }
234
235 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
236 pub fn enable_tracers<I>(mut self, tracers: I) -> TracingBuilder
238 where
239 I: IntoIterator<Item = Tracer>,
240 {
241 self.tracers.extend(tracers);
242 self
243 }
244
245 pub fn disable_progress_bars(mut self) -> TracingBuilder {
247 self.disable_progress_bars = true;
248 self
249 }
250
251 pub fn build(self) -> Result<TracingHandle, Error> {
258 self.build_with_additional(Identity::new())
259 }
260
261 pub fn build_with_additional<L>(self, additional_layer: L) -> Result<TracingHandle, Error>
276 where
277 L: Layer<Registry> + Send + Sync + 'static,
278 {
279 let indicatif_layer = IndicatifLayer::new().with_progress_style(PB_SPINNER_STYLE.clone());
281 let stdout_writer = indicatif_layer.get_stdout_writer();
282 let stderr_writer = indicatif_layer.get_stderr_writer();
283
284 let layered = tracing_subscriber::fmt::Layer::new()
285 .fmt_fields(FilteredFormatFields::new(
286 tracing_subscriber::fmt::format::DefaultFields::new(),
287 |field| field.name() != "indicatif.pb_show",
288 ))
289 .with_writer(indicatif_layer.get_stderr_writer())
290 .compact()
291 .with_filter(construct_filter(self.level.to_owned()))
292 .and_then((!self.disable_progress_bars).then(|| {
293 indicatif_layer.with_filter(
294 IndicatifFilter::new(false),
296 )
297 }));
298
299 #[cfg(feature = "chrome")]
300 let (layered, chrome_guard) = if self.tracers.contains(Tracer::ChromeStyle) {
301 let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
302 .include_args(true)
303 .trace_style(tracing_chrome::TraceStyle::Async)
304 .build();
305 (
306 Layer::and_then(layered, Some(chrome_layer)),
307 Some(std::rc::Rc::new(guard)),
308 )
309 } else {
310 (Layer::and_then(layered, None), None)
311 };
312
313 #[cfg(feature = "otlp")]
314 let mut g_tracer_provider = None;
315 #[cfg(feature = "otlp")]
316 let mut g_meter_provider = None;
317
318 #[cfg(feature = "otlp")]
320 let layered = Layer::and_then(layered, {
321 self.tracers.contains(Tracer::Otlp).then(|| {
322 use opentelemetry::trace::TracerProvider;
323
324 opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
326
327 let tracer_provider =
328 gen_tracer_provider().expect("Unable to configure trace provider");
329
330 let meter_provider =
331 gen_meter_provider().expect("Unable to configure meter provider");
332
333 opentelemetry::global::set_meter_provider(meter_provider.clone());
336
337 g_tracer_provider = Some(tracer_provider.clone());
338 g_meter_provider = Some(meter_provider);
339
340 tracing_opentelemetry::layer().with_tracer(tracer_provider.tracer("snix"))
342 })
343 });
344
345 #[cfg(feature = "tracy")]
346 let layered = Layer::and_then(
347 layered,
348 self.tracers.contains(Tracer::Tracy).then(|| {
349 let _client = tracing_tracy::client::Client::start();
350 TracyLayer::default()
351 }),
352 );
353
354 tracing_subscriber::registry()
355 .with(additional_layer)
358 .with(layered)
359 .try_init()?;
360
361 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
362 if !self.tracers.is_empty() {
363 let tracers = std::fmt::from_fn(|f| {
364 for (idx, tracer) in self.tracers.iter().enumerate() {
365 if idx > 0 {
366 f.write_str(",")?;
367 }
368 write!(f, "{tracer}")?;
369 }
370 Ok(())
371 });
372 tracing::debug!(%tracers, "started tracing");
373 }
374 Ok(TracingHandle {
375 stdout_writer,
376 stderr_writer,
377
378 #[cfg(feature = "otlp")]
379 meter_provider: g_meter_provider,
380 #[cfg(feature = "otlp")]
381 tracer_provider: g_tracer_provider,
382 #[cfg(feature = "chrome")]
383 chrome_guard,
384 })
385 }
386
387 #[cfg(feature = "clap")]
388 pub fn handle_verbosity_flags<L: LogLevel>(mut self, args: &Verbosity<L>) -> Self {
390 if args.is_silent() {
391 self.level = ChosenLevel::NoOutput;
392 self.disable_progress_bars = true;
393 return self;
394 }
395
396 use std::io::IsTerminal;
397 if !std::io::stderr().is_terminal() {
398 self.disable_progress_bars = true
399 }
400
401 if args.is_present() {
402 self.level = ChosenLevel::Level(args.tracing_level().expect("not silent"));
403 } else {
404 self.level = ChosenLevel::Unset(args.tracing_level().expect("not silent"))
405 }
406
407 self
408 }
409
410 #[cfg(feature = "clap")]
411 pub fn handle_tracing_args<L: LogLevel>(
413 #[allow(unused_mut)] mut self,
414 args: &TracingArgs<L>,
415 ) -> Self {
416 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
417 {
418 self = self.enable_tracers(args.tracers());
419 }
420
421 self.handle_verbosity_flags(&args.verbosity)
422 }
423}
424
425#[cfg(feature = "otlp")]
426fn gen_resources() -> Resource {
427 Resource::builder()
429 .with_detector(Box::new(SdkProvidedResourceDetector))
430 .build()
431}
432
433#[cfg(feature = "otlp")]
436fn gen_tracer_provider()
437-> Result<opentelemetry_sdk::trace::SdkTracerProvider, opentelemetry_otlp::ExporterBuildError> {
438 use opentelemetry_otlp::{ExportConfig, SpanExporter, WithExportConfig};
439
440 let exporter = SpanExporter::builder()
441 .with_tonic()
442 .with_export_config(ExportConfig::default())
443 .build()?;
444
445 let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
446 .with_batch_exporter(exporter)
447 .with_resource(gen_resources())
448 .build();
449 Ok(tracer_provider)
469}
470
471const _OTEL_METRIC_EXPORT_INTERVAL: std::time::Duration = std::time::Duration::from_secs(10);
478
479#[cfg(feature = "otlp")]
480fn gen_meter_provider()
481-> Result<opentelemetry_sdk::metrics::SdkMeterProvider, opentelemetry_otlp::ExporterBuildError> {
482 use std::time::Duration;
483
484 use opentelemetry_otlp::WithExportConfig;
485 use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
486 let exporter = opentelemetry_otlp::MetricExporter::builder()
487 .with_tonic()
488 .with_timeout(Duration::from_secs(10))
489 .build()?;
490
491 let reader = PeriodicReader::builder(exporter)
492 .with_interval(_OTEL_METRIC_EXPORT_INTERVAL)
493 .build();
494
495 Ok(SdkMeterProvider::builder()
496 .with_reader(reader)
497 .with_resource(gen_resources())
498 .build())
499}
500
501#[cfg(all(
507 feature = "clap",
508 any(feature = "otlp", feature = "tracy", feature = "chrome")
509))]
510#[derive(Clone, Debug, Default)]
511struct TracersValueParser(clap::builder::EnumValueParser<Tracer>);
512#[cfg(all(
513 feature = "clap",
514 any(feature = "otlp", feature = "tracy", feature = "chrome")
515))]
516impl clap::builder::TypedValueParser for TracersValueParser {
517 type Value = EnumSet<Tracer>;
518
519 fn parse_ref(
520 &self,
521 cmd: &clap::Command,
522 arg: Option<&clap::Arg>,
523 value: &std::ffi::OsStr,
524 ) -> Result<Self::Value, clap::Error> {
525 if value.is_empty() {
526 return Ok(EnumSet::empty());
527 }
528 self.0.parse_ref(cmd, arg, value).map(EnumSet::only)
529 }
530
531 fn possible_values(
532 &self,
533 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
534 self.0.possible_values()
535 }
536}
537
538#[cfg(feature = "clap")]
539#[derive(clap::Parser, Clone)]
540pub struct TracingArgs<L: LogLevel = InfoLevel> {
541 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
542 #[arg(long, action(clap::ArgAction::Append), env, value_parser=TracersValueParser::default(), value_delimiter=',')]
544 tracer: Vec<EnumSet<Tracer>>,
545
546 #[clap(flatten)]
547 verbosity: Verbosity<L>,
548}
549
550#[cfg(feature = "clap")]
551impl<L: LogLevel> TracingArgs<L> {
552 #[cfg(any(feature = "otlp", feature = "tracy", feature = "chrome"))]
553 pub fn tracers(&self) -> EnumSet<Tracer> {
554 self.tracer
555 .iter()
556 .cloned()
557 .fold(EnumSet::empty(), |ret, next| ret.union(next))
558 }
559}
560
561fn construct_filter<S>(level: ChosenLevel) -> impl tracing_subscriber::layer::Filter<S> {
563 let mut b = EnvFilter::builder();
564 if let ChosenLevel::Unset(level) = level {
565 b = b.with_default_directive(level.to_owned().into());
566 }
567 let mut f = b.from_env().expect("invalid RUST_LOG");
568 if let ChosenLevel::Level(level) = level {
569 f = f.add_directive(level.to_owned().into());
570 }
571 f
572}