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
15mod channel_logger;
16mod result_extensions;
17
18#[cfg(feature = "setup")]
19mod multi_logger;
20
21#[cfg(feature = "setup")]
22mod setup;
23
24#[cfg(all(feature = "setup", target_arch = "wasm32"))]
25mod web_logger;
26
27pub use log::{Level, LevelFilter};
28
29pub use result_extensions::ResultExt;
30
31// The tracing macros support more syntax features than the log, that's why we use them:
32pub use tracing::{debug, error, info, trace, warn};
33
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, log_once, trace_once, warn_once};
38
39pub use channel_logger::*;
40
41#[cfg(feature = "setup")]
42pub use multi_logger::{add_boxed_logger, add_logger, MultiLoggerNotSetupError};
43
44#[cfg(feature = "setup")]
45pub use setup::{setup_logging, setup_logging_with_filter};
46
47#[cfg(all(feature = "setup", not(target_arch = "wasm32")))]
48pub use setup::PanicOnWarnScope;
49
50/// Re-exports of other crates.
51pub mod external {
52 pub use log;
53}
54
55/// Never log anything less serious than a `ERROR` from these crates.
56const CRATES_AT_ERROR_LEVEL: &[&str] = &[
57 // silence rustls in release mode: https://github.com/rerun-io/rerun/issues/3104
58 #[cfg(not(debug_assertions))]
59 "rustls",
60];
61
62/// Never log anything less serious than a `WARN` from these crates.
63const CRATES_AT_WARN_LEVEL: &[&str] = &[
64 // wgpu crates spam a lot on info level, which is really annoying
65 // TODO(emilk): remove once https://github.com/gfx-rs/wgpu/issues/3206 is fixed
66 "naga",
67 "tracing",
68 "wgpu_core",
69 "wgpu_hal",
70 "zbus",
71];
72
73/// Never log anything less serious than a `INFO` from these crates.
74const CRATES_AT_INFO_LEVEL: &[&str] = &[
75 // These are quite spammy on debug, drowning out what we care about:
76 "h2",
77 "hyper",
78 "prost_build",
79 "tower",
80 "ureq",
81 // only let rustls log in debug mode: https://github.com/rerun-io/rerun/issues/3104
82 #[cfg(debug_assertions)]
83 "rustls",
84 // walkers generates noise around tile download, see https://github.com/podusowski/walkers/issues/199
85 "walkers",
86 // winit 0.30.5 spams about `set_cursor_visible` calls. It's gone on winit master, so hopefully gone in next winit release.
87 "winit",
88];
89
90/// Determines the default log filter.
91///
92/// Native: Get `RUST_LOG` environment variable or `info`, if not set.
93/// Also sets some other log levels on crates that are too loud.
94///
95/// Web: `debug` since web console allows arbitrary filtering.
96#[cfg(not(target_arch = "wasm32"))]
97pub fn default_log_filter() -> String {
98 let base_log_filter = if cfg!(debug_assertions) {
99 // We want the DEBUG level to be useful yet not too spammy.
100 // This is a good way to enforce that.
101 "debug"
102 } else {
103 // Important to keep the default at (at least) "info",
104 // as we print crucial information at INFO,
105 // e.g. the ip:port when hosting a server with `rerun-cli`.
106 "info"
107 };
108 log_filter_from_env_or_default(base_log_filter)
109}
110
111/// Determines the default log filter.
112///
113/// Native: Get `RUST_LOG` environment variable or `info`, if not set.
114/// Also sets some other log levels on crates that are too loud.
115///
116/// Web: `debug` since web console allows arbitrary filtering.
117#[cfg(target_arch = "wasm32")]
118pub fn default_log_filter() -> String {
119 "debug".to_owned()
120}
121
122/// Determines the log filter from the `RUST_LOG` environment variable or an explicit default.
123///
124/// Always adds builtin filters as well.
125#[cfg(not(target_arch = "wasm32"))]
126pub fn log_filter_from_env_or_default(default_base_log_filter: &str) -> String {
127 let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| default_base_log_filter.to_owned());
128 add_builtin_log_filter(&rust_log)
129}
130
131/// Adds builtin log level filters for crates that are too verbose.
132#[cfg(not(target_arch = "wasm32"))]
133fn add_builtin_log_filter(base_log_filter: &str) -> String {
134 let mut rust_log = base_log_filter.to_lowercase();
135
136 if base_log_filter != "off" {
137 // If base level is `off`, don't opt-in to anything.
138
139 for crate_name in crate::CRATES_AT_ERROR_LEVEL {
140 if !rust_log.contains(&format!("{crate_name}=")) {
141 rust_log += &format!(",{crate_name}=error");
142 }
143 }
144
145 if base_log_filter != "error" {
146 // If base level is `error`, don't opt-in to `warn` or `info`.
147
148 for crate_name in crate::CRATES_AT_WARN_LEVEL {
149 if !rust_log.contains(&format!("{crate_name}=")) {
150 rust_log += &format!(",{crate_name}=warn");
151 }
152 }
153
154 if base_log_filter != "warn" {
155 // If base level is not `error`/`warn`, don't opt-in to `info`.
156
157 for crate_name in crate::CRATES_AT_INFO_LEVEL {
158 if !rust_log.contains(&format!("{crate_name}=")) {
159 rust_log += &format!(",{crate_name}=info");
160 }
161 }
162 }
163 }
164 }
165
166 //TODO(#8077): should be removed as soon as the upstream issue is resolved
167 rust_log += ",walkers::download=off";
168
169 rust_log
170}
171
172/// Should we log this message given the filter?
173fn is_log_enabled(filter: log::LevelFilter, metadata: &log::Metadata<'_>) -> bool {
174 if CRATES_AT_ERROR_LEVEL
175 .iter()
176 .any(|crate_name| metadata.target().starts_with(crate_name))
177 {
178 return metadata.level() <= log::LevelFilter::Error;
179 }
180
181 if CRATES_AT_WARN_LEVEL
182 .iter()
183 .any(|crate_name| metadata.target().starts_with(crate_name))
184 {
185 return metadata.level() <= log::LevelFilter::Warn;
186 }
187
188 if CRATES_AT_INFO_LEVEL
189 .iter()
190 .any(|crate_name| metadata.target().starts_with(crate_name))
191 {
192 return metadata.level() <= log::LevelFilter::Info;
193 }
194
195 metadata.level() <= filter
196}
197
198/// Shorten a path to a Rust source file.
199///
200/// Example input:
201/// * `/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs`
202/// * `crates/rerun/src/main.rs`
203/// * `/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs`
204///
205/// Example output:
206/// * `tokio-1.24.1/src/runtime/runtime.rs`
207/// * `rerun/src/main.rs`
208/// * `core/src/ops/function.rs`
209#[allow(dead_code)] // only used on web and in tests
210fn shorten_file_path(file_path: &str) -> &str {
211 if let Some(i) = file_path.rfind("/src/") {
212 if let Some(prev_slash) = file_path[..i].rfind('/') {
213 &file_path[prev_slash + 1..]
214 } else {
215 file_path
216 }
217 } else {
218 file_path
219 }
220}
221
222#[test]
223fn test_shorten_file_path() {
224 for (before, after) in [
225 (
226 "/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs",
227 "tokio-1.24.1/src/runtime/runtime.rs",
228 ),
229 ("crates/rerun/src/main.rs", "rerun/src/main.rs"),
230 (
231 "/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs",
232 "core/src/ops/function.rs",
233 ),
234 ("/weird/path/file.rs", "/weird/path/file.rs"),
235 ] {
236 assert_eq!(shorten_file_path(before), after);
237 }
238}