Skip to main content

sozu_command_lib/logging/
logs.rs

1use std::{
2    cell::{Cell, RefCell},
3    cmp, env,
4    fmt::Arguments,
5    fs::{File, OpenOptions},
6    io::{Error as IoError, ErrorKind as IoErrorKind, Stdout, Write, stdout},
7    net::{SocketAddr, TcpStream, UdpSocket},
8    ops::{Deref, DerefMut},
9    path::Path,
10    str::FromStr,
11};
12
13use mio::net::UnixDatagram;
14use prost::{Message, encoding::encoded_len_varint};
15
16use crate::{
17    AsString,
18    config::{Config, DEFAULT_LOG_TARGET},
19    logging::{LogDuration, LogError, LogMessage, RequestRecord},
20    proto::command::ProtobufAccessLogFormat,
21    writer::MultiLineWriter,
22};
23
24thread_local! {
25  pub static LOGGER: RefCell<Logger> = RefCell::new(Logger::new());
26  /// Side-channel mirror of [`InnerLogger::colored`]. Read by
27  /// [`is_logger_colored`] without borrowing the [`LOGGER`] cell, so macros
28  /// like `log_context!` can consult it while the logger is already held
29  /// mutably by the enclosing `error!`/`info!`/... emission.
30  static LOGGER_COLORED: Cell<bool> = const { Cell::new(false) };
31}
32
33/// Returns `true` when the thread-local [`Logger`] is configured to emit colored
34/// output. Safe to call from inside an `error!`/`info!`/… argument list — the
35/// flag mirrors [`Logger::is_colored`] (i.e. [`InnerLogger::colored`]) into a
36/// dedicated [`Cell`] at [`Logger::init`] time so we never re-borrow the main
37/// logger cell (which is already held mutably while the outer log macro is
38/// formatting its arguments). The mirror is the single source of truth for
39/// hot-path callers; `Logger::is_colored` is the single source of truth for
40/// the rest of the API. They go through the same `InnerLogger::colored`
41/// field, so they cannot diverge by construction.
42pub fn is_logger_colored() -> bool {
43    LOGGER_COLORED.with(|c| c.get())
44}
45
46/// ANSI palette used by every `log_context!` / `log_*_context!` macro in the
47/// proxy stack. Returns a 5-tuple `(open, reset, grey, gray, white)` where:
48///
49/// - `open`   = `\x1b[1;97m` (bold bright-white) — protocol label (`MUX`,
50///   `MUX-H2`, `SOCKET`, `RUSTLS`, `PIPE`, …).
51/// - `reset`  = `\x1b[0m` — ANSI reset.
52/// - `grey`   = `\x1b[37m` (light grey) — `Session` keyword.
53/// - `gray`   = `\x1b[90m` (bright black) — attribute keys (`peer=`,
54///   `local=`, `cluster=`, …).
55/// - `white`  = `\x1b[97m` (bright white) — attribute values.
56///
57/// When [`is_logger_colored`] returns `false` every slot is an empty string,
58/// so macros can unconditionally inline the tokens into `format!` without
59/// leaking escape codes into plain-text log sinks.
60///
61/// Callers that only need the label pair can destructure with wildcards:
62/// `let (open, reset, _, _, _) = ansi_palette();`.
63pub fn ansi_palette() -> (
64    &'static str,
65    &'static str,
66    &'static str,
67    &'static str,
68    &'static str,
69) {
70    if is_logger_colored() {
71        ("\x1b[1;97m", "\x1b[0m", "\x1b[37m", "\x1b[90m", "\x1b[97m")
72    } else {
73        ("", "", "", "", "")
74    }
75}
76
77// TODO: check if this error is critical:
78//     could not register compat logger: SetLoggerError(())
79// The CompatLogger may need a variable that tells wether it has been initiated already
80pub static COMPAT_LOGGER: CompatLogger = CompatLogger;
81
82#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
83#[serde(deny_unknown_fields, rename_all = "lowercase")]
84pub enum AccessLogFormat {
85    Ascii,
86    Protobuf,
87}
88
89impl From<&ProtobufAccessLogFormat> for AccessLogFormat {
90    fn from(value: &ProtobufAccessLogFormat) -> Self {
91        match value {
92            ProtobufAccessLogFormat::Ascii => Self::Ascii,
93            ProtobufAccessLogFormat::Protobuf => Self::Protobuf,
94        }
95    }
96}
97
98impl From<&Option<AccessLogFormat>> for ProtobufAccessLogFormat {
99    fn from(value: &Option<AccessLogFormat>) -> Self {
100        match value {
101            Some(AccessLogFormat::Ascii) | None => Self::Ascii,
102            Some(AccessLogFormat::Protobuf) => Self::Protobuf,
103        }
104    }
105}
106
107pub struct InnerLogger {
108    directives: Vec<LogDirective>,
109    backend: LoggerBackend,
110    /// inherited from the config's `log_target`, used to revive the backend
111    log_target: String,
112    pub colored: bool,
113    /// target of the access logs
114    access_backend: Option<LoggerBackend>,
115    /// used to revive the backend for access logs
116    access_logs_target: Option<String>,
117    /// how to format the access logs
118    access_format: AccessLogFormat,
119    access_colored: bool,
120    buffer: LoggerBuffer,
121}
122
123pub struct Logger {
124    inner: InnerLogger,
125    /// is displayed in each log, for instance "MAIN" or worker_id
126    tag: String,
127    /// the pid of the current process (main or worker)
128    pid: i32,
129    initialized: bool,
130}
131
132impl std::ops::Deref for Logger {
133    type Target = InnerLogger;
134    fn deref(&self) -> &Self::Target {
135        &self.inner
136    }
137}
138impl std::ops::DerefMut for Logger {
139    fn deref_mut(&mut self) -> &mut Self::Target {
140        &mut self.inner
141    }
142}
143
144impl Default for Logger {
145    fn default() -> Self {
146        Self {
147            inner: InnerLogger {
148                directives: vec![LogDirective {
149                    name: None,
150                    level: LogLevelFilter::Error,
151                }],
152                backend: LoggerBackend::Stdout(stdout()),
153                log_target: DEFAULT_LOG_TARGET.to_string(),
154                colored: false,
155                access_backend: None,
156                access_logs_target: None,
157                access_format: AccessLogFormat::Ascii,
158                access_colored: false,
159                buffer: LoggerBuffer(Vec::with_capacity(4096)),
160            },
161            tag: "UNINITIALIZED".to_string(),
162            pid: 0,
163            initialized: false,
164        }
165    }
166}
167
168impl Logger {
169    pub fn new() -> Self {
170        Self::default()
171    }
172
173    pub fn init(
174        tag: String,
175        spec: &str,
176        log_target: &str,
177        colored: bool,
178        access_logs_target: Option<&str>,
179        access_format: Option<AccessLogFormat>,
180        access_colored: Option<bool>,
181    ) -> Result<(), LogError> {
182        println!("Logs will be sent to {log_target}");
183        let backend = target_or_default(log_target);
184
185        println!("Access logs will be sent to {access_logs_target:?}");
186        let access_backend = access_logs_target.map(target_to_backend).transpose()?;
187
188        let (directives, _errors) = parse_logging_spec(spec);
189        LOGGER.with(|logger| {
190            let mut logger = logger.borrow_mut();
191            if !logger.initialized {
192                logger.set_directives(directives);
193                logger.colored = match backend {
194                    LoggerBackend::Stdout(_) => colored,
195                    _ => false,
196                };
197                // Mirror into the side-channel cell via the canonical
198                // accessor so `is_logger_colored()` can be called from
199                // inside log-macro argument formatters without re-borrowing
200                // the main `LOGGER` cell (it is held mutably here and by
201                // every subsequent `error!`/`info!`/...). Going through
202                // `Logger::is_colored` (instead of the `Deref`'d
203                // `logger.colored`) makes the single source of truth
204                // explicit at the call site.
205                LOGGER_COLORED.with(|c| c.set(logger.is_colored()));
206                logger.access_colored = match (&access_backend, &backend) {
207                    (Some(LoggerBackend::Stdout(_)), _) | (None, LoggerBackend::Stdout(_)) => {
208                        access_colored.unwrap_or(colored)
209                    }
210                    _ => false,
211                };
212                logger.backend = backend;
213                logger.log_target = log_target.to_owned();
214                logger.access_backend = access_backend;
215                logger.access_logs_target = access_logs_target.map(ToOwned::to_owned);
216                logger.access_format = access_format.unwrap_or(AccessLogFormat::Ascii);
217                logger.tag = tag;
218                // SAFETY: `libc::getpid` takes no input pointers, never
219                // fails, and returns a value type. No invariant beyond
220                // "FFI signature matches libc".
221                logger.pid = unsafe { libc::getpid() };
222                logger.initialized = true;
223
224                let _ = log::set_logger(&COMPAT_LOGGER)
225                    .map_err(|e| println!("could not register compat logger: {e:?}"));
226
227                log::set_max_level(log::LevelFilter::Info);
228            }
229        });
230        Ok(())
231    }
232
233    pub fn set_directives(&mut self, directives: Vec<LogDirective>) {
234        self.directives = directives;
235    }
236
237    pub fn split(&mut self) -> (i32, &str, &mut InnerLogger) {
238        (self.pid, &self.tag, &mut self.inner)
239    }
240
241    /// Returns `true` when the logger emits colored output on its main backend.
242    pub fn is_colored(&self) -> bool {
243        self.inner.colored
244    }
245}
246
247struct LoggerBuffer(Vec<u8>);
248
249impl Deref for LoggerBuffer {
250    type Target = Vec<u8>;
251    fn deref(&self) -> &Self::Target {
252        &self.0
253    }
254}
255impl DerefMut for LoggerBuffer {
256    fn deref_mut(&mut self) -> &mut Self::Target {
257        &mut self.0
258    }
259}
260
261impl LoggerBuffer {
262    fn fmt<F: FnOnce(&[u8]) -> Result<usize, IoError>>(
263        &mut self,
264        args: Arguments,
265        flush: F,
266    ) -> Result<(), IoError> {
267        self.clear();
268        self.write_fmt(args)?;
269        flush(self.as_slice())?;
270        Ok(())
271    }
272}
273
274fn log_arguments(
275    args: Arguments,
276    backend: &mut LoggerBackend,
277    buffer: &mut LoggerBuffer,
278) -> Result<(), IoError> {
279    match backend {
280        LoggerBackend::Stdout(stdout) => {
281            let _ = stdout.write_fmt(args);
282            Ok(())
283        }
284        LoggerBackend::Tcp(socket) => socket.write_fmt(args),
285        LoggerBackend::File(file) => file.write_fmt(args),
286        LoggerBackend::Unix(socket) => buffer.fmt(args, |bytes| socket.send(bytes)),
287        LoggerBackend::Udp(sock, addr) => buffer.fmt(args, |b| sock.send_to(b, *addr)),
288    }
289}
290
291impl InnerLogger {
292    pub fn log(&mut self, args: Arguments) {
293        if let Err(e) = log_arguments(args, &mut self.backend, &mut self.buffer) {
294            println!("Could not write log to {}: {e:?}", self.backend.as_ref());
295        }
296    }
297
298    /// write an access log to the proper logging target
299    ///
300    /// Protobuf access logs are written with a prost length delimiter before, and 2 empty bytes after
301    pub fn log_access(&mut self, log: RequestRecord) -> bool {
302        let backend = self.access_backend.as_mut().unwrap_or(&mut self.backend);
303
304        let io_result = match self.access_format {
305            AccessLogFormat::Protobuf => {
306                let binary_log = log.into_binary_access_log();
307                let log_length = binary_log.encoded_len();
308                let total_length = log_length + encoded_len_varint(log_length as u64);
309                self.buffer.clear();
310                let current_capacity = self.buffer.capacity();
311                if current_capacity < total_length {
312                    self.buffer.reserve(total_length - current_capacity);
313                }
314
315                if let Err(e) = binary_log.encode_length_delimited(&mut self.buffer.0) {
316                    Err(IoError::new(IoErrorKind::InvalidData, e))
317                } else {
318                    self.buffer.extend_from_slice(&[0, 0]); // add two empty bytes after each protobuf access log
319                    let bytes = &self.buffer;
320                    match backend {
321                        LoggerBackend::Stdout(stdout) => {
322                            let _ = stdout.write(bytes);
323                            return true;
324                        }
325                        LoggerBackend::Tcp(socket) => socket.write(bytes),
326                        LoggerBackend::File(file) => file.write(bytes),
327                        LoggerBackend::Unix(socket) => socket.send(bytes),
328                        LoggerBackend::Udp(socket, address) => socket.send_to(bytes, *address),
329                    }
330                    .map(|_| ())
331                }
332            }
333            AccessLogFormat::Ascii => crate::_prompt_log! {
334                logger: |args| log_arguments(args, backend, &mut self.buffer),
335                is_access: true,
336                condition: self.access_colored,
337                prompt: [
338                    log.now,
339                    log.precise_time,
340                    log.pid,
341                    log.level,
342                    log.tag,
343                ],
344                standard: {
345                    formats: ["{} {} {} {}/{}/{}/{}/{} {} {} [{}] {:?} {} {}{}\n"],
346                    args: [
347                        log.context,
348                        log.session_address.as_string_or("-"),
349                        log.backend_address.as_string_or("-"),
350                        LogDuration(Some(log.request_time)),
351                        LogDuration(Some(log.service_time)),
352                        LogDuration(log.response_time),
353                        LogDuration(log.client_rtt),
354                        LogDuration(log.server_rtt),
355                        log.bytes_in,
356                        log.bytes_out,
357                        log.full_tags(),
358                        log.otel,
359                        log.protocol,
360                        log.endpoint,
361                        LogMessage(log.message),
362                    ]
363                },
364                colored: {
365                    formats: ["\x1b[;1m{}\x1b[m {} {} {}/{}/{}/{}/{} {} {} \x1b[2m[{}] {:?} \x1b[;1m{} {:#}\x1b[m{}\n"],
366                    args: @,
367                }
368            },
369        };
370
371        if let Err(e) = io_result {
372            println!("Could not write access log to {}: {e:?}", backend.as_ref());
373            println!(
374                "Trying to revive the backend of access logs to {:?}, or defaulting to {}",
375                self.access_logs_target, self.log_target
376            );
377            let log_target = self.access_logs_target.as_ref().unwrap_or(&self.log_target);
378            if let Err(err) = backend.revive(log_target) {
379                eprintln!("could not revive logger backend: {err}");
380            }
381            false
382        } else {
383            true
384        }
385    }
386
387    pub fn enabled(&self, meta: Metadata) -> bool {
388        // Search for the longest match, the vector is assumed to be pre-sorted.
389        for directive in self.directives.iter().rev() {
390            match &directive.name {
391                Some(name) if !meta.target.starts_with(name) => {}
392                Some(_) | None => return meta.level <= directive.level,
393            }
394        }
395        false
396    }
397
398    fn compat_enabled(&self, meta: &log::Metadata) -> bool {
399        // Search for the longest match, the vector is assumed to be pre-sorted.
400        for directive in self.directives.iter().rev() {
401            match &directive.name {
402                Some(name) if !meta.target().starts_with(name) => {}
403                Some(_) | None => return LogLevel::from(meta.level()) <= directive.level,
404            }
405        }
406        false
407    }
408}
409
410pub enum LoggerBackend {
411    Stdout(Stdout),
412    Unix(UnixDatagram),
413    Udp(UdpSocket, SocketAddr),
414    Tcp(TcpStream),
415    File(crate::writer::MultiLineWriter<File>),
416}
417
418impl LoggerBackend {
419    fn revive(&mut self, log_target: &str) -> Result<(), LogError> {
420        *self = target_to_backend(log_target)?;
421        Ok(())
422    }
423}
424
425#[repr(usize)]
426#[derive(Clone, Copy, Eq, Debug)]
427pub enum LogLevel {
428    /// The "error" level.
429    ///
430    /// Designates very serious errors.
431    Error = 1, // This way these line up with the discriminants for LogLevelFilter below
432    /// The "warn" level.
433    ///
434    /// Designates hazardous situations.
435    Warn,
436    /// The "info" level.
437    ///
438    /// Designates useful information.
439    Info,
440    /// The "debug" level.
441    ///
442    /// Designates lower priority information.
443    Debug,
444    /// The "trace" level.
445    ///
446    /// Designates very low priority, often extremely verbose, information.
447    Trace,
448}
449
450static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
451
452impl PartialEq for LogLevel {
453    #[inline]
454    fn eq(&self, other: &LogLevel) -> bool {
455        *self as usize == *other as usize
456    }
457}
458
459impl PartialEq<LogLevelFilter> for LogLevel {
460    #[inline]
461    fn eq(&self, other: &LogLevelFilter) -> bool {
462        *self as usize == *other as usize
463    }
464}
465
466impl PartialOrd for LogLevel {
467    #[inline]
468    fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
469        Some(self.cmp(other))
470    }
471}
472
473impl PartialOrd<LogLevelFilter> for LogLevel {
474    #[inline]
475    fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
476        Some((*self as usize).cmp(&(*other as usize)))
477    }
478}
479
480impl Ord for LogLevel {
481    #[inline]
482    fn cmp(&self, other: &LogLevel) -> cmp::Ordering {
483        (*self as usize).cmp(&(*other as usize))
484    }
485}
486
487impl LogLevel {
488    fn from_usize(u: usize) -> Option<LogLevel> {
489        match u {
490            1 => Some(LogLevel::Error),
491            2 => Some(LogLevel::Warn),
492            3 => Some(LogLevel::Info),
493            4 => Some(LogLevel::Debug),
494            5 => Some(LogLevel::Trace),
495            _ => None,
496        }
497    }
498
499    /// Returns the most verbose logging level.
500    #[inline]
501    pub fn max() -> LogLevel {
502        LogLevel::Trace
503    }
504
505    /// Converts the `LogLevel` to the equivalent `LogLevelFilter`.
506    #[inline]
507    pub fn to_log_level_filter(self) -> LogLevelFilter {
508        LogLevelFilter::from_usize(self as usize).unwrap()
509    }
510}
511
512#[repr(usize)]
513#[derive(Clone, Copy, Eq, Debug)]
514pub enum LogLevelFilter {
515    Off,
516    Error,
517    Warn,
518    Info,
519    Debug,
520    Trace,
521}
522
523impl PartialEq for LogLevelFilter {
524    #[inline]
525    fn eq(&self, other: &LogLevelFilter) -> bool {
526        *self as usize == *other as usize
527    }
528}
529
530impl PartialEq<LogLevel> for LogLevelFilter {
531    #[inline]
532    fn eq(&self, other: &LogLevel) -> bool {
533        other.eq(self)
534    }
535}
536
537impl PartialOrd for LogLevelFilter {
538    #[inline]
539    fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
540        Some(self.cmp(other))
541    }
542}
543
544impl PartialOrd<LogLevel> for LogLevelFilter {
545    #[inline]
546    fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
547        other.partial_cmp(self).map(|x| x.reverse())
548    }
549}
550
551impl Ord for LogLevelFilter {
552    #[inline]
553    fn cmp(&self, other: &LogLevelFilter) -> cmp::Ordering {
554        (*self as usize).cmp(&(*other as usize))
555    }
556}
557
558impl FromStr for LogLevelFilter {
559    type Err = ();
560    fn from_str(level: &str) -> Result<LogLevelFilter, ()> {
561        LOG_LEVEL_NAMES
562            .iter()
563            .position(|&name| name.eq_ignore_ascii_case(level))
564            .map(|p| LogLevelFilter::from_usize(p).unwrap())
565            .ok_or(())
566    }
567}
568
569impl LogLevelFilter {
570    fn from_usize(u: usize) -> Option<LogLevelFilter> {
571        match u {
572            0 => Some(LogLevelFilter::Off),
573            1 => Some(LogLevelFilter::Error),
574            2 => Some(LogLevelFilter::Warn),
575            3 => Some(LogLevelFilter::Info),
576            4 => Some(LogLevelFilter::Debug),
577            5 => Some(LogLevelFilter::Trace),
578            _ => None,
579        }
580    }
581    /// Returns the most verbose logging level filter.
582    #[inline]
583    pub fn max() -> LogLevelFilter {
584        LogLevelFilter::Trace
585    }
586
587    /// Converts `self` to the equivalent `LogLevel`.
588    ///
589    /// Returns `None` if `self` is `LogLevelFilter::Off`.
590    #[inline]
591    pub fn to_log_level(self) -> Option<LogLevel> {
592        LogLevel::from_usize(self as usize)
593    }
594}
595
596/// Metadata about a log message.
597#[derive(Debug)]
598pub struct Metadata {
599    pub level: LogLevel,
600    pub target: &'static str,
601}
602
603#[derive(Debug)]
604pub struct LogDirective {
605    name: Option<String>,
606    level: LogLevelFilter,
607}
608
609#[derive(thiserror::Error, Debug)]
610pub enum LogSpecParseError {
611    #[error("Too many '/'s: {0}")]
612    TooManySlashes(String),
613    #[error("Too many '='s: {0}")]
614    TooManyEquals(String),
615    #[error("Invalid log level: {0}")]
616    InvalidLogLevel(String),
617}
618
619pub fn parse_logging_spec(spec: &str) -> (Vec<LogDirective>, Vec<LogSpecParseError>) {
620    let mut dirs = Vec::new();
621    let mut errors = Vec::new();
622
623    let mut parts = spec.split('/');
624    let mods = parts.next();
625    let _ = parts.next();
626    if parts.next().is_some() {
627        errors.push(LogSpecParseError::TooManySlashes(spec.to_string()));
628    }
629    if let Some(m) = mods {
630        for s in m.split(',') {
631            if s.is_empty() {
632                continue;
633            }
634            let mut parts = s.split('=');
635            let (log_level, name) =
636                match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
637                    (Some(part0), None, None) => {
638                        // if the single argument is a log-level string or number,
639                        // treat that as a global fallback
640                        match part0.parse() {
641                            Ok(num) => (num, None),
642                            Err(_) => {
643                                errors.push(LogSpecParseError::InvalidLogLevel(s.to_string()));
644                                (LogLevelFilter::max(), None)
645                            }
646                        }
647                    }
648                    (Some(part0), Some(""), None) => (LogLevelFilter::max(), Some(part0)),
649                    (Some(part0), Some(part1), None) => match part1.parse() {
650                        Ok(num) => (num, Some(part0)),
651                        Err(_) => {
652                            errors.push(LogSpecParseError::InvalidLogLevel(s.to_string()));
653                            continue;
654                        }
655                    },
656                    _ => {
657                        errors.push(LogSpecParseError::TooManyEquals(s.to_string()));
658                        continue;
659                    }
660                };
661            dirs.push(LogDirective {
662                name: name.map(|s| s.to_string()),
663                level: log_level,
664            });
665        }
666    }
667
668    for error in &errors {
669        println!("{error:?}");
670    }
671    (dirs, errors)
672}
673
674/// start the logger with all logs and access logs on stdout
675pub fn setup_default_logging(
676    log_colored: bool,
677    log_level: &str,
678    tag: &str,
679) -> Result<(), LogError> {
680    setup_logging("stdout", log_colored, None, None, None, log_level, tag)
681}
682
683/// start the logger from config (takes RUST_LOG into account)
684pub fn setup_logging_with_config(config: &Config, tag: &str) -> Result<(), LogError> {
685    setup_logging(
686        &config.log_target,
687        config.log_colored,
688        config.access_logs_target.as_deref(),
689        config.access_logs_format.clone(),
690        config.access_logs_colored,
691        &config.log_level,
692        tag,
693    )
694}
695
696/// start the logger, after:
697///
698/// - determining logging backends
699/// - taking RUST_LOG into account
700pub fn setup_logging(
701    log_target: &str,
702    log_colored: bool,
703    access_logs_target: Option<&str>,
704    access_logs_format: Option<AccessLogFormat>,
705    access_logs_colored: Option<bool>,
706    log_level: &str,
707    tag: &str,
708) -> Result<(), LogError> {
709    Logger::init(
710        tag.to_string(),
711        env::var("RUST_LOG").as_deref().unwrap_or(log_level),
712        log_target,
713        log_colored,
714        access_logs_target,
715        access_logs_format,
716        access_logs_colored,
717    )
718}
719
720/// defaults to stdout if the log target is unparseable
721fn target_or_default(target: &str) -> LoggerBackend {
722    match target_to_backend(target) {
723        Ok(backend) => backend,
724        Err(target_error) => {
725            eprintln!("{target_error}, defaulting to stdout");
726            LoggerBackend::Stdout(stdout())
727        }
728    }
729}
730
731pub fn target_to_backend(target: &str) -> Result<LoggerBackend, LogError> {
732    if target == "stdout" {
733        return Ok(LoggerBackend::Stdout(stdout()));
734    }
735
736    if let Some(addr) = target.strip_prefix("udp://") {
737        let address = addr
738            .parse::<SocketAddr>()
739            .map_err(|e| LogError::InvalidSocketAddress(target.to_owned(), e))?;
740
741        let socket = UdpSocket::bind(("0.0.0.0", 0)).map_err(LogError::UdpBind)?;
742
743        return Ok(LoggerBackend::Udp(socket, address));
744    }
745
746    if let Some(addr) = target.strip_prefix("tcp://") {
747        let tcp_stream =
748            TcpStream::connect(addr).map_err(|e| LogError::TcpConnect(target.to_owned(), e))?;
749
750        return Ok(LoggerBackend::Tcp(tcp_stream));
751    }
752
753    if let Some(addr) = target.strip_prefix("unix://") {
754        let socket = UnixDatagram::unbound().map_err(LogError::CreateUnixSocket)?;
755
756        socket
757            .connect(addr)
758            .map_err(|e| LogError::ConnectToUnixSocket(target.to_owned(), e))?;
759
760        return Ok(LoggerBackend::Unix(socket));
761    }
762
763    if let Some(addr) = target.strip_prefix("file://") {
764        let path = Path::new(addr);
765        let file = OpenOptions::new()
766            .create(true)
767            .append(true)
768            .open(path)
769            .map_err(|e| LogError::OpenFile(target.to_owned(), e))?;
770
771        return Ok(LoggerBackend::File(MultiLineWriter::new(file)));
772    }
773
774    Err(LogError::InvalidLogTarget(
775        target.to_owned(),
776        "Log target is not parseable",
777    ))
778}
779
780#[macro_export]
781macro_rules! _prompt_log {
782    {
783        logger: $logger:expr,
784        is_access: $access:expr,
785        condition: $cond:expr,
786        prompt: [$($p:tt)*],
787        standard: {$($std:tt)*}$(,)?
788    } => {
789        $crate::_prompt_log!{
790            logger: $logger,
791            is_access: $access,
792            condition: $cond,
793            prompt: [$($p)*],
794            standard: {$($std)*},
795            colored: {$($std)*},
796        }
797    };
798    {
799        logger: $logger:expr,
800        is_access: $access:expr,
801        condition: $cond:expr,
802        prompt: [$($p:tt)*],
803        standard: {
804            formats: [$($std_fmt:tt)*],
805            args: [$($std_args:expr),*$(,)?]$(,)?
806        },
807        colored: {
808            formats: [$($col_fmt:tt)*],
809            args: @$(,)?
810        }$(,)?
811    } => {
812        $crate::_prompt_log!{
813            logger: $logger,
814            is_access: $access,
815            condition: $cond,
816            prompt: [$($p)*],
817            standard: {
818                formats: [$($std_fmt)*],
819                args: [$($std_args),*],
820            },
821            colored: {
822                formats: [$($col_fmt)*],
823                args: [$($std_args),*],
824            },
825        }
826    };
827    {
828        logger: $logger:expr,
829        is_access: $access:expr,
830        condition: $cond:expr,
831        prompt: [$now:expr, $precise_time:expr, $pid:expr, $lvl:expr, $tag:expr$(,)?],
832        standard: {
833            formats: [$($std_fmt:tt)*],
834            args: [$($std_args:expr),*$(,)?]$(,)?
835        },
836        colored: {
837            formats: [$($col_fmt:tt)*],
838            args: [$($col_args:expr),*$(,)?]$(,)?
839        }$(,)?
840    } => {
841        if $cond {
842            $crate::_prompt_log!(@bind [$logger, concat!("{} \x1b[2m{} \x1b[;2;1m{} {} \x1b[0;1m{}\x1b[m\t", $($col_fmt)*)] [$now, $precise_time, $pid, $lvl.as_str($access, true), $tag] $($col_args),*)
843        } else {
844            $crate::_prompt_log!(@bind [$logger, concat!("{} {} {} {} {}\t", $($std_fmt)*)] [$now, $precise_time, $pid, $lvl.as_str($access, false), $tag] $($std_args),*)
845        }
846    };
847    (@bind [$logger:expr, $fmt:expr] [$($bindings:expr),*] $arg:expr $(, $args:expr)*) => {{
848        let binding = &$arg;
849        $crate::_prompt_log!(@bind [$logger, $fmt] [$($bindings),* , binding] $($args),*)
850    }};
851    (@bind [$logger:expr, $fmt:expr] [$($bindings:expr),*]) => {
852        $logger(format_args!($fmt, $($bindings),*))
853    };
854}
855
856#[derive(Clone, Copy, Debug)]
857pub struct LogLineCachedState(u8);
858const LOG_LINE_ENABLED: u8 = 1 << 7;
859
860impl Default for LogLineCachedState {
861    fn default() -> Self {
862        Self::new()
863    }
864}
865
866impl LogLineCachedState {
867    pub const fn new() -> Self {
868        Self(0)
869    }
870    #[inline(always)]
871    pub fn version(&self) -> u8 {
872        self.0 & !LOG_LINE_ENABLED
873    }
874    #[inline(always)]
875    pub fn enabled(&self) -> bool {
876        self.0 & LOG_LINE_ENABLED != 0
877    }
878    #[inline(always)]
879    pub fn set(&mut self, version: u8, enabled: bool) {
880        self.0 = version;
881        if enabled {
882            self.0 |= LOG_LINE_ENABLED
883        }
884    }
885}
886
887#[macro_export]
888macro_rules! _log_enabled {
889    ($logger:expr, $lvl:expr) => {{
890        let logger = $logger.borrow_mut();
891        if !logger.enabled($crate::logging::Metadata {
892            level: $lvl,
893            target: module_path!(),
894        }) {
895            return;
896        }
897        logger
898    }};
899}
900
901#[macro_export]
902macro_rules! _log {
903    ($lvl:expr, $format:expr $(, $args:expr)*) => {{
904        $crate::logging::LOGGER.with(|logger| {
905            let mut logger = $crate::_log_enabled!(logger, $lvl);
906            let (pid, tag, inner) = logger.split();
907            let (now, precise_time) = $crate::logging::now();
908
909            $crate::_prompt_log!{
910                logger: |args| inner.log(args),
911                is_access: false,
912                condition: inner.colored,
913                prompt: [now, precise_time, pid, $lvl, tag],
914                standard: {
915                    formats: [$format, '\n'],
916                    args: [$($args),*]
917                }
918            };
919        })
920    }};
921}
922
923#[macro_export]
924macro_rules! _log_access {
925    ($lvl:expr, $on_failure:block, $($request_record_fields:tt)*) => {{
926         $crate::logging::LOGGER.with(|logger| {
927            let success = {
928                let mut logger = $crate::_log_enabled!(logger, $lvl);
929                let (pid, tag, inner) = logger.split();
930                let (now, precise_time) = $crate::logging::now();
931
932                inner.log_access(
933                    $crate::_structured_access_log!(
934                        [$crate::logging::RequestRecord]
935                        pid, tag, now, precise_time, level: $lvl, $($request_record_fields)*
936                    )
937                )
938            }; // logger dropped here
939
940            if !success {
941                // recording this metric may borrow the logger, so we have
942                // to perform this action after the block above
943                $on_failure
944            }
945        });
946    }};
947}
948
949#[macro_export]
950macro_rules! _structured_access_log {
951    ([$($struct_name:tt)+] $($fields:tt)*) => {{
952        $($struct_name)+ {$(
953            $fields
954        )*}
955    }};
956}
957
958#[macro_export]
959/// dynamically chose between info_access and error_access
960macro_rules! log_access {
961    ($error:expr, on_failure: $on_failure:block, $($request_record_fields:tt)*) => {
962        let lvl = if $error {
963            $crate::logging::LogLevel::Error
964        } else {
965            $crate::logging::LogLevel::Info
966        };
967        _log_access!(lvl, $on_failure, $($request_record_fields)*);
968    };
969}
970
971/// log a failure concerning an HTTP or TCP request
972#[macro_export]
973macro_rules! error_access {
974    (on_failure: $on_failure:block, $($request_record_fields:tt)*) => {
975        $crate::_log_access!($crate::logging::LogLevel::Error, $on_failure, $($request_record_fields)*);
976    };
977}
978
979/// log the success of an HTTP or TCP request
980#[macro_export]
981macro_rules! info_access {
982    (on_failure: $on_failure:block, $($request_record_fields:tt)*) => {
983        $crate::_log_access!($crate::logging::LogLevel::Info, $on_failure, $($request_record_fields)*);
984    };
985}
986
987/// log an error with Sōzu's custom log stack
988#[macro_export]
989macro_rules! error {
990    ($format:expr $(, $args:expr)* $(,)?) => {
991        $crate::_log!($crate::logging::LogLevel::Error, $format $(, $args)*)
992    };
993}
994
995/// log a warning with Sōzu’s custom log stack
996#[macro_export]
997macro_rules! warn {
998    ($format:expr $(, $args:expr)* $(,)?) => {
999        $crate::_log!($crate::logging::LogLevel::Warn, $format $(, $args)*)
1000    };
1001}
1002
1003/// log an info with Sōzu’s custom log stack
1004#[macro_export]
1005macro_rules! info {
1006    ($format:expr $(, $args:expr)* $(,)?) => {
1007        $crate::_log!($crate::logging::LogLevel::Info, $format $(, $args)*)
1008    };
1009}
1010
1011/// log a debug with Sōzu’s custom log stack
1012#[macro_export]
1013macro_rules! debug {
1014    ($format:expr $(, $args:expr)* $(,)?) => {{
1015        #[cfg(any(debug_assertions, feature = "logs-debug", feature = "logs-trace"))]
1016        $crate::_log!($crate::logging::LogLevel::Debug, concat!("{}\t", $format), module_path!() $(, $args)*);
1017        // Dead-code arm silences `unused_variables` warnings on
1018        // `--no-default-features` builds where the emit arm is stripped.
1019        // Must gate on the SAME set of features as the emit arm above, or
1020        // else both arms compile in and double-move the caller's arguments
1021        // (observed on `release + logs-debug` builds where the emit arm
1022        // fires and the no-op arm also fires, each consuming `$args` once).
1023        #[cfg(not(any(debug_assertions, feature = "logs-debug", feature = "logs-trace")))]
1024        if false {$( let _ = $args; )*}
1025    }};
1026}
1027
1028/// log a trace with Sōzu’s custom log stack
1029#[macro_export]
1030macro_rules! trace {
1031    ($format:expr $(, $args:expr)* $(,)?) => {{
1032        #[cfg(any(debug_assertions, feature = "logs-trace"))]
1033        $crate::_log!($crate::logging::LogLevel::Trace, concat!("{}\t", $format), module_path!() $(, $args)*);
1034        #[cfg(not(any(debug_assertions, feature = "logs-trace")))]
1035        if false {$( let _ = $args; )*}
1036    }};
1037}
1038
1039/// write a log with a "FIXME" prefix on an info level
1040#[macro_export]
1041macro_rules! fixme {
1042    ($(, $args:expr)* $(,)?) => {
1043        $crate::_log!($crate::logging::LogLevel::Info, "FIXME: {}:{} in {}: {}", file!(), line!(), module_path!() $(, $args)*)
1044    };
1045}
1046
1047pub struct CompatLogger;
1048
1049impl From<log::Level> for LogLevel {
1050    fn from(lvl: log::Level) -> Self {
1051        match lvl {
1052            log::Level::Error => LogLevel::Error,
1053            log::Level::Warn => LogLevel::Warn,
1054            log::Level::Info => LogLevel::Info,
1055            log::Level::Debug => LogLevel::Debug,
1056            log::Level::Trace => LogLevel::Trace,
1057        }
1058    }
1059}
1060
1061impl log::Log for CompatLogger {
1062    fn enabled(&self, _: &log::Metadata) -> bool {
1063        true
1064    }
1065
1066    fn log(&self, record: &log::Record) {
1067        LOGGER.with(|logger| {
1068            let mut logger = logger.borrow_mut();
1069            if !logger.compat_enabled(record.metadata()) {
1070                return;
1071            }
1072            let (pid, tag, inner) = logger.split();
1073            let (now, precise_time) = now();
1074            crate::_prompt_log! {
1075                logger: |args| inner.log(args),
1076                is_access: false,
1077                condition: inner.colored,
1078                prompt: [
1079                    now, precise_time, pid, LogLevel::from(record.level()), tag
1080                ],
1081                standard: {
1082                    formats: ["{}\n"],
1083                    args: [record.args()]
1084                }
1085            };
1086        })
1087    }
1088
1089    fn flush(&self) {}
1090}
1091
1092/// start a logger used in test environment
1093#[macro_export]
1094macro_rules! setup_test_logger {
1095    () => {
1096        let _ = $crate::logging::Logger::init(
1097            module_path!().to_string(),
1098            "error",
1099            sozu_command_lib::config::DEFAULT_LOG_TARGET,
1100            false,
1101            None,
1102            None,
1103            None,
1104        );
1105    };
1106}
1107
1108pub struct Rfc3339Time {
1109    pub inner: ::time::OffsetDateTime,
1110}
1111
1112/// yields (Rfc3339Time, unix_epoch)
1113pub fn now() -> (Rfc3339Time, i128) {
1114    let t = time::OffsetDateTime::now_utc();
1115    (
1116        Rfc3339Time { inner: t },
1117        (t - time::OffsetDateTime::UNIX_EPOCH).whole_nanoseconds(),
1118    )
1119}