Skip to content

Commit

Permalink
telemetry: add capability to forward log facade logs to Foundations
Browse files Browse the repository at this point in the history
  • Loading branch information
evanrittenhouse committed Feb 6, 2024
1 parent 25534b3 commit 1cb59df
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ governor = "0.6"
hyper = { version = "0.14", default-features = false }
indexmap = "2.0.0"
ipnetwork = "0.20"
log = "0.4.20"
once_cell = "1.5"
parking_lot = "0.12"
proc-macro2 = { version = "1", default-features = false }
Expand All @@ -61,6 +62,8 @@ slab = "0.4.9"
slog = "2.4"
slog-async = "2.3"
slog-json = "2.3"
slog-scope = "4.4.0"
slog-stdlog = "4.1.1"
slog-term = "2.4"
tempfile = "3.7"
tokio = "1.31.0"
Expand Down
6 changes: 6 additions & 0 deletions foundations/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,13 @@ telemetry-server = [
# Enables logging functionality.
logging = [
"dep:governor",
"dep:log",
"dep:once_cell",
"dep:parking_lot",
"dep:slog-async",
"dep:slog-json",
"dep:slog-scope",
"dep:slog-stdlog",
"dep:slog-term",
"dep:slog",
"dep:thread_local",
Expand Down Expand Up @@ -162,6 +165,7 @@ hyper = { workspace = true, optional = true, features = [
"server",
] }
indexmap = { workspace = true, optional = true, features = ["serde"] }
log = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true }
parking_lot = { workspace = true, optional = true }
prometheus = { workspace = true, optional = true, features = ["process"] }
Expand All @@ -179,6 +183,8 @@ slab = { workspace = true, optional = true }
slog = { workspace = true, optional = true, features = ["max_level_trace"] }
slog-async = { workspace = true, optional = true }
slog-json = { workspace = true, optional = true }
slog-scope = { workspace = true, optional = true }
slog-stdlog = { workspace = true, optional = true }
slog-term = { workspace = true, optional = true }
socket2 = { workspace = true, optional = true }
thread_local = { workspace = true, optional = true }
Expand Down
57 changes: 57 additions & 0 deletions foundations/src/telemetry/log/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ use self::internal::current_log;
use crate::telemetry::log::init::build_log_with_drain;
use crate::telemetry::settings::LogVerbosity;
use crate::Result;
use log::Level as std_level;
use slog::{Level, Logger, OwnedKV};
use slog_scope::{set_global_logger, GlobalLoggerGuard};
use std::sync::Arc;

static mut GLOBAL_LOGGER_GUARD: Option<GlobalLoggerGuard> = None;
static GLOBAL_LOGGER_CAPTURE: parking_lot::Once = parking_lot::Once::new();

#[cfg(any(test, feature = "testing"))]
pub use self::testing::TestLogRecord;

Expand Down Expand Up @@ -59,6 +64,58 @@ pub fn slog_logger() -> Arc<parking_lot::RwLock<Logger>> {
current_log()
}

/// Capture logs from the [`log`](https://docs.rs/log/latest/log/) crate by forwarding them into
/// the current [`slog::Drain`]. Due to the intricacies and subtleties of this method, **you should
/// be very careful when using it**. Note also that this method can only be called once.
///
/// # Note
///
/// After calling this method, all `log` logs will be forwarded to the [`slog::Drain`] in use for
/// the rest of the program's lifetime.
///
/// # Examples
/// ```should_panic
/// use foundations::telemetry::TelemetryContext;
/// use foundations::telemetry::log::capture_global_log_logs;
/// use log::warn as log_warn;
///
/// let cx = TelemetryContext::test();
/// capture_global_log_logs();
/// for i in 0..16 {
/// log_warn!("{}", i);
/// }
///
/// assert_eq!(cx.log_records().len(), 16);
/// ```
pub fn capture_global_log_logs() {
unsafe {
// SAFETY: mutating a `static mut` is generally unsafe, but since we're guarding it behind
// a call_once, we should be fine.
GLOBAL_LOGGER_CAPTURE.call_once(|| {
let curr_logger = Arc::clone(&slog_logger()).read().clone();
let scope_guard = set_global_logger(curr_logger);

// Convert slog::Level from Foundations settings to log::Level
let normalized_level = match verbosity().0 {
Level::Critical | Level::Error => std_level::Error,
Level::Warning => std_level::Warn,
Level::Info => std_level::Info,
Level::Debug => std_level::Debug,
Level::Trace => std_level::Trace,
};

slog_stdlog::init_with_level(normalized_level).unwrap();

// Storing the scope guard in a static global guard means that logs will be forwarded
// to the slog::Drain for the entirety of the program's lifetime. This prevents users
// from accidentally calling the method twice and triggering log::SetLoggerErrors, or
// attempting to log messages after dropping the guard and triggering
// slog_scope::NoLoggerSet.
GLOBAL_LOGGER_GUARD = Some(scope_guard);
});
}
}

// NOTE: `#[doc(hidden)]` + `#[doc(inline)]` for `pub use` trick is used to prevent these macros
// to show up in the crate's top level docs.

Expand Down

0 comments on commit 1cb59df

Please sign in to comment.