rustfoundry/telemetry/log/
mod.rs

1//! Logging-related functionality.
2
3mod field_dedup;
4mod field_filtering;
5mod field_redact;
6mod rate_limit;
7
8pub(crate) mod init;
9
10#[cfg(any(test, feature = "testing"))]
11pub(crate) mod testing;
12
13#[doc(hidden)]
14pub mod internal;
15
16#[cfg(feature = "metrics")]
17pub mod log_volume;
18
19use self::init::LogHarness;
20use self::internal::current_log;
21use crate::telemetry::log::init::build_log_with_drain;
22use crate::telemetry::settings::LogVerbosity;
23use crate::Result;
24use slog::{Logger, OwnedKV};
25use std::ops::Deref;
26use std::sync::Arc;
27
28#[cfg(any(test, feature = "testing"))]
29pub use self::testing::TestLogRecord;
30
31/// Sets current log's verbosity, overriding the settings used in [`init`].
32///
33/// For reasons related to the current implementation of `set_verbosity()`, there is a danger of
34/// stack overflow if it is called an extremely large number of times on the same logger. To
35/// protect against the possibility of stack overflow, there is an internal counter which will
36/// trigger a panic if a limit of (currently) 1000 calls on a single logger is reached.
37///
38/// To avoid this panic, only call `set_verbosity()` when there is an actual change to the
39/// verbosity level.
40///
41/// [`init`]: crate::telemetry::init
42pub fn set_verbosity(verbosity: LogVerbosity) -> Result<()> {
43    let harness = LogHarness::get();
44
45    let mut settings = harness.settings.clone();
46    settings.verbosity = verbosity;
47
48    let current_log = current_log();
49    let current_log_lock = current_log.write();
50
51    let Some(mut current_log_lock) =
52        internal::LoggerWithKvNestingTracking::check_nesting_level(current_log_lock)
53    else {
54        return Ok(()); // avoid changes, nesting level was beyond threshold
55    };
56
57    let kv = OwnedKV(current_log_lock.list().clone());
58    current_log_lock.inner = build_log_with_drain(&settings, kv, Arc::clone(&harness.root_drain));
59
60    Ok(())
61}
62
63/// Gets the current log's verbosity.
64pub fn verbosity() -> LogVerbosity {
65    let harness = LogHarness::get();
66    harness.settings.verbosity
67}
68
69/// Returns current log as a raw [slog] crate's `Logger` used by Rustfoundry internally.
70///
71/// Can be used to propagate the logging context to libraries that don't use Rustfoundry'
72/// telemetry.
73///
74/// [slog]: https://crates.io/crates/slog
75pub fn slog_logger() -> Arc<parking_lot::RwLock<impl Deref<Target = Logger>>> {
76    current_log()
77}
78
79// NOTE: `#[doc(hidden)]` + `#[doc(inline)]` for `pub use` trick is used to prevent these macros
80// to show up in the crate's top level docs.
81
82/// Adds fields to all the log records, making them context fields.
83///
84/// Calling the method with same field name multiple times updates the key value. There is a small
85/// cost in performance if large numbers of the same field are added, which then must be
86/// deduplicated at runtime. For that reason, as well as the fact that there is a danger of stack
87/// overflow if `add_fields!` is called an extremely large number of times on the same logger,
88/// there is an internal counter which will trigger a panic if a limit of (currently) 1000 calls on
89/// a single logger is reached.
90///
91/// To avoid this panic, make sure to only use `add_fields!` for fields that will remain relatively
92/// static (under 1000 updates over the lifetime of any given logger).
93///
94/// Certain added fields may not be present in the resulting logs if
95/// [`LoggingSettings::redact_keys`] is used.
96///
97/// # Examples
98/// ```
99/// use rustfoundry::telemetry::TelemetryContext;
100/// use rustfoundry::telemetry::log::{self, TestLogRecord};
101/// use rustfoundry::telemetry::settings::Level;
102///
103/// // Test context is used for demonstration purposes to show the resulting log records.
104/// let ctx = TelemetryContext::test();
105/// let _scope = ctx.scope();
106///
107/// log::warn!("Hello with one field"; "foo" => "bar");
108///
109/// log::add_fields!("ctx_field1" => 42, "ctx_field2" => "baz");
110///
111/// log::warn!("With context fields"; "foo" => "bar");
112///
113/// // Update the context field value
114/// log::add_fields!("ctx_field1" => 43);
115///
116/// log::warn!("One more with context fields");
117///
118/// assert_eq!(*ctx.log_records(), &[
119///     TestLogRecord {
120///         level: Level::Warning,
121///         message: "Hello with one field".into(),
122///         fields: vec![("foo".into(), "bar".into())]
123///     },
124///     TestLogRecord {
125///         level: Level::Warning,
126///         message: "With context fields".into(),
127///         fields: vec![
128///             ("ctx_field2".into(), "baz".into()),
129///             ("ctx_field1".into(), "42".into()),
130///             ("foo".into(), "bar".into())
131///         ]
132///     },
133///     TestLogRecord {
134///         level: Level::Warning,
135///         message: "One more with context fields".into(),
136///         fields: vec![
137///             ("ctx_field1".into(), "43".into()),
138///             ("ctx_field2".into(), "baz".into()),
139///         ]
140///     }
141/// ]);
142/// ```
143///
144/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
145#[macro_export]
146#[doc(hidden)]
147macro_rules! __add_fields {
148    ( $($args:tt)* ) => {
149        $crate::telemetry::log::internal::add_log_fields(
150            $crate::reexports_for_macros::slog::o!($($args)*)
151        );
152    };
153}
154
155/// Log error level record.
156///
157/// If duplicate fields are specified for the record then the last one takes precedence and
158/// overwrites the value of the previous one.
159///
160/// Certain added fields may not be present in the resulting logs if
161/// [`LoggingSettings::redact_keys`] is used.
162///
163/// # Examples
164/// ```
165/// use rustfoundry::telemetry::TelemetryContext;
166/// use rustfoundry::telemetry::log::{self, TestLogRecord};
167/// use rustfoundry::telemetry::settings::Level;
168///
169/// // Test context is used for demonstration purposes to show the resulting log records.
170/// let ctx = TelemetryContext::test();
171/// let _scope = ctx.scope();
172///
173/// // Simple log message
174/// log::error!("Hello world!");
175///
176/// // Macro also accepts format arguments
177/// log::error!("The values are: {}, {}", 42, true);
178///
179/// // Fields key-value pairs can be added to log record, by separating the format message
180/// // and fields by `;`.
181/// log::error!("Answer: {}", 42; "foo" => "bar", "baz" => 1337);
182///
183/// assert_eq!(*ctx.log_records(), &[
184///     TestLogRecord {
185///         level: Level::Error,
186///         message: "Hello world!".into(),
187///         fields: vec![]
188///     },
189///     TestLogRecord {
190///         level: Level::Error,
191///         message: "The values are: 42, true".into(),
192///         fields: vec![]
193///     },
194///     TestLogRecord {
195///         level: Level::Error,
196///         message: "Answer: 42".into(),
197///         fields: vec![
198///             ("baz".into(), "1337".into()),
199///             ("foo".into(), "bar".into())
200///         ]
201///     }
202/// ]);
203/// ```
204///
205/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
206#[macro_export]
207#[doc(hidden)]
208macro_rules! __error {
209    ( $($args:tt)+ ) => {
210        $crate::reexports_for_macros::slog::error!(
211            $crate::telemetry::log::internal::current_log().read(),
212            $($args)+
213        );
214    };
215}
216
217/// Log warning level record.
218///
219/// If duplicate fields are specified for the record then the last one takes precedence and
220/// overwrites the value of the previous one.
221///
222/// Certain added fields may not be present in the resulting logs if
223/// [`LoggingSettings::redact_keys`] is used.
224///
225/// # Examples
226/// ```
227/// use rustfoundry::telemetry::TelemetryContext;
228/// use rustfoundry::telemetry::log::{self, TestLogRecord};
229/// use rustfoundry::telemetry::settings::Level;
230///
231/// // Test context is used for demonstration purposes to show the resulting log records.
232/// let ctx = TelemetryContext::test();
233/// let _scope = ctx.scope();
234///
235/// // Simple log message
236/// log::warn!("Hello world!");
237///
238/// // Macro also accepts format arguments
239/// log::warn!("The values are: {}, {}", 42, true);
240///
241/// // Fields key-value pairs can be added to log record, by separating the format message
242/// // and fields by `;`.
243/// log::warn!("Answer: {}", 42; "foo" => "bar", "baz" => 1337);
244///
245/// assert_eq!(*ctx.log_records(), &[
246///     TestLogRecord {
247///         level: Level::Warning,
248///         message: "Hello world!".into(),
249///         fields: vec![]
250///     },
251///     TestLogRecord {
252///         level: Level::Warning,
253///         message: "The values are: 42, true".into(),
254///         fields: vec![]
255///     },
256///     TestLogRecord {
257///         level: Level::Warning,
258///         message: "Answer: 42".into(),
259///         fields: vec![
260///             ("baz".into(), "1337".into()),
261///             ("foo".into(), "bar".into())
262///         ]
263///     }
264/// ]);
265/// ```
266///
267/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
268#[doc(hidden)]
269#[macro_export]
270macro_rules! __warn {
271    ( $($args:tt)+ ) => {
272        $crate::reexports_for_macros::slog::warn!(
273            $crate::telemetry::log::internal::current_log().read(),
274            $($args)+
275        );
276    };
277}
278
279/// Log debug level record.
280///
281/// If duplicate fields are specified for the record then the last one takes precedence and
282/// overwrites the value of the previous one.
283///
284/// Certain added fields may not be present in the resulting logs if
285/// [`LoggingSettings::redact_keys`] is used.
286///
287/// # Examples
288/// ```
289/// use rustfoundry::telemetry::TelemetryContext;
290/// use rustfoundry::telemetry::log::{self, TestLogRecord};
291/// use rustfoundry::telemetry::settings::Level;
292///
293/// // Test context is used for demonstration purposes to show the resulting log records.
294/// let ctx = TelemetryContext::test();
295/// let _scope = ctx.scope();
296///
297/// // Simple log message
298/// log::debug!("Hello world!");
299///
300/// // Macro also accepts format arguments
301/// log::debug!("The values are: {}, {}", 42, true);
302///
303/// // Fields key-value pairs can be added to log record, by separating the format message
304/// // and fields by `;`.
305/// log::debug!("Answer: {}", 42; "foo" => "bar", "baz" => 1337);
306///
307/// assert_eq!(*ctx.log_records(), &[
308///     TestLogRecord {
309///         level: Level::Debug,
310///         message: "Hello world!".into(),
311///         fields: vec![]
312///     },
313///     TestLogRecord {
314///         level: Level::Debug,
315///         message: "The values are: 42, true".into(),
316///         fields: vec![]
317///     },
318///     TestLogRecord {
319///         level: Level::Debug,
320///         message: "Answer: 42".into(),
321///         fields: vec![
322///             ("baz".into(), "1337".into()),
323///             ("foo".into(), "bar".into())
324///         ]
325///     }
326/// ]);
327/// ```
328///
329/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
330#[macro_export]
331#[doc(hidden)]
332macro_rules! __debug {
333    ( $($args:tt)+ ) => {
334        $crate::reexports_for_macros::slog::debug!(
335            $crate::telemetry::log::internal::current_log().read(),
336            $($args)+
337        );
338    };
339}
340
341/// Log info level record.
342///
343/// If duplicate fields are specified for the record then the last one takes precedence and
344/// overwrites the value of the previous one.
345///
346/// Certain added fields may not be present in the resulting logs if
347/// [`LoggingSettings::redact_keys`] is used.
348///
349/// # Examples
350/// ```
351/// use rustfoundry::telemetry::TelemetryContext;
352/// use rustfoundry::telemetry::log::{self, TestLogRecord};
353/// use rustfoundry::telemetry::settings::Level;
354///
355/// // Test context is used for demonstration purposes to show the resulting log records.
356/// let ctx = TelemetryContext::test();
357/// let _scope = ctx.scope();
358///
359/// // Simple log message
360/// log::info!("Hello world!");
361///
362/// // Macro also accepts format arguments
363/// log::info!("The values are: {}, {}", 42, true);
364///
365/// // Fields key-value pairs can be added to log record, by separating the format message
366/// // and fields by `;`.
367/// log::info!("Answer: {}", 42; "foo" => "bar", "baz" => 1337);
368///
369/// assert_eq!(*ctx.log_records(), &[
370///     TestLogRecord {
371///         level: Level::Info,
372///         message: "Hello world!".into(),
373///         fields: vec![]
374///     },
375///     TestLogRecord {
376///         level: Level::Info,
377///         message: "The values are: 42, true".into(),
378///         fields: vec![]
379///     },
380///     TestLogRecord {
381///         level: Level::Info,
382///         message: "Answer: 42".into(),
383///         fields: vec![
384///             ("baz".into(), "1337".into()),
385///             ("foo".into(), "bar".into())
386///         ]
387///     }
388/// ]);
389/// ```
390///
391/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
392#[macro_export]
393#[doc(hidden)]
394macro_rules! __info {
395    ( $($args:tt)+ ) => {
396        $crate::reexports_for_macros::slog::info!(
397            $crate::telemetry::log::internal::current_log().read(),
398            $($args)+
399        );
400    };
401}
402
403/// Log trace level record.
404///
405/// If duplicate fields are specified for the record then the last one takes precedence and
406/// overwrites the value of the previous one.
407///
408/// Certain added fields may not be present in the resulting logs if
409/// [`LoggingSettings::redact_keys`] is used.
410///
411/// # Examples
412/// ```
413/// use rustfoundry::telemetry::TelemetryContext;
414/// use rustfoundry::telemetry::log::{self, TestLogRecord};
415/// use rustfoundry::telemetry::settings::Level;
416///
417/// // Test context is used for demonstration purposes to show the resulting log records.
418/// let ctx = TelemetryContext::test();
419/// let _scope = ctx.scope();
420///
421/// // Simple log message
422/// log::trace!("Hello world!");
423///
424/// // Macro also accepts format arguments
425/// log::trace!("The values are: {}, {}", 42, true);
426///
427/// // Fields key-value pairs can be added to log record, by separating the format message
428/// // and fields by `;`.
429/// log::trace!("Answer: {}", 42; "foo" => "bar", "baz" => 1337);
430///
431/// assert_eq!(*ctx.log_records(), &[
432///     TestLogRecord {
433///         level: Level::Trace,
434///         message: "Hello world!".into(),
435///         fields: vec![]
436///     },
437///     TestLogRecord {
438///         level: Level::Trace,
439///         message: "The values are: 42, true".into(),
440///         fields: vec![]
441///     },
442///     TestLogRecord {
443///         level: Level::Trace,
444///         message: "Answer: 42".into(),
445///         fields: vec![
446///             ("baz".into(), "1337".into()),
447///             ("foo".into(), "bar".into())
448///         ]
449///     }
450/// ]);
451/// ```
452///
453/// [`LoggingSettings::redact_keys`]: crate::telemetry::settings::LoggingSettings::redact_keys
454#[macro_export]
455#[doc(hidden)]
456macro_rules! __trace {
457    ( $($args:tt)+ ) => {
458        $crate::reexports_for_macros::slog::trace!(
459            $crate::telemetry::log::internal::current_log().read(),
460            $($args)+
461        );
462    };
463}
464
465#[doc(inline)]
466pub use {
467    __add_fields as add_fields, __debug as debug, __error as error, __info as info,
468    __trace as trace, __warn as warn,
469};