Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

telemetry: add capability to forward log facade logs to Foundations #17

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
/// ```
/// 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