LCOV - code coverage report
Current view: top level - libs/utils/src - logging.rs (source / functions) Coverage Total Hit
Test: 2453312769e0b6b061a2008879e6693300d0b938.info Lines: 59.9 % 167 100
Test Date: 2024-09-06 16:40:18 Functions: 51.7 % 29 15

            Line data    Source code
       1              : use std::str::FromStr;
       2              : 
       3              : use anyhow::Context;
       4              : use metrics::{IntCounter, IntCounterVec};
       5              : use once_cell::sync::Lazy;
       6              : use strum_macros::{EnumString, EnumVariantNames};
       7              : 
       8              : #[derive(
       9          618 :     EnumString, strum_macros::Display, EnumVariantNames, Eq, PartialEq, Debug, Clone, Copy,
      10              : )]
      11              : #[strum(serialize_all = "snake_case")]
      12              : pub enum LogFormat {
      13              :     Plain,
      14              :     Json,
      15              :     Test,
      16              : }
      17              : 
      18              : impl LogFormat {
      19            0 :     pub fn from_config(s: &str) -> anyhow::Result<LogFormat> {
      20              :         use strum::VariantNames;
      21            0 :         LogFormat::from_str(s).with_context(|| {
      22            0 :             format!(
      23            0 :                 "Unrecognized log format. Please specify one of: {:?}",
      24            0 :                 LogFormat::VARIANTS
      25            0 :             )
      26            0 :         })
      27            0 :     }
      28              : }
      29              : 
      30              : struct TracingEventCountMetric {
      31              :     error: IntCounter,
      32              :     warn: IntCounter,
      33              :     info: IntCounter,
      34              :     debug: IntCounter,
      35              :     trace: IntCounter,
      36              : }
      37              : 
      38          618 : static TRACING_EVENT_COUNT_METRIC: Lazy<TracingEventCountMetric> = Lazy::new(|| {
      39          618 :     let vec = metrics::register_int_counter_vec!(
      40          618 :         "libmetrics_tracing_event_count",
      41          618 :         "Number of tracing events, by level",
      42          618 :         &["level"]
      43          618 :     )
      44          618 :     .expect("failed to define metric");
      45          618 :     TracingEventCountMetric::new(vec)
      46          618 : });
      47              : 
      48              : impl TracingEventCountMetric {
      49          619 :     fn new(vec: IntCounterVec) -> Self {
      50          619 :         Self {
      51          619 :             error: vec.with_label_values(&["error"]),
      52          619 :             warn: vec.with_label_values(&["warn"]),
      53          619 :             info: vec.with_label_values(&["info"]),
      54          619 :             debug: vec.with_label_values(&["debug"]),
      55          619 :             trace: vec.with_label_values(&["trace"]),
      56          619 :         }
      57          619 :     }
      58              : 
      59        65627 :     fn inc_for_level(&self, level: tracing::Level) {
      60        65627 :         let counter = match level {
      61          223 :             tracing::Level::ERROR => &self.error,
      62          667 :             tracing::Level::WARN => &self.warn,
      63        64735 :             tracing::Level::INFO => &self.info,
      64            1 :             tracing::Level::DEBUG => &self.debug,
      65            1 :             tracing::Level::TRACE => &self.trace,
      66              :         };
      67        65627 :         counter.inc();
      68        65627 :     }
      69              : }
      70              : 
      71              : struct TracingEventCountLayer(&'static TracingEventCountMetric);
      72              : 
      73              : impl<S> tracing_subscriber::layer::Layer<S> for TracingEventCountLayer
      74              : where
      75              :     S: tracing::Subscriber,
      76              : {
      77        65627 :     fn on_event(
      78        65627 :         &self,
      79        65627 :         event: &tracing::Event<'_>,
      80        65627 :         _ctx: tracing_subscriber::layer::Context<'_, S>,
      81        65627 :     ) {
      82        65627 :         self.0.inc_for_level(*event.metadata().level());
      83        65627 :     }
      84              : }
      85              : 
      86              : /// Whether to add the `tracing_error` crate's `ErrorLayer`
      87              : /// to the global tracing subscriber.
      88              : ///
      89              : pub enum TracingErrorLayerEnablement {
      90              :     /// Do not add the `ErrorLayer`.
      91              :     Disabled,
      92              :     /// Add the `ErrorLayer` with the filter specified by RUST_LOG, defaulting to `info` if `RUST_LOG` is unset.
      93              :     EnableWithRustLogFilter,
      94              : }
      95              : 
      96              : /// Where the logging should output to.
      97              : #[derive(Clone, Copy)]
      98              : pub enum Output {
      99              :     Stdout,
     100              :     Stderr,
     101              : }
     102              : 
     103          618 : pub fn init(
     104          618 :     log_format: LogFormat,
     105          618 :     tracing_error_layer_enablement: TracingErrorLayerEnablement,
     106          618 :     output: Output,
     107          618 : ) -> anyhow::Result<()> {
     108          618 :     // We fall back to printing all spans at info-level or above if
     109          618 :     // the RUST_LOG environment variable is not set.
     110         1808 :     let rust_log_env_filter = || {
     111         1808 :         tracing_subscriber::EnvFilter::try_from_default_env()
     112         1808 :             .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"))
     113         1808 :     };
     114              : 
     115              :     // NB: the order of the with() calls does not matter.
     116              :     // See https://docs.rs/tracing-subscriber/0.3.16/tracing_subscriber/layer/index.html#per-layer-filtering
     117              :     use tracing_subscriber::prelude::*;
     118          618 :     let r = tracing_subscriber::registry();
     119          618 :     let r = r.with({
     120          618 :         let log_layer = tracing_subscriber::fmt::layer()
     121          618 :             .with_target(false)
     122          618 :             .with_ansi(false)
     123          618 :             .with_writer(move || -> Box<dyn std::io::Write> {
     124            0 :                 match output {
     125            0 :                     Output::Stdout => Box::new(std::io::stdout()),
     126            0 :                     Output::Stderr => Box::new(std::io::stderr()),
     127              :                 }
     128          618 :             });
     129          618 :         let log_layer = match log_format {
     130            0 :             LogFormat::Json => log_layer.json().boxed(),
     131            0 :             LogFormat::Plain => log_layer.boxed(),
     132          618 :             LogFormat::Test => log_layer.with_test_writer().boxed(),
     133              :         };
     134          618 :         log_layer.with_filter(rust_log_env_filter())
     135          618 :     });
     136          618 :     let r = r.with(
     137          618 :         TracingEventCountLayer(&TRACING_EVENT_COUNT_METRIC).with_filter(rust_log_env_filter()),
     138          618 :     );
     139          618 :     match tracing_error_layer_enablement {
     140          572 :         TracingErrorLayerEnablement::EnableWithRustLogFilter => r
     141          572 :             .with(tracing_error::ErrorLayer::default().with_filter(rust_log_env_filter()))
     142          572 :             .init(),
     143           46 :         TracingErrorLayerEnablement::Disabled => r.init(),
     144              :     }
     145              : 
     146          618 :     Ok(())
     147          618 : }
     148              : 
     149              : /// Disable the default rust panic hook by using `set_hook`.
     150              : ///
     151              : /// For neon binaries, the assumption is that tracing is configured before with [`init`], after
     152              : /// that sentry is configured (if needed). sentry will install it's own on top of this, always
     153              : /// processing the panic before we log it.
     154              : ///
     155              : /// When the return value is dropped, the hook is reverted to std default hook (prints to stderr).
     156              : /// If the assumptions about the initialization order are not held, use
     157              : /// [`TracingPanicHookGuard::forget`] but keep in mind, if tracing is stopped, then panics will be
     158              : /// lost.
     159              : #[must_use]
     160           12 : pub fn replace_panic_hook_with_tracing_panic_hook() -> TracingPanicHookGuard {
     161           12 :     std::panic::set_hook(Box::new(tracing_panic_hook));
     162           12 :     TracingPanicHookGuard::new()
     163           12 : }
     164              : 
     165              : /// Drop guard which restores the std panic hook on drop.
     166              : ///
     167              : /// Tracing should not be used when it's not configured, but we cannot really latch on to any
     168              : /// imaginary lifetime of tracing.
     169              : pub struct TracingPanicHookGuard {
     170              :     act: bool,
     171              : }
     172              : 
     173              : impl TracingPanicHookGuard {
     174           12 :     fn new() -> Self {
     175           12 :         TracingPanicHookGuard { act: true }
     176           12 :     }
     177              : 
     178              :     /// Make this hook guard not do anything when dropped.
     179           12 :     pub fn forget(&mut self) {
     180           12 :         self.act = false;
     181           12 :     }
     182              : }
     183              : 
     184              : impl Drop for TracingPanicHookGuard {
     185           12 :     fn drop(&mut self) {
     186           12 :         if self.act {
     187            0 :             let _ = std::panic::take_hook();
     188           12 :         }
     189           12 :     }
     190              : }
     191              : 
     192              : /// Named symbol for our panic hook, which logs the panic.
     193            0 : fn tracing_panic_hook(info: &std::panic::PanicHookInfo) {
     194            0 :     // following rust 1.66.1 std implementation:
     195            0 :     // https://github.com/rust-lang/rust/blob/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/panicking.rs#L235-L288
     196            0 :     let location = info.location();
     197              : 
     198            0 :     let msg = match info.payload().downcast_ref::<&'static str>() {
     199            0 :         Some(s) => *s,
     200            0 :         None => match info.payload().downcast_ref::<String>() {
     201            0 :             Some(s) => &s[..],
     202            0 :             None => "Box<dyn Any>",
     203              :         },
     204              :     };
     205              : 
     206            0 :     let thread = std::thread::current();
     207            0 :     let thread = thread.name().unwrap_or("<unnamed>");
     208            0 :     let backtrace = std::backtrace::Backtrace::capture();
     209              : 
     210            0 :     let _entered = if let Some(location) = location {
     211            0 :         tracing::error_span!("panic", %thread, location = %PrettyLocation(location))
     212              :     } else {
     213              :         // very unlikely to hit here, but the guarantees of std could change
     214            0 :         tracing::error_span!("panic", %thread)
     215              :     }
     216            0 :     .entered();
     217            0 : 
     218            0 :     if backtrace.status() == std::backtrace::BacktraceStatus::Captured {
     219              :         // this has an annoying extra '\n' in the end which anyhow doesn't do, but we cannot really
     220              :         // get rid of it as we cannot get in between of std::fmt::Formatter<'_>; we could format to
     221              :         // string, maybe even to a TLS one but tracing already does that.
     222            0 :         tracing::error!("{msg}\n\nStack backtrace:\n{backtrace}");
     223              :     } else {
     224            0 :         tracing::error!("{msg}");
     225              :     }
     226              : 
     227              :     // ensure that we log something on the panic if this hook is left after tracing has been
     228              :     // unconfigured. worst case when teardown is racing the panic is to log the panic twice.
     229            0 :     tracing::dispatcher::get_default(|d| {
     230            0 :         if let Some(_none) = d.downcast_ref::<tracing::subscriber::NoSubscriber>() {
     231            0 :             let location = location.map(PrettyLocation);
     232            0 :             log_panic_to_stderr(thread, msg, location, &backtrace);
     233            0 :         }
     234            0 :     });
     235            0 : }
     236              : 
     237              : #[cold]
     238            0 : fn log_panic_to_stderr(
     239            0 :     thread: &str,
     240            0 :     msg: &str,
     241            0 :     location: Option<PrettyLocation<'_, '_>>,
     242            0 :     backtrace: &std::backtrace::Backtrace,
     243            0 : ) {
     244            0 :     eprintln!("panic while tracing is unconfigured: thread '{thread}' panicked at '{msg}', {location:?}\nStack backtrace:\n{backtrace}");
     245            0 : }
     246              : 
     247              : struct PrettyLocation<'a, 'b>(&'a std::panic::Location<'b>);
     248              : 
     249              : impl std::fmt::Display for PrettyLocation<'_, '_> {
     250            0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     251            0 :         write!(f, "{}:{}:{}", self.0.file(), self.0.line(), self.0.column())
     252            0 :     }
     253              : }
     254              : 
     255              : impl std::fmt::Debug for PrettyLocation<'_, '_> {
     256            0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     257            0 :         <Self as std::fmt::Display>::fmt(self, f)
     258            0 :     }
     259              : }
     260              : 
     261              : /// When you will store a secret but want to make sure it won't
     262              : /// be accidentally logged, wrap it in a SecretString, whose Debug
     263              : /// implementation does not expose the contents.
     264              : #[derive(Clone, Eq, PartialEq)]
     265              : pub struct SecretString(String);
     266              : 
     267              : impl SecretString {
     268            0 :     pub fn get_contents(&self) -> &str {
     269            0 :         self.0.as_str()
     270            0 :     }
     271              : }
     272              : 
     273              : impl From<String> for SecretString {
     274            0 :     fn from(s: String) -> Self {
     275            0 :         Self(s)
     276            0 :     }
     277              : }
     278              : 
     279              : impl FromStr for SecretString {
     280              :     type Err = std::convert::Infallible;
     281              : 
     282            0 :     fn from_str(s: &str) -> Result<Self, Self::Err> {
     283            0 :         Ok(Self(s.to_string()))
     284            0 :     }
     285              : }
     286              : 
     287              : impl std::fmt::Debug for SecretString {
     288            0 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     289            0 :         write!(f, "[SECRET]")
     290            0 :     }
     291              : }
     292              : 
     293              : #[cfg(test)]
     294              : mod tests {
     295              :     use metrics::{core::Opts, IntCounterVec};
     296              : 
     297              :     use crate::logging::{TracingEventCountLayer, TracingEventCountMetric};
     298              : 
     299              :     #[test]
     300            1 :     fn tracing_event_count_metric() {
     301            1 :         let counter_vec =
     302            1 :             IntCounterVec::new(Opts::new("testmetric", "testhelp"), &["level"]).unwrap();
     303            1 :         let metric = Box::leak(Box::new(TracingEventCountMetric::new(counter_vec.clone())));
     304            1 :         let layer = TracingEventCountLayer(metric);
     305              :         use tracing_subscriber::prelude::*;
     306              : 
     307            1 :         tracing::subscriber::with_default(tracing_subscriber::registry().with(layer), || {
     308            1 :             tracing::trace!("foo");
     309            1 :             tracing::debug!("foo");
     310            1 :             tracing::info!("foo");
     311            1 :             tracing::warn!("foo");
     312            1 :             tracing::error!("foo");
     313            1 :         });
     314            1 : 
     315            1 :         assert_eq!(counter_vec.with_label_values(&["trace"]).get(), 1);
     316            1 :         assert_eq!(counter_vec.with_label_values(&["debug"]).get(), 1);
     317            1 :         assert_eq!(counter_vec.with_label_values(&["info"]).get(), 1);
     318            1 :         assert_eq!(counter_vec.with_label_values(&["warn"]).get(), 1);
     319            1 :         assert_eq!(counter_vec.with_label_values(&["error"]).get(), 1);
     320            1 :     }
     321              : }
        

Generated by: LCOV version 2.1-beta