use std::borrow::BorrowMut;
use std::fmt::Write as fmtwrite;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::path::Path;
use std::str::FromStr;
use std::sync::Mutex;
use containerd_shim::error::Error;
use log::kv::{self, Visitor};
use log::{Metadata, Record};
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
#[cfg(windows)]
use crate::vendor::containerd_shim::sys::windows::NamedPipeLogger;
pub const LOG_ENV: &str = "RUST_LOG";
pub struct FifoLogger {
file: Mutex<File>,
}
impl FifoLogger {
#[allow(dead_code)]
pub fn new() -> Result<FifoLogger, io::Error> {
Self::with_path("log")
}
#[allow(dead_code)]
pub fn with_path<P: AsRef<Path>>(path: P) -> Result<FifoLogger, io::Error> {
let f = OpenOptions::new()
.write(true)
.read(false)
.create(false)
.open(path)?;
Ok(FifoLogger {
file: Mutex::new(f),
})
}
}
pub(crate) struct SimpleWriteVisitor {
key_values: String,
}
impl<'kvs> Visitor<'kvs> for SimpleWriteVisitor {
fn visit_pair(&mut self, k: kv::Key<'kvs>, v: kv::Value<'kvs>) -> Result<(), kv::Error> {
write!(&mut self.key_values, " {}=\"{}\"", k, v)?;
Ok(())
}
}
impl SimpleWriteVisitor {
pub(crate) fn new() -> SimpleWriteVisitor {
SimpleWriteVisitor {
key_values: String::new(),
}
}
pub(crate) fn as_str(&self) -> &str {
&self.key_values
}
}
impl log::Log for FifoLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut guard = self.file.lock().unwrap();
let mut writer = SimpleWriteVisitor::new();
let _ = record.key_values().visit(&mut writer);
let _ = writeln!(
guard.borrow_mut(),
"time=\"{}\" level={}{} msg=\"{}\"\n",
rfc3339_formatted(),
record.level().as_str().to_lowercase(),
writer.as_str(),
record.args()
);
}
}
fn flush(&self) {
let _ = self.file.lock().unwrap().sync_all();
}
}
pub fn init(
debug: bool,
default_log_level: &str,
_namespace: &str,
_id: &str,
) -> Result<(), Error> {
#[cfg(unix)]
let logger =
FifoLogger::new().map_err(containerd_shim::io_error!(e, "failed to init logger"))?;
#[cfg(windows)]
let logger = NamedPipeLogger::new(_namespace, _id)
.map_err(containerd_shim::io_error!(e, "failed to init logger"))?;
configure_logging_level(debug, default_log_level);
log::set_boxed_logger(Box::new(logger))?;
Ok(())
}
fn configure_logging_level(debug: bool, default_log_level: &str) {
let debug_level = std::env::var(LOG_ENV).unwrap_or(default_log_level.to_string());
let debug_level = log::LevelFilter::from_str(&debug_level).unwrap_or(log::LevelFilter::Info);
let level = if debug && log::LevelFilter::Debug > debug_level {
log::LevelFilter::Debug
} else {
debug_level
};
log::set_max_level(level);
}
pub(crate) fn rfc3339_formatted() -> String {
OffsetDateTime::now_utc()
.format(&Rfc3339)
.unwrap_or(OffsetDateTime::now_utc().to_string())
}