Skip to main content

moq_native/
log.rs

1use anyhow::Context;
2use serde::{Deserialize, Serialize};
3use serde_with::DisplayFromStr;
4use tracing::Level;
5use tracing::level_filters::LevelFilter;
6use tracing_subscriber::EnvFilter;
7use tracing_subscriber::Layer;
8use tracing_subscriber::layer::SubscriberExt;
9use tracing_subscriber::util::SubscriberInitExt;
10
11/// Tracing log configuration.
12#[serde_with::serde_as]
13#[derive(Clone, clap::Parser, Serialize, Deserialize, Debug)]
14#[serde(deny_unknown_fields, default)]
15#[non_exhaustive]
16pub struct Log {
17	/// The level filter to use.
18	#[serde_as(as = "DisplayFromStr")]
19	#[arg(id = "log-level", long = "log-level", default_value = "info", env = "MOQ_LOG_LEVEL")]
20	pub level: Level,
21}
22
23impl Default for Log {
24	fn default() -> Self {
25		Self { level: Level::INFO }
26	}
27}
28
29impl Log {
30	pub fn new(level: Level) -> Self {
31		Self { level }
32	}
33
34	pub fn level(&self) -> LevelFilter {
35		LevelFilter::from_level(self.level)
36	}
37
38	pub fn init(&self) -> anyhow::Result<()> {
39		let filter = EnvFilter::builder()
40			.with_default_directive(self.level().into()) // Default to our -q/-v args
41			.from_env_lossy() // Allow overriding with RUST_LOG
42			.add_directive("h2=warn".parse()?)
43			.add_directive("quinn=info".parse()?)
44			.add_directive("tungstenite=info".parse()?)
45			.add_directive("rustls=info".parse()?)
46			.add_directive("tracing::span=off".parse()?)
47			.add_directive("tracing::span::active=off".parse()?)
48			.add_directive("tokio=info".parse()?)
49			.add_directive("runtime=info".parse()?);
50
51		let registry = tracing_subscriber::registry();
52
53		// On Android, route logs to logcat so they can be inspected via ADB/Android Studio.
54		// Everywhere else, format to stderr.
55		#[cfg(all(target_os = "android", feature = "android-logcat"))]
56		let registry = {
57			let logcat_layer = tracing_android::layer("MoQNative")
58				.context("failed to initialize Android logcat layer")?
59				.with_filter(filter);
60			registry.with(logcat_layer)
61		};
62
63		#[cfg(not(all(target_os = "android", feature = "android-logcat")))]
64		let registry = {
65			let fmt_layer = tracing_subscriber::fmt::layer()
66				.with_writer(std::io::stderr)
67				.with_filter(filter);
68			registry.with(fmt_layer)
69		};
70
71		#[cfg(feature = "tokio-console")]
72		let registry = registry.with(console_subscriber::spawn());
73
74		registry.try_init().context("failed to set global tracing subscriber")?;
75
76		// Start deadlock detection thread (only in debug builds)
77		#[cfg(debug_assertions)]
78		std::thread::spawn(Self::deadlock_detector);
79
80		Ok(())
81	}
82
83	#[cfg(debug_assertions)]
84	fn deadlock_detector() {
85		loop {
86			std::thread::sleep(std::time::Duration::from_secs(1));
87
88			let deadlocks = parking_lot::deadlock::check_deadlock();
89			if deadlocks.is_empty() {
90				continue;
91			}
92
93			tracing::error!("DEADLOCK DETECTED");
94
95			for (i, threads) in deadlocks.iter().enumerate() {
96				tracing::error!("Deadlock #{}", i);
97				for t in threads {
98					tracing::error!("Thread Id {:#?}", t.thread_id());
99					tracing::error!("{:#?}", t.backtrace());
100				}
101			}
102
103			// Optionally: std::process::abort() to get a core dump
104		}
105	}
106}