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