1use 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#[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#[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#[sorted]
152#[derive(ThisError, Debug)]
153pub enum Error {
154 #[error("failed to connect socket: {0}")]
156 Connect(io::Error),
157 #[error("failed to get lowest file descriptor: {0}")]
159 GetLowestFd(io::Error),
160 #[error("guess of fd for syslog connection was invalid")]
162 InvalidFd,
163 #[error("initialization was never attempted")]
165 NeverInitialized,
166 #[error("initialization previously failed and cannot be retried")]
168 Poisoned,
169 #[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 filter: env_logger::filter::Filter,
184 loggers: Vec<Box<dyn Log + Send>>,
186 descriptors: Vec<RawDescriptor>,
188 early_init: bool,
191}
192
193struct 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 pub filter: String,
218 pub stderr: bool,
220 pub proc_name: String,
222 pub syslog: bool,
224 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 pub log_args: LogArgs,
244 pub pipe: Option<Box<dyn io::Write + Send>>,
246 pub pipe_fd: Option<RawDescriptor>,
248 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 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 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 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
347pub fn init() -> Result<(), Error> {
354 init_with(Default::default())
355}
356
357pub 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 apply_logging_state(&LOGGING_FACADE);
380
381 Ok(())
382}
383
384pub 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
396pub 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
413pub 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
444pub 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 let send_line = match line.split_last() {
480 Some((b'\r', trimmed)) => trimmed,
481 _ => line,
482 };
483 #[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}