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