use std::fmt::Display;
use std::io;
use std::io::Write;
use std::sync::LazyLock;
use std::sync::MutexGuard;
use chrono::Utc;
pub use env_logger::fmt;
pub use env_logger::{self};
pub use log::*;
use remain::sorted;
use serde::Deserialize;
use serde::Serialize;
use sync::Mutex;
use thiserror::Error as ThisError;
use crate::descriptor::AsRawDescriptor;
use crate::platform::syslog::PlatformSyslog;
use crate::platform::RawDescriptor;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Priority {
Emergency = 0,
Alert = 1,
Critical = 2,
Error = 3,
Warning = 4,
Notice = 5,
Info = 6,
Debug = 7,
}
impl Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use self::Priority::*;
let string = match self {
Emergency => "EMERGENCY",
Alert => "ALERT",
Critical => "CRITICAL",
Error => "ERROR",
Warning => "WARNING",
Notice => "NOTICE",
Info => "INFO",
Debug => "DEBUG",
};
write!(f, "{string}")
}
}
impl From<log::Level> for Priority {
fn from(level: log::Level) -> Self {
match level {
log::Level::Error => Priority::Error,
log::Level::Warn => Priority::Warning,
log::Level::Info => Priority::Info,
log::Level::Debug => Priority::Debug,
log::Level::Trace => Priority::Debug,
}
}
}
impl TryFrom<&str> for Priority {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
match value {
"0" | "EMERGENCY" => Ok(Priority::Emergency),
"1" | "ALERT" => Ok(Priority::Alert),
"2" | "CRITICAL" => Ok(Priority::Critical),
"3" | "ERROR" => Ok(Priority::Error),
"4" | "WARNING" => Ok(Priority::Warning),
"5" | "NOTICE" => Ok(Priority::Notice),
"6" | "INFO" => Ok(Priority::Info),
"7" | "DEBUG" => Ok(Priority::Debug),
_ => Err("Priority can only be parsed from 0-7 and given variant names"),
}
}
}
#[derive(Copy, Clone, serde::Deserialize, serde::Serialize)]
pub enum Facility {
Kernel = 0,
User = 1 << 3,
Mail = 2 << 3,
Daemon = 3 << 3,
Auth = 4 << 3,
Syslog = 5 << 3,
Lpr = 6 << 3,
News = 7 << 3,
Uucp = 8 << 3,
Local0 = 16 << 3,
Local1 = 17 << 3,
Local2 = 18 << 3,
Local3 = 19 << 3,
Local4 = 20 << 3,
Local5 = 21 << 3,
Local6 = 22 << 3,
Local7 = 23 << 3,
}
#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
#[error("failed to connect socket: {0}")]
Connect(io::Error),
#[error("failed to get lowest file descriptor: {0}")]
GetLowestFd(io::Error),
#[error("guess of fd for syslog connection was invalid")]
InvalidFd,
#[error("initialization was never attempted")]
NeverInitialized,
#[error("initialization previously failed and cannot be retried")]
Poisoned,
#[error("failed to create socket: {0}")]
Socket(io::Error),
}
pub(crate) trait Syslog {
fn new(
proc_name: String,
facility: Facility,
) -> Result<(Option<Box<dyn Log + Send>>, Option<RawDescriptor>), &'static Error>;
}
pub struct State {
filter: env_logger::filter::Filter,
loggers: Vec<Box<dyn Log + Send>>,
descriptors: Vec<RawDescriptor>,
early_init: bool,
}
struct LoggingFacade {}
impl Log for LoggingFacade {
fn enabled(&self, metadata: &log::Metadata) -> bool {
STATE.lock().enabled(metadata)
}
fn log(&self, record: &log::Record) {
STATE.lock().log(record)
}
fn flush(&self) {
STATE.lock().flush()
}
}
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct LogArgs {
pub filter: String,
pub stderr: bool,
pub proc_name: String,
pub syslog: bool,
pub syslog_facility: Facility,
}
impl Default for LogArgs {
fn default() -> Self {
Self {
filter: String::from("info"),
stderr: true,
proc_name: String::from("crosvm"),
syslog: true,
syslog_facility: Facility::User,
}
}
}
#[derive(Default)]
pub struct LogConfig {
pub log_args: LogArgs,
pub pipe: Option<Box<dyn io::Write + Send>>,
pub pipe_fd: Option<RawDescriptor>,
pub pipe_formatter: Option<
Box<dyn Fn(&mut fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send>,
>,
}
impl State {
pub fn new(cfg: LogConfig) -> Result<Self, Error> {
let mut loggers: Vec<Box<dyn Log + Send>> = vec![];
let mut descriptors = vec![];
let mut builder = env_logger::filter::Builder::new();
builder.parse(&cfg.log_args.filter);
let filter = builder.build();
let create_formatted_builder = || {
let mut builder = env_logger::Builder::new();
builder.format(|buf, record| {
writeln!(
buf,
"[{} {:5} {}] {}",
Utc::now().format("%Y-%m-%dT%H:%M:%S%.9f%:z"),
record.level(),
record.module_path().unwrap_or("<missing module path>"),
record.args()
)
});
builder
};
if cfg.log_args.stderr {
let mut builder = create_formatted_builder();
builder.filter_level(log::LevelFilter::Trace);
builder.target(env_logger::Target::Stderr);
loggers.push(Box::new(builder.build()));
descriptors.push(std::io::stderr().as_raw_descriptor());
}
if let Some(fd) = cfg.pipe_fd {
descriptors.push(fd);
}
if let Some(file) = cfg.pipe {
let mut builder = create_formatted_builder();
builder.filter_level(log::LevelFilter::Trace);
builder.target(env_logger::Target::Pipe(Box::new(file)));
builder.is_test(true);
if let Some(format) = cfg.pipe_formatter {
builder.format(format);
}
loggers.push(Box::new(builder.build()));
}
if cfg.log_args.syslog {
match PlatformSyslog::new(cfg.log_args.proc_name, cfg.log_args.syslog_facility) {
Ok((mut logger, fd)) => {
if let Some(fd) = fd {
descriptors.push(fd);
}
if let Some(logger) = logger.take() {
loggers.push(logger);
}
}
Err(e) => {
eprintln!("syslog init failed: {e}");
}
}
}
Ok(State {
filter,
loggers,
descriptors,
early_init: false,
})
}
}
impl Default for State {
fn default() -> Self {
Self::new(Default::default()).unwrap()
}
}
static STATE: LazyLock<Mutex<State>> = LazyLock::new(|| {
let mut state = State::new(LogConfig::default()).expect("failed to configure minimal logging");
state.early_init = true;
Mutex::new(state)
});
static LOGGING_FACADE: LoggingFacade = LoggingFacade {};
static EARLY_INIT_CALLED: Mutex<bool> = Mutex::new(false);
pub fn init() -> Result<(), Error> {
init_with(Default::default())
}
pub fn init_with(cfg: LogConfig) -> Result<(), Error> {
let mut state = STATE.lock();
if !state.early_init {
panic!("double-init of the logging system is not permitted.");
}
*state = State::new(cfg)?;
apply_logging_state(&LOGGING_FACADE);
Ok(())
}
pub fn early_init() {
let mut early_init_called = EARLY_INIT_CALLED.lock();
if !*early_init_called {
apply_logging_state(&LOGGING_FACADE);
*early_init_called = true;
} else {
panic!("double early init of the logging system is not permitted.");
}
}
pub fn test_only_ensure_inited() -> Result<(), Error> {
let mut early_init_called = EARLY_INIT_CALLED.lock();
if !*early_init_called {
apply_logging_state(&LOGGING_FACADE);
*early_init_called = true;
}
Ok(())
}
fn apply_logging_state(facade: &'static LoggingFacade) {
let _ = log::set_logger(facade);
log::set_max_level(log::LevelFilter::Trace);
}
pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
let state = STATE.lock();
fds.extend(state.descriptors.iter());
}
impl Log for State {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.filter.enabled(metadata)
}
fn log(&self, record: &log::Record) {
if self.filter.matches(record) {
for logger in self.loggers.iter() {
logger.log(record)
}
}
}
fn flush(&self) {
for logger in self.loggers.iter() {
logger.flush()
}
}
}
pub struct Syslogger<'a> {
buf: Vec<u8>,
level: log::Level,
get_state_fn: Box<dyn Fn() -> MutexGuard<'a, State> + Send + 'a>,
}
impl<'a> Syslogger<'a> {
pub fn new(level: log::Level) -> Syslogger<'a> {
Syslogger {
buf: Vec::new(),
level,
get_state_fn: Box::new(|| STATE.lock()),
}
}
pub fn test_only_from_state<F: 'a + Fn() -> MutexGuard<'a, State> + Send>(
level: log::Level,
get_state_fn: F,
) -> Syslogger<'a> {
Syslogger {
buf: Vec::new(),
level,
get_state_fn: Box::new(get_state_fn),
}
}
}
impl io::Write for Syslogger<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let state = (self.get_state_fn)();
self.buf.extend_from_slice(buf);
if let Some(last_newline_idx) = self.buf.iter().rposition(|&x| x == b'\n') {
for line in (self.buf[..last_newline_idx]).split(|&x| x == b'\n') {
let send_line = match line.split_last() {
Some((b'\r', trimmed)) => trimmed,
_ => line,
};
#[allow(clippy::match_single_binding)]
match format_args!("{}", String::from_utf8_lossy(send_line)) {
args => {
let mut record_builder = log::Record::builder();
record_builder.level(self.level);
record_builder.target("syslogger");
record_builder.args(args);
let record = record_builder.build();
state.log(&record);
}
}
}
self.buf.drain(..=last_newline_idx);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
STATE.lock().flush();
Ok(())
}
}