Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ edition = "2021"
async-trait = "0.1.52"
futures = "0.3.19"
libc = "0.2.112"
log = "0.4"
log = {version = "0.4.2", features=["kv_unstable"]}
nix = "0.27"
oci-spec = "0.6"
os_pipe = "1.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/shim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ containerd-shim-protos = { path = "../shim-protos", version = "0.5.0" }
go-flag = "0.1.0"
lazy_static = "1.4.0"
libc.workspace = true
log = { workspace = true, features = ["std"] }
log = { workspace = true, features = ["std", "kv_unstable" ] }
nix = { workspace = true, features = [
"ioctl",
"fs",
Expand Down
73 changes: 71 additions & 2 deletions crates/shim/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

use std::{
borrow::BorrowMut,
fmt::Write as fmtwrite,
fs::{File, OpenOptions},
io,
io::Write,
path::Path,
sync::Mutex,
};

use log::{Metadata, Record};
use log::{
kv::{self, Visitor},
Metadata, Record,
};
use time::{format_description::well_known::Rfc3339, OffsetDateTime};

use crate::error::Error;
Expand Down Expand Up @@ -54,6 +58,29 @@ impl FifoLogger {
}
}

pub(crate) struct SimpleWriteVistor {
key_values: String,
}

impl<'kvs> Visitor<'kvs> for SimpleWriteVistor {
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 SimpleWriteVistor {
pub(crate) fn new() -> SimpleWriteVistor {
SimpleWriteVistor {
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()
Expand All @@ -62,6 +89,11 @@ impl log::Log for FifoLogger {
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut guard = self.file.lock().unwrap();

// collect key_values but don't fail if error parsing
let mut writer = SimpleWriteVistor::new();
let _ = record.key_values().visit(&mut writer);

// The logger server may have temporarily shutdown, ignore the error instead of panic.
//
// Manual for pipe/FIFO: https://man7.org/linux/man-pages/man7/pipe.7.html
Expand All @@ -71,9 +103,10 @@ impl log::Log for FifoLogger {
// EPIPE.
let _ = writeln!(
guard.borrow_mut(),
"time=\"{}\" level={} {}\n",
"time=\"{}\" level={}{} msg=\"{}\"\n",
rfc3339_formated(),
record.level().as_str().to_lowercase(),
writer.as_str(),
record.args()
);
}
Expand Down Expand Up @@ -118,6 +151,8 @@ pub(crate) fn rfc3339_formated() -> String {

#[cfg(test)]
mod tests {
use std::fs;

use log::{Log, Record};

use super::*;
Expand Down Expand Up @@ -151,12 +186,46 @@ mod tests {
log::set_max_level(log::LevelFilter::Info);
thread.join().unwrap();

let kvs: &[(&str, i32)] = &[("a", 1), ("b", 2)];
let record = Record::builder()
.level(log::Level::Error)
.line(Some(1))
.file(Some("sample file"))
.key_values(&kvs)
.build();
logger.log(&record);
logger.flush();
}

#[test]
fn test_supports_structured_logging() {
let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().to_str().unwrap().to_owned() + "/log";
File::create(path.clone()).unwrap();

let logger = FifoLogger::with_path(&path).unwrap();
log::set_max_level(log::LevelFilter::Info);

let record = Record::builder()
.level(log::Level::Info)
.args(format_args!("no keys"))
.build();
logger.log(&record);
logger.flush();

let contents = fs::read_to_string(path.clone()).unwrap();
assert!(contents.contains("level=info msg=\"no keys\""));

let kvs: &[(&str, i32)] = &[("key", 1), ("b", 2)];
let record = Record::builder()
.level(log::Level::Error)
.key_values(&kvs)
.args(format_args!("structured!"))
.build();
logger.log(&record);
logger.flush();

let contents = fs::read_to_string(path).unwrap();
assert!(contents.contains("level=error key=\"1\" b=\"2\" msg=\"structured!\""));
}
}
14 changes: 10 additions & 4 deletions crates/shim/src/sys/windows/named_pipe_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ impl log::Log for NamedPipeLogger {

fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
// collect key_values but don't fail if error parsing
let mut writer = logger::SimpleWriteVistor::new();
let _ = record.key_values().visit(&mut writer);

let message = format!(
"time=\"{}\" level={} {}\n",
"time=\"{}\" level={}{} msg=\"{}\"\n",
logger::rfc3339_formated(),
record.level().as_str().to_lowercase(),
writer.as_str(),
record.args()
);

Expand Down Expand Up @@ -173,20 +178,21 @@ mod tests {
let mut client = create_client(pipe_name.as_str());

log::set_max_level(log::LevelFilter::Info);
let kvs: &[(&str, i32)] = &[("key", 1), ("b", 2)];
let record = Record::builder()
.level(log::Level::Info)
.line(Some(1))
.file(Some("sample file"))
.key_values(&kvs)
.args(format_args!("hello"))
.build();
logger.log(&record);
logger.flush();

let buf = read_message(&mut client, 53);
let buf = read_message(&mut client, 73);
let message = std::str::from_utf8(&buf).unwrap();
assert!(message.starts_with("time=\""), "message was: {:?}", message);
assert!(
message.ends_with("level=info hello\n"),
message.contains("level=info key=\"1\" b=\"2\" msg=\"hello\"\n"),
"message was: {:?}",
message
);
Expand Down