xref: /aosp_15_r20/external/crosvm/base/src/syslog.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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 
43 use std::fmt::Display;
44 use std::io;
45 use std::io::Write;
46 use std::sync::MutexGuard;
47 
48 use chrono::Utc;
49 pub use env_logger::fmt;
50 pub use env_logger::{self};
51 pub use log::*;
52 use once_cell::sync::Lazy;
53 use remain::sorted;
54 use serde::Deserialize;
55 use serde::Serialize;
56 use sync::Mutex;
57 use thiserror::Error as ThisError;
58 
59 use crate::descriptor::AsRawDescriptor;
60 use crate::platform::syslog::PlatformSyslog;
61 use 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)]
67 pub 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 
78 impl Display for Priority {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result79     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 
97 impl From<log::Level> for Priority {
from(level: log::Level) -> Self98     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 
109 impl TryFrom<&str> for Priority {
110     type Error = &'static str;
111 
try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error>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)]
130 pub 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)]
153 pub 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 
174 pub(crate) trait Syslog {
new( proc_name: String, facility: Facility, ) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), Error>175     fn new(
176         proc_name: String,
177         facility: Facility,
178     ) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), Error>;
179 }
180 
181 pub 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.
195 struct LoggingFacade {}
196 
197 impl Log for LoggingFacade {
enabled(&self, metadata: &log::Metadata) -> bool198     fn enabled(&self, metadata: &log::Metadata) -> bool {
199         STATE.lock().enabled(metadata)
200     }
201 
log(&self, record: &log::Record)202     fn log(&self, record: &log::Record) {
203         STATE.lock().log(record)
204     }
205 
flush(&self)206     fn flush(&self) {
207         STATE.lock().flush()
208     }
209 }
210 
211 #[derive(Clone, serde::Deserialize, serde::Serialize)]
212 pub 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 
228 impl Default for LogArgs {
default() -> Self229     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)]
241 pub 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 
255 impl State {
new(cfg: LogConfig) -> Result<Self, Error>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 
333 impl Default for State {
default() -> Self334     fn default() -> Self {
335         Self::new(Default::default()).unwrap()
336     }
337 }
338 
339 static STATE: Lazy<Mutex<State>> = Lazy::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 });
344 static LOGGING_FACADE: LoggingFacade = LoggingFacade {};
345 static 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
init() -> Result<(), Error>353 pub 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
init_with(cfg: LogConfig) -> Result<(), Error>371 pub 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.
early_init()386 pub 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.
test_only_ensure_inited() -> Result<(), Error>399 pub 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 
apply_logging_state(facade: &'static LoggingFacade)408 fn 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.
push_descriptors(fds: &mut Vec<RawDescriptor>)419 pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
420     let state = STATE.lock();
421     fds.extend(state.descriptors.iter());
422 }
423 
424 impl Log for State {
enabled(&self, metadata: &log::Metadata) -> bool425     fn enabled(&self, metadata: &log::Metadata) -> bool {
426         self.filter.enabled(metadata)
427     }
428 
log(&self, record: &log::Record)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 
flush(&self)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
445 pub 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 
451 impl<'a> Syslogger<'a> {
new(level: log::Level) -> 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     }
test_only_from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>( level: log::Level, get_state_fn: F, ) -> Syslogger<'a>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 
471 impl<'a> io::Write for Syslogger<'a> {
write(&mut self, buf: &[u8]) -> io::Result<usize>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 
flush(&mut self) -> io::Result<()>504     fn flush(&mut self) -> io::Result<()> {
505         STATE.lock().flush();
506         Ok(())
507     }
508 }
509