base/
syslog.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Facilities for sending log message to syslog.
6//!
7//! Every function exported by this module is thread-safe. Each function will silently fail until
8//! `syslog::init()` is called and returns `Ok`.
9//!
10//! This implements and sets logger up for logging facade exposed by the [`log`
11//! crate][log-crate-url].
12//!
13//! # Examples
14//!
15//! ```
16//! use log::{error, warn};
17//! use base::syslog;
18//!
19//! if let Err(e) = syslog::init() {
20//!     println!("failed to initiailize syslog: {}", e);
21//!     return;
22//! }
23//! warn!("this is your {} warning", "final");
24//! error!("something went horribly wrong: {}", "out of RAMs");
25//! ```
26//!
27//! ```
28//! use log::{error, warn};
29//! use base::syslog::{init_with, LogConfig, fmt};
30//! use std::io::Write;
31//!
32//! let mut cfg = LogConfig::default();
33//! cfg.log_args.stderr = true;
34//! cfg.log_args.filter = String::from("info,base=debug,base::syslog=error,serial_console=false");
35//!
36//! init_with(cfg).unwrap();
37//! error!("something went horribly wrong: {}", "out of RAMs");
38//! ```
39//!
40//!
41//! [log-crate-url]: https://docs.rs/log/
42
43use std::fmt::Display;
44use std::io;
45use std::io::Write;
46use std::sync::LazyLock;
47use std::sync::MutexGuard;
48
49use chrono::Utc;
50pub use env_logger::fmt;
51pub use env_logger::{self};
52pub use log::*;
53use remain::sorted;
54use serde::Deserialize;
55use serde::Serialize;
56use sync::Mutex;
57use thiserror::Error as ThisError;
58
59use crate::descriptor::AsRawDescriptor;
60use crate::platform::syslog::PlatformSyslog;
61use crate::platform::RawDescriptor;
62
63/// The priority (i.e. severity) of a syslog message.
64///
65/// See syslog man pages for information on their semantics.
66#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
67pub enum Priority {
68    Emergency = 0,
69    Alert = 1,
70    Critical = 2,
71    Error = 3,
72    Warning = 4,
73    Notice = 5,
74    Info = 6,
75    Debug = 7,
76}
77
78impl Display for Priority {
79    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80        use self::Priority::*;
81
82        let string = match self {
83            Emergency => "EMERGENCY",
84            Alert => "ALERT",
85            Critical => "CRITICAL",
86            Error => "ERROR",
87            Warning => "WARNING",
88            Notice => "NOTICE",
89            Info => "INFO",
90            Debug => "DEBUG",
91        };
92
93        write!(f, "{string}")
94    }
95}
96
97impl From<log::Level> for Priority {
98    fn from(level: log::Level) -> Self {
99        match level {
100            log::Level::Error => Priority::Error,
101            log::Level::Warn => Priority::Warning,
102            log::Level::Info => Priority::Info,
103            log::Level::Debug => Priority::Debug,
104            log::Level::Trace => Priority::Debug,
105        }
106    }
107}
108
109impl TryFrom<&str> for Priority {
110    type Error = &'static str;
111
112    fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
113        match value {
114            "0" | "EMERGENCY" => Ok(Priority::Emergency),
115            "1" | "ALERT" => Ok(Priority::Alert),
116            "2" | "CRITICAL" => Ok(Priority::Critical),
117            "3" | "ERROR" => Ok(Priority::Error),
118            "4" | "WARNING" => Ok(Priority::Warning),
119            "5" | "NOTICE" => Ok(Priority::Notice),
120            "6" | "INFO" => Ok(Priority::Info),
121            "7" | "DEBUG" => Ok(Priority::Debug),
122            _ => Err("Priority can only be parsed from 0-7 and given variant names"),
123        }
124    }
125}
126/// The facility of a syslog message.
127///
128/// See syslog man pages for information on their semantics.
129#[derive(Copy, Clone, serde::Deserialize, serde::Serialize)]
130pub enum Facility {
131    Kernel = 0,
132    User = 1 << 3,
133    Mail = 2 << 3,
134    Daemon = 3 << 3,
135    Auth = 4 << 3,
136    Syslog = 5 << 3,
137    Lpr = 6 << 3,
138    News = 7 << 3,
139    Uucp = 8 << 3,
140    Local0 = 16 << 3,
141    Local1 = 17 << 3,
142    Local2 = 18 << 3,
143    Local3 = 19 << 3,
144    Local4 = 20 << 3,
145    Local5 = 21 << 3,
146    Local6 = 22 << 3,
147    Local7 = 23 << 3,
148}
149
150/// Errors returned by `syslog::init()`.
151#[sorted]
152#[derive(ThisError, Debug)]
153pub enum Error {
154    /// Error while attempting to connect socket.
155    #[error("failed to connect socket: {0}")]
156    Connect(io::Error),
157    /// There was an error using `open` to get the lowest file descriptor.
158    #[error("failed to get lowest file descriptor: {0}")]
159    GetLowestFd(io::Error),
160    /// The guess of libc's file descriptor for the syslog connection was invalid.
161    #[error("guess of fd for syslog connection was invalid")]
162    InvalidFd,
163    /// Initialization was never attempted.
164    #[error("initialization was never attempted")]
165    NeverInitialized,
166    /// Initialization has previously failed and can not be retried.
167    #[error("initialization previously failed and cannot be retried")]
168    Poisoned,
169    /// Error while creating socket.
170    #[error("failed to create socket: {0}")]
171    Socket(io::Error),
172}
173
174pub(crate) trait Syslog {
175    fn new(
176        proc_name: String,
177        facility: Facility,
178    ) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), &'static Error>;
179}
180
181pub struct State {
182    /// Record filter
183    filter: env_logger::filter::Filter,
184    /// All the loggers we have
185    loggers: Vec<Box<dyn Log + Send>>,
186    /// Raw Descriptors to preserve
187    descriptors: Vec<RawDescriptor>,
188    /// True if we have just been initialized with safe startup defaults (stderr logging), false
189    /// after detailed initialization has occurred.
190    early_init: bool,
191}
192
193/// The logger that is provided to the `log` crate. Wraps our State struct so that we can
194/// reconfigure logging sinks on the fly.
195struct LoggingFacade {}
196
197impl Log for LoggingFacade {
198    fn enabled(&self, metadata: &log::Metadata) -> bool {
199        STATE.lock().enabled(metadata)
200    }
201
202    fn log(&self, record: &log::Record) {
203        STATE.lock().log(record)
204    }
205
206    fn flush(&self) {
207        STATE.lock().flush()
208    }
209}
210
211#[derive(Clone, serde::Deserialize, serde::Serialize)]
212pub struct LogArgs {
213    /// A filter for log messages. Please see
214    /// module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
215    ///
216    /// Example: `off`, `trace`, `trace,crosvm=error,base::syslog=debug`
217    pub filter: String,
218    /// If set to true will duplicate output to stderr
219    pub stderr: bool,
220    /// TAG to use for syslog output
221    pub proc_name: String,
222    /// Enable/disable platform's "syslog"
223    pub syslog: bool,
224    /// Facility to use for syslog output
225    pub syslog_facility: Facility,
226}
227
228impl Default for LogArgs {
229    fn default() -> Self {
230        Self {
231            filter: String::from("info"),
232            stderr: true,
233            proc_name: String::from("crosvm"),
234            syslog: true,
235            syslog_facility: Facility::User,
236        }
237    }
238}
239
240#[derive(Default)]
241pub struct LogConfig {
242    /// Logging configuration arguments.
243    pub log_args: LogArgs,
244    /// If specified will output to given Sink
245    pub pipe: Option<Box<dyn io::Write + Send>>,
246    /// descriptor to preserve on forks (intended to be used with pipe)
247    pub pipe_fd: Option<RawDescriptor>,
248    /// A formatter to use with the pipe. (Syslog has hardcoded format)
249    /// see module level documentation and [`env_logger` crate](https://docs.rs/env_logger)
250    pub pipe_formatter: Option<
251        Box<dyn Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send>,
252    >,
253}
254
255impl State {
256    pub fn new(cfg: LogConfig) -> Result<Self, Error> {
257        let mut loggers: Vec<Box<dyn Log + Send>> = vec![];
258        let mut descriptors = vec![];
259        let mut builder = env_logger::filter::Builder::new();
260        builder.parse(&cfg.log_args.filter);
261        let filter = builder.build();
262
263        let create_formatted_builder = || {
264            let mut builder = env_logger::Builder::new();
265
266            // Output log lines w/ local ISO 8601 timestamps.
267            builder.format(|buf, record| {
268                writeln!(
269                    buf,
270                    "[{} {:5} {}] {}",
271                    Utc::now().format("%Y-%m-%dT%H:%M:%S%.9f%:z"),
272                    record.level(),
273                    record.module_path().unwrap_or("<missing module path>"),
274                    record.args()
275                )
276            });
277            builder
278        };
279
280        if cfg.log_args.stderr {
281            let mut builder = create_formatted_builder();
282            builder.filter_level(log::LevelFilter::Trace);
283            builder.target(env_logger::Target::Stderr);
284            loggers.push(Box::new(builder.build()));
285            descriptors.push(std::io::stderr().as_raw_descriptor());
286        }
287
288        if let Some(fd) = cfg.pipe_fd {
289            descriptors.push(fd);
290        }
291
292        if let Some(file) = cfg.pipe {
293            let mut builder = create_formatted_builder();
294            builder.filter_level(log::LevelFilter::Trace);
295            builder.target(env_logger::Target::Pipe(Box::new(file)));
296            // https://github.com/env-logger-rs/env_logger/issues/208
297            builder.is_test(true);
298
299            if let Some(format) = cfg.pipe_formatter {
300                builder.format(format);
301            }
302            loggers.push(Box::new(builder.build()));
303        }
304
305        if cfg.log_args.syslog {
306            match PlatformSyslog::new(cfg.log_args.proc_name, cfg.log_args.syslog_facility) {
307                Ok((mut logger, fd)) => {
308                    if let Some(fd) = fd {
309                        descriptors.push(fd);
310                    }
311                    if let Some(logger) = logger.take() {
312                        loggers.push(logger);
313                    }
314                }
315                Err(e) => {
316                    // The default log configuration used in early_init() enables syslog, so we
317                    // don't want to terminate the program if syslog can't be initialized. Warn the
318                    // user but continue running.
319                    eprintln!("syslog init failed: {e}");
320                }
321            }
322        }
323
324        Ok(State {
325            filter,
326            loggers,
327            descriptors,
328            early_init: false,
329        })
330    }
331}
332
333impl Default for State {
334    fn default() -> Self {
335        Self::new(Default::default()).unwrap()
336    }
337}
338
339static STATE: LazyLock<Mutex<State>> = LazyLock::new(|| {
340    let mut state = State::new(LogConfig::default()).expect("failed to configure minimal logging");
341    state.early_init = true;
342    Mutex::new(state)
343});
344static LOGGING_FACADE: LoggingFacade = LoggingFacade {};
345static EARLY_INIT_CALLED: Mutex<bool> = Mutex::new(false);
346
347/// Initialize the syslog connection and internal variables.
348///
349/// This should only be called once per process before any other threads have been spawned or any
350/// signal handlers have been registered. Every call made after the first will panic.
351///
352/// Use `init_with_filter` to initialize with filtering
353pub fn init() -> Result<(), Error> {
354    init_with(Default::default())
355}
356
357/// Initialize the syslog connection and internal variables.
358///
359/// This should only be called once per process before any other threads have been spawned or any
360/// signal handlers have been registered. Every call made after the first will
361/// panic.
362///
363/// Arguments:
364/// * filter: See <https://docs.rs/env_logger/0.9/env_logger/index.html> for example filter
365///   specifications
366/// * stderr: If set will output to stderr (in addition)
367/// * file:  If set will output to this file (in addition)
368/// * proc_name: proc name for Syslog implementation
369/// * syslog_facility: syslog facility
370/// * file_formatter: custom formatter for file output. See env_logger docs
371pub fn init_with(cfg: LogConfig) -> Result<(), Error> {
372    let mut state = STATE.lock();
373    if !state.early_init {
374        panic!("double-init of the logging system is not permitted.");
375    }
376    *state = State::new(cfg)?;
377
378    // This has no effect if the logging facade was already set.
379    apply_logging_state(&LOGGING_FACADE);
380
381    Ok(())
382}
383
384/// Performs early (as in, moment of process start) logging initialization. Any logging prior to
385/// this call will be SILENTLY discarded. Calling more than once per process will panic.
386pub fn early_init() {
387    let mut early_init_called = EARLY_INIT_CALLED.lock();
388    if !*early_init_called {
389        apply_logging_state(&LOGGING_FACADE);
390        *early_init_called = true;
391    } else {
392        panic!("double early init of the logging system is not permitted.");
393    }
394}
395
396/// Test only function that ensures logging has been configured. Since tests
397/// share module state, we need a way to make sure it has been initialized
398/// with *some* configuration.
399pub fn test_only_ensure_inited() -> Result<(), Error> {
400    let mut early_init_called = EARLY_INIT_CALLED.lock();
401    if !*early_init_called {
402        apply_logging_state(&LOGGING_FACADE);
403        *early_init_called = true;
404    }
405    Ok(())
406}
407
408fn apply_logging_state(facade: &'static LoggingFacade) {
409    let _ = log::set_logger(facade);
410    log::set_max_level(log::LevelFilter::Trace);
411}
412
413/// Retrieves the file descriptors owned by the global syslogger.
414///
415/// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
416/// pushed into `fds`.
417///
418/// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
419pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
420    let state = STATE.lock();
421    fds.extend(state.descriptors.iter());
422}
423
424impl Log for State {
425    fn enabled(&self, metadata: &log::Metadata) -> bool {
426        self.filter.enabled(metadata)
427    }
428
429    fn log(&self, record: &log::Record) {
430        if self.filter.matches(record) {
431            for logger in self.loggers.iter() {
432                logger.log(record)
433            }
434        }
435    }
436
437    fn flush(&self) {
438        for logger in self.loggers.iter() {
439            logger.flush()
440        }
441    }
442}
443
444// Struct that implements io::Write to be used for writing directly to the syslog
445pub struct Syslogger<'a> {
446    buf: Vec<u8>,
447    level: log::Level,
448    get_state_fn: Box<dyn Fn() -> MutexGuard<'a, State> + Send + 'a>,
449}
450
451impl<'a> Syslogger<'a> {
452    pub fn new(level: log::Level) -> Syslogger<'a> {
453        Syslogger {
454            buf: Vec::new(),
455            level,
456            get_state_fn: Box::new(|| STATE.lock()),
457        }
458    }
459    pub fn test_only_from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>(
460        level: log::Level,
461        get_state_fn: F,
462    ) -> Syslogger<'a> {
463        Syslogger {
464            buf: Vec::new(),
465            level,
466            get_state_fn: Box::new(get_state_fn),
467        }
468    }
469}
470
471impl io::Write for Syslogger<'_> {
472    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
473        let state = (self.get_state_fn)();
474        self.buf.extend_from_slice(buf);
475
476        if let Some(last_newline_idx) = self.buf.iter().rposition(|&x| x == b'\n') {
477            for line in (self.buf[..last_newline_idx]).split(|&x| x == b'\n') {
478                // Also drop CR+LF line endings.
479                let send_line = match line.split_last() {
480                    Some((b'\r', trimmed)) => trimmed,
481                    _ => line,
482                };
483                // Match is to explicitly limit lifetime of args
484                // https://github.com/rust-lang/rust/issues/92698
485                // https://github.com/rust-lang/rust/issues/15023
486                #[allow(clippy::match_single_binding)]
487                match format_args!("{}", String::from_utf8_lossy(send_line)) {
488                    args => {
489                        let mut record_builder = log::Record::builder();
490                        record_builder.level(self.level);
491                        record_builder.target("syslogger");
492                        record_builder.args(args);
493                        let record = record_builder.build();
494                        state.log(&record);
495                    }
496                }
497            }
498
499            self.buf.drain(..=last_newline_idx);
500        }
501        Ok(buf.len())
502    }
503
504    fn flush(&mut self) -> io::Result<()> {
505        STATE.lock().flush();
506        Ok(())
507    }
508}