LCOV - differential code coverage report
Current view: top level - libs/utils/src - logging.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 78.7 % 150 118 32 118
Current Date: 2023-10-19 02:04:12 Functions: 64.9 % 37 24 13 24
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

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

Generated by: LCOV version 2.1-beta