Skip to main content

re_log/
lib.rs

1//! Text logging (nothing to do with rerun logging) for use in rerun libraries.
2//!
3//! Provides helpers for adding multiple loggers,
4//! and for setting up logging on native and on web.
5//!
6//! * `trace`: spammy things
7//! * `debug`: things that might be useful when debugging
8//! * `info`: things that we want to show to users
9//! * `warn`: problems that we can recover from
10//! * `error`: problems that lead to loss of functionality or data
11//!
12//! The `warn_once` etc macros are for when you want to suppress repeated
13//! logging of the exact same message.
14//!
15//! In the viewer these logs, if >= info, become notifications. See
16//! `re_ui::notifications` for more information.
17
18#[cfg(feature = "setup")]
19mod channel_logger;
20mod debug_assert;
21#[cfg(feature = "setup")]
22mod event_visitor;
23mod result_extensions;
24#[cfg(feature = "setup")]
25mod setup;
26#[cfg(feature = "setup")]
27pub use channel_logger::{LogMsg, Receiver, Sender, add_log_msg_receiver};
28#[cfg(feature = "setup")]
29pub use event_visitor::FieldValue;
30
31pub use tracing::Level;
32#[cfg(feature = "setup")]
33pub use tracing_subscriber::filter::LevelFilter;
34// The `re_log::info_once!(…)` etc are nice helpers, but the `log-once` crate is a bit lacking.
35// In the future we should implement our own macros to de-duplicate based on the callsite,
36// similar to how the log console in a browser will automatically suppress duplicates.
37pub use log_once::{debug_once, error_once, info_once, trace_once, warn_once};
38pub use result_extensions::ResultExt;
39#[cfg(all(feature = "setup", not(target_arch = "wasm32")))]
40pub use setup::PanicOnWarnScope;
41#[cfg(feature = "setup")]
42pub use setup::{setup_logging, setup_logging_with_filter};
43// The tracing macros support more syntax features than the log, that's why we use them:
44pub use tracing::{debug, error, info, trace, warn};
45
46/// Log once at the given [`Level`].
47#[macro_export]
48macro_rules! log_once {
49    ($level:expr, $($arg:tt)+) => {
50        match $level {
51            $crate::Level::ERROR => $crate::error_once!($($arg)+),
52            $crate::Level::WARN => $crate::warn_once!($($arg)+),
53            $crate::Level::INFO => $crate::info_once!($($arg)+),
54            $crate::Level::DEBUG => $crate::debug_once!($($arg)+),
55            $crate::Level::TRACE => $crate::trace_once!($($arg)+),
56        }
57    };
58}
59
60/// Log a warning in debug builds, or a debug message in release builds.
61///
62/// This is useful for logging messages that should be visible during development
63/// (to help catch issues), but shouldn't spam the logs in release builds.
64///
65/// In debug builds, the message is prefixed with "DEBUG: " and logged at WARN level.
66/// In release builds, the message is logged at DEBUG level without any prefix.
67#[cfg(debug_assertions)]
68#[macro_export]
69macro_rules! debug_warn {
70    ($($arg:tt)+) => {
71        $crate::warn!("DEBUG: {}", format_args!($($arg)+))
72    };
73}
74
75/// Log a warning in debug builds, or a debug message in release builds.
76///
77/// This is useful for logging messages that should be visible during development
78/// (to help catch issues), but shouldn't spam the logs in release builds.
79///
80/// In debug builds, the message is prefixed with "DEBUG: " and logged at WARN level.
81/// In release builds, the message is logged at DEBUG level without any prefix.
82#[cfg(not(debug_assertions))]
83#[macro_export]
84macro_rules! debug_warn {
85    ($($arg:tt)+) => {
86        $crate::debug!($($arg)+)
87    };
88}
89
90/// Like [`debug_warn!`], but only logs once per call site.
91///
92/// This is useful for logging messages that should be visible during development
93/// (to help catch issues), but shouldn't spam the logs in release builds.
94///
95/// In debug builds, the message is prefixed with "DEBUG: " and logged at WARN level.
96/// In release builds, the message is logged at DEBUG level without any prefix.
97#[cfg(debug_assertions)]
98#[macro_export]
99macro_rules! debug_warn_once {
100    ($($arg:tt)+) => {
101        $crate::warn_once!("DEBUG: {}", format_args!($($arg)+))
102    };
103}
104
105/// Like [`debug_warn!`], but only logs once per call site.
106///
107/// This is useful for logging messages that should be visible during development
108/// (to help catch issues), but shouldn't spam the logs in release builds.
109///
110/// In debug builds, the message is prefixed with "DEBUG: " and logged at WARN level.
111/// In release builds, the message is logged at DEBUG level without any prefix.
112#[cfg(not(debug_assertions))]
113#[macro_export]
114macro_rules! debug_warn_once {
115    ($($arg:tt)+) => {
116        $crate::debug_once!($($arg)+)
117    };
118}
119
120/// Re-exports of other crates.
121pub mod external {
122    pub use log;
123}
124
125/// Never log anything less serious than a `ERROR` from these crates.
126const CRATES_AT_ERROR_LEVEL: &[&str] = &[
127    // silence rustls in release mode: https://github.com/rerun-io/rerun/issues/3104
128    #[cfg(not(debug_assertions))]
129    "rustls",
130];
131
132/// Never log anything less serious than a `WARN` from these crates.
133const CRATES_AT_WARN_LEVEL: &[&str] = &[
134    // wgpu crates spam a lot on info level, which is really annoying
135    // TODO(emilk): remove once https://github.com/gfx-rs/wgpu/issues/3206 is fixed
136    "naga",
137    "tracing",
138    "wgpu_core",
139    "wgpu_hal",
140    "zbus",
141];
142
143/// Never log anything less serious than a `INFO` from these crates.
144///
145/// These creates are quite spammy on debug, drowning out what we care about:
146const CRATES_AT_INFO_LEVEL: &[&str] = &[
147    "datafusion_optimizer",
148    "datafusion",
149    "h2",
150    "hyper",
151    "opentelemetry", // Spams about NoopMeterProvider
152    "prost_build",
153    "reqwest", // Spams "starting new connection: …"
154    "sqlparser",
155    "tonic_web",
156    "tower",
157    "ureq",
158    // only let rustls log in debug mode: https://github.com/rerun-io/rerun/issues/3104
159    #[cfg(debug_assertions)]
160    "rustls",
161    // walkers generates noise around tile download, see https://github.com/podusowski/walkers/issues/199
162    "walkers",
163    // winit 0.30.5 spams about `set_cursor_visible` calls. It's gone on winit master, so hopefully gone in next winit release.
164    "winit",
165];
166
167/// Determines the default log filter.
168///
169/// Native: Get `RUST_LOG` environment variable or `info`, if not set.
170/// Also sets some other log levels on crates that are too loud.
171///
172/// Web: `debug` since web console allows arbitrary filtering.
173#[cfg(not(target_arch = "wasm32"))]
174pub fn default_log_filter() -> String {
175    let base_log_filter = if cfg!(debug_assertions) {
176        // We want the DEBUG level to be useful yet not too spammy.
177        // This is a good way to enforce that.
178        "debug"
179    } else {
180        // Important to keep the default at (at least) "info",
181        // as we print crucial information at INFO,
182        // e.g. the ip:port when hosting a server with `rerun-cli`.
183        "info"
184    };
185    log_filter_from_env_or_default(base_log_filter)
186}
187
188/// Determines the default log filter.
189///
190/// Native: Get `RUST_LOG` environment variable or `info`, if not set.
191/// Also sets some other log levels on crates that are too loud.
192///
193/// Web: `debug` since web console allows arbitrary filtering.
194#[cfg(target_arch = "wasm32")]
195pub fn default_log_filter() -> String {
196    "debug".to_owned()
197}
198
199/// Determines the log filter from the `RUST_LOG` environment variable or an explicit default.
200///
201/// Always adds builtin filters as well.
202#[cfg(not(target_arch = "wasm32"))]
203pub fn log_filter_from_env_or_default(default_base_log_filter: &str) -> String {
204    let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| default_base_log_filter.to_owned());
205    add_builtin_log_filter(&rust_log)
206}
207
208/// Adds builtin log level filters for crates that are too verbose.
209#[cfg(not(target_arch = "wasm32"))]
210fn add_builtin_log_filter(base_log_filter: &str) -> String {
211    use std::fmt::Write as _;
212
213    let mut rust_log = base_log_filter.to_lowercase();
214
215    if base_log_filter != "off" {
216        // If base level is `off`, don't opt-in to anything.
217
218        for crate_name in crate::CRATES_AT_ERROR_LEVEL {
219            if !rust_log.contains(&format!("{crate_name}=")) {
220                write!(rust_log, ",{crate_name}=error").ok();
221            }
222        }
223
224        if base_log_filter != "error" {
225            // If base level is `error`, don't opt-in to `warn` or `info`.
226
227            for crate_name in crate::CRATES_AT_WARN_LEVEL {
228                if !rust_log.contains(&format!("{crate_name}=")) {
229                    write!(rust_log, ",{crate_name}=warn").ok();
230                }
231            }
232
233            if base_log_filter != "warn" {
234                // If base level is not `error`/`warn`, don't opt-in to `info`.
235
236                for crate_name in crate::CRATES_AT_INFO_LEVEL {
237                    if !rust_log.contains(&format!("{crate_name}=")) {
238                        write!(rust_log, ",{crate_name}=info").ok();
239                    }
240                }
241            }
242        }
243    }
244
245    //TODO(#8077): should be removed as soon as the upstream issue is resolved
246    rust_log += ",walkers::download=off";
247
248    rust_log
249}
250
251/// Should we log this message given the filter?
252#[cfg(feature = "setup")]
253fn is_log_enabled(
254    filter: tracing_subscriber::filter::LevelFilter,
255    metadata: &tracing::Metadata<'_>,
256) -> bool {
257    if CRATES_AT_ERROR_LEVEL
258        .iter()
259        .any(|crate_name| metadata.target().starts_with(crate_name))
260    {
261        *metadata.level() <= tracing_subscriber::filter::LevelFilter::ERROR
262    } else if CRATES_AT_WARN_LEVEL
263        .iter()
264        .any(|crate_name| metadata.target().starts_with(crate_name))
265    {
266        *metadata.level() <= tracing_subscriber::filter::LevelFilter::WARN
267    } else if CRATES_AT_INFO_LEVEL
268        .iter()
269        .any(|crate_name| metadata.target().starts_with(crate_name))
270    {
271        *metadata.level() <= tracing_subscriber::filter::LevelFilter::INFO
272    } else {
273        *metadata.level() <= filter
274    }
275}
276
277/// Check if an environment variable is set to a truthy value.
278///
279/// Returns `true` if the environment variable is set to "1/true/yes/on" (case-insensitive).
280/// Returns `false` if the environment variable is set to "0/false/no/off" (case-insensitive).
281/// Otherwise returns `None`.
282///
283/// # Example
284///
285/// ```ignore
286/// if env_var_flag("TELEMETRY_ENABLED") == Some(true) {
287///     // enable telemetry
288/// }
289/// ```
290pub fn env_var_flag(var_name: &str) -> Option<bool> {
291    match std::env::var(var_name)
292        .ok()?
293        .trim()
294        .to_ascii_lowercase()
295        .as_str()
296    {
297        "" => None,
298        "0" | "false" | "no" | "off" => Some(false),
299        "1" | "true" | "yes" | "on" => Some(true),
300        value => {
301            crate::warn_once!(
302                "Ignoring unrecognized value {value:?} for environment variable {var_name:?} \
303                    (expected one of: 1/true/yes/on, 0/false/no/off); falling back to the default."
304            );
305            None
306        }
307    }
308}
309
310/// Check if an environment variable is set to a truthy value.
311///
312/// Returns `true` if the environment variable is set to "1/true/yes/on" (case-insensitive).
313/// Otherwise returns `false`.
314///
315/// # Example
316///
317/// ```ignore
318/// if env_var_is_truthy("TELEMETRY_ENABLED") {
319///     // enable telemetry
320/// }
321/// ```
322pub fn env_var_is_truthy(var_name: &str) -> bool {
323    env_var_flag(var_name).unwrap_or(false)
324}
325
326/// Is `RERUN_VERY_STRICT` set to a truthy value?
327///
328/// In very strict mode, Rerun may panic anywhere, at any time, for any reason whenever it
329/// detects something it does not like — e.g. out-of-order chunks, unsorted timelines,
330/// or other invariant violations. Very strict mode is meant for development, testing and
331/// CI, never for production: enable it to catch silent corruption early.
332///
333/// The result is cached on the first call, so subsequent calls are very cheap and
334/// changing the environment variable at runtime has no effect.
335pub fn is_rerun_very_strict() -> bool {
336    static VERY_STRICT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
337    *VERY_STRICT.get_or_init(|| env_var_is_truthy("RERUN_VERY_STRICT"))
338}
339
340/// Shorten a path to a Rust source file.
341///
342/// Example input:
343/// * `/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs`
344/// * `crates/rerun/src/main.rs`
345/// * `/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs`
346///
347/// Example output:
348/// * `tokio-1.24.1/src/runtime/runtime.rs`
349/// * `rerun/src/main.rs`
350/// * `core/src/ops/function.rs`
351#[allow(clippy::allow_attributes, dead_code)] // only used on web and in tests
352fn shorten_file_path(file_path: &str) -> &str {
353    if let Some(i) = file_path.rfind("/src/") {
354        if let Some(prev_slash) = file_path[..i].rfind('/') {
355            &file_path[prev_slash + 1..]
356        } else {
357            file_path
358        }
359    } else {
360        file_path
361    }
362}
363
364#[test]
365fn test_shorten_file_path() {
366    for (before, after) in [
367        (
368            "/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs",
369            "tokio-1.24.1/src/runtime/runtime.rs",
370        ),
371        ("crates/rerun/src/main.rs", "rerun/src/main.rs"),
372        (
373            "/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs",
374            "core/src/ops/function.rs",
375        ),
376        ("/weird/path/file.rs", "/weird/path/file.rs"),
377    ] {
378        assert_eq!(shorten_file_path(before), after);
379    }
380}