Skip to main content

moq_native/
log.rs

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