taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

log.rs (4875B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::io::IsTerminal;
     18 
     19 use compact_str::CompactString;
     20 use jiff::{Timestamp, fmt::StdFmtWrite, tz::TimeZone};
     21 use tracing::{Event, Level, Subscriber};
     22 use tracing_subscriber::{
     23     fmt::{FmtContext, FormatEvent, FormatFields, format::Writer},
     24     layer::SubscriberExt,
     25     registry::LookupSpan,
     26     util::SubscriberInitExt,
     27 };
     28 
     29 tokio::task_local! {
     30     pub static LOG_TASK_ID: CompactString;
     31 }
     32 
     33 pub struct TalerFmt {
     34     zone: Option<TimeZone>,
     35 }
     36 
     37 impl Default for TalerFmt {
     38     fn default() -> Self {
     39         Self {
     40             zone: if std::env::var_os("JOURNAL_STREAM").is_some() {
     41                 None
     42             } else {
     43                 Some(match TimeZone::try_system() {
     44                     Ok(z) => z,
     45                     Err(e) => {
     46                         eprintln!("could not fetch system time zone, default to UTC: {e}");
     47                         TimeZone::UTC
     48                     }
     49                 })
     50             },
     51         }
     52     }
     53 }
     54 
     55 impl<S, N> FormatEvent<S, N> for TalerFmt
     56 where
     57     S: Subscriber + for<'a> LookupSpan<'a>,
     58     N: for<'a> FormatFields<'a> + 'static,
     59 {
     60     fn format_event(
     61         &self,
     62         ctx: &FmtContext<'_, S, N>,
     63         mut w: Writer<'_>,
     64         event: &Event<'_>,
     65     ) -> std::fmt::Result {
     66         let meta = event.metadata();
     67         let ansi = w.has_ansi_escapes();
     68 
     69         if let Some(zone) = &self.zone {
     70             let timestamp = Timestamp::now();
     71             let offset = zone.to_offset(timestamp);
     72             if ansi {
     73                 write!(&mut w, "\x1b[2m")?; // Dim fg
     74             }
     75             jiff::fmt::temporal::DateTimePrinter::new()
     76                 .precision(Some(6))
     77                 .separator(b'T')
     78                 .print_timestamp_with_offset(&timestamp, offset, StdFmtWrite(&mut w))
     79                 .map_err(|_| std::fmt::Error)?;
     80             // TODO remove : in offset
     81         }
     82         let level = meta.level();
     83         if ansi {
     84             let color = match *level {
     85                 tracing::Level::ERROR => "\x1b[31m", // red
     86                 tracing::Level::WARN => "\x1b[33m",  // yellow
     87                 tracing::Level::INFO => "\x1b[32m",  // green
     88                 tracing::Level::DEBUG => "\x1b[34m", // blue
     89                 tracing::Level::TRACE => "\x1b[35m", // magenta
     90             };
     91             write!(&mut w, "\x1b[0m{color}")?
     92         };
     93         let level = match *level {
     94             tracing::Level::ERROR => "ERROR", // red
     95             tracing::Level::WARN => " WARN",  // yellow
     96             tracing::Level::INFO => " INFO",  // green
     97             tracing::Level::DEBUG => "DEBUG", // blue
     98             tracing::Level::TRACE => "TRACE", // magenta
     99         };
    100         write!(&mut w, " {level} ")?; // Reset dim then color
    101         if ansi {
    102             write!(&mut w, "\x1b[2;37m")?; // Dim fg
    103         }
    104         LOG_TASK_ID
    105             .try_with(|id| write!(w, "({id})"))
    106             .ok()
    107             .transpose()?;
    108         write!(w, "{}: ", meta.target())?;
    109         if ansi {
    110             write!(&mut w, "\x1b[0m")?; // Reset
    111         }
    112         ctx.format_fields(w.by_ref(), event)?;
    113         writeln!(&mut w)
    114     }
    115 }
    116 
    117 pub fn taler_logger(max_level: Option<Level>, verbose: bool) -> impl SubscriberInitExt {
    118     let max_level = max_level.unwrap_or(Level::INFO);
    119     tracing_subscriber::registry()
    120         .with(
    121             tracing_subscriber::fmt::layer()
    122                 .event_format(TalerFmt::default())
    123                 .with_writer(std::io::stderr)
    124                 .with_ansi(std::io::stderr().is_terminal()),
    125         )
    126         .with(tracing_subscriber::filter::filter_fn(move |metadata| {
    127             let target = metadata.target();
    128             *metadata.level() <= max_level
    129                 && (verbose
    130                     || !(target.starts_with("sqlx")
    131                         || target.starts_with("log")
    132                         || target.starts_with("axum")
    133                         || target.contains("hyper_util")
    134                         || target.starts_with("h2")
    135                         || target.starts_with("reqwest")
    136                         || target.starts_with("rustls")
    137                         || target.starts_with("hyper_rustls")))
    138         }))
    139 }