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(×tamp, 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 }