From 05305afa5cee7fa55d4e9fcc12b133704b0f6413 Mon Sep 17 00:00:00 2001 From: yihuaf Date: Sat, 27 May 2023 00:04:06 -0700 Subject: [PATCH] implemented sending logs to systemd Signed-off-by: yihuaf --- Cargo.lock | 12 ++++++++++++ crates/youki/Cargo.toml | 1 + crates/youki/src/main.rs | 18 +++++++++++++++--- crates/youki/src/observability.rs | 27 +++++++++++++++++---------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2df7ec613..7b1ab475b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3925,6 +3925,17 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-journald" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba316a74e8fc3c3896a850dba2375928a9fa171b085ecddfc7c054d39970f3fd" +dependencies = [ + "libc", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -5463,6 +5474,7 @@ dependencies = [ "tabwriter", "tempfile", "tracing", + "tracing-journald", "tracing-subscriber", "vergen", "wasmedge-sdk", diff --git a/crates/youki/Cargo.toml b/crates/youki/Cargo.toml index 7bee6326e..be74bd839 100644 --- a/crates/youki/Cargo.toml +++ b/crates/youki/Cargo.toml @@ -48,6 +48,7 @@ wasmtime = {version = "9.0.1", optional = true } wasmtime-wasi = {version = "9.0.1", optional = true } tracing = { version = "0.1.37", features = ["attributes"]} tracing-subscriber = { version = "0.3.16", features = ["json", "env-filter"] } +tracing-journald = "0.3.0" [dev-dependencies] serial_test = "2.0.0" diff --git a/crates/youki/src/main.rs b/crates/youki/src/main.rs index df067e66c..dfb2923da 100644 --- a/crates/youki/src/main.rs +++ b/crates/youki/src/main.rs @@ -15,6 +15,14 @@ use crate::commands::info; use liboci_cli::{CommonCmd, GlobalOpts, StandardCmd}; +// Additional options that are not defined in OCI runtime-spec, but are used by Youki. +#[derive(Parser, Debug)] +struct YoukiExtendOpts { + /// Enable logging to systemd-journald + #[clap(long)] + pub systemd_log: bool, +} + // High-level commandline option definition // This takes global options as well as individual commands as specified in [OCI runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md) // Also check [runc commandline documentation](https://github.com/opencontainers/runc/blob/master/man/runc.8.md) for more explanation @@ -24,6 +32,9 @@ struct Opts { #[clap(flatten)] global: GlobalOpts, + #[clap(flatten)] + youki_extend: YoukiExtendOpts, + #[clap(subcommand)] subcmd: SubCommand, } @@ -78,9 +89,10 @@ fn main() -> Result<()> { let opts = Opts::parse(); let mut app = Opts::command(); - if let Err(e) = crate::observability::init(&opts) { - eprintln!("log init failed: {e:?}"); - } + crate::observability::init(&opts).map_err(|err| { + eprintln!("failed to initialize observability: {}", err); + err + })?; tracing::debug!( "started by user {} with {:?}", diff --git a/crates/youki/src/observability.rs b/crates/youki/src/observability.rs index b000da1e2..0c18d5875 100644 --- a/crates/youki/src/observability.rs +++ b/crates/youki/src/observability.rs @@ -4,8 +4,7 @@ use std::fs::OpenOptions; use std::path::PathBuf; use std::str::FromStr; use tracing::Level; -use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::prelude::*; const LOG_LEVEL_ENV_NAME: &str = "YOUKI_LOG_LEVEL"; const LOG_FORMAT_TEXT: &str = "text"; @@ -42,10 +41,12 @@ fn detect_log_level(is_debug: bool) -> Result { Ok(Level::from_str(filter.as_ref())?) } +#[derive(Debug, Default)] pub struct ObservabilityConfig { pub log_debug_flag: bool, pub log_file: Option, pub log_format: Option, + pub systemd_log: bool, } impl From<&crate::Opts> for ObservabilityConfig { @@ -54,6 +55,7 @@ impl From<&crate::Opts> for ObservabilityConfig { log_debug_flag: opts.global.debug, log_file: opts.global.log.to_owned(), log_format: opts.global.log_format.to_owned(), + systemd_log: opts.youki_extend.systemd_log, } } } @@ -68,12 +70,19 @@ where let log_level_filter = tracing_subscriber::filter::LevelFilter::from(level); let log_format = detect_log_format(config.log_format.as_deref()) .with_context(|| "failed to detect log format")?; - - let subscriber = tracing_subscriber::registry().with(log_level_filter); + let systemd_journald = if config.systemd_log { + Some(tracing_journald::layer()?.with_syslog_identifier("youki".to_string())) + } else { + None + }; + let subscriber = tracing_subscriber::registry() + .with(log_level_filter) + .with(systemd_journald); // I really dislike how we have to specify individual branch for each // combination, but I can't find any better way to do this. The tracing - // crate makes it hard to build a single layer with different conditions. + // crate makes it hard to build a single format layer with different + // conditions. match (config.log_file.as_ref(), log_format) { (None, LogFormat::Text) => { // Text to stderr @@ -197,9 +206,8 @@ mod tests { let log_file = Path::join(temp_dir.path(), "test.log"); let _guard = LogLevelGuard::new("error").unwrap(); let config = ObservabilityConfig { - log_debug_flag: false, log_file: Some(log_file), - log_format: None, + ..Default::default() }; init(config).map_err(|err| TestCallbackError::Other(err.into()))?; Ok(()) @@ -220,9 +228,8 @@ mod tests { // Note, we can only init the tracing once, so we have to test in a // single unit test. The orders are important here. let config = ObservabilityConfig { - log_debug_flag: false, log_file: Some(log_file.clone()), - log_format: None, + ..Default::default() }; init(config).map_err(|err| TestCallbackError::Other(err.into()))?; assert!( @@ -265,9 +272,9 @@ mod tests { // Note, we can only init the tracing once, so we have to test in a // single unit test. The orders are important here. let config = ObservabilityConfig { - log_debug_flag: false, log_file: Some(log_file.clone()), log_format: Some(LOG_FORMAT_JSON.to_owned()), + ..Default::default() }; init(config).map_err(|err| TestCallbackError::Other(err.into()))?; assert!(