diff --git a/Cargo.lock b/Cargo.lock index 6d3321678b..adae646fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,7 @@ dependencies = [ "tokio-fd", "tokio-util", "tracing", + "tracing-appender", "tracing-journald", "tracing-subscriber", "tz-rs", @@ -1481,6 +1482,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.22" diff --git a/conmon-rs/server/Cargo.toml b/conmon-rs/server/Cargo.toml index fe3f21a91c..8f08d8fab4 100644 --- a/conmon-rs/server/Cargo.toml +++ b/conmon-rs/server/Cargo.toml @@ -28,6 +28,7 @@ strum = { version = "0.24.1", features = ["derive"] } shadow-rs = "=0.16.1" multimap = "0.8.3" tracing = "0.1.36" +tracing-appender = "0.2.2" tracing-journald = "0.3.0" tracing-subscriber = "0.3.15" uuid = { version = "1.1.2", features = ["v4", "fast-rng", "macro-diagnostics"] } diff --git a/conmon-rs/server/src/config.rs b/conmon-rs/server/src/config.rs index 46d7cd9e40..1c2bc7d7ea 100644 --- a/conmon-rs/server/src/config.rs +++ b/conmon-rs/server/src/config.rs @@ -48,17 +48,18 @@ pub struct Config { /// The logging level of the conmon server. log_level: String, - #[get_copy = "pub"] + #[get = "pub"] #[clap( default_value(LogDriver::Systemd.into()), - env(concat!(prefix!(), "LOG_DRIVER")), - long("log-driver"), + env(concat!(prefix!(), "LOG_DRIVERS")), + multiple(true), + long("log-drivers"), short('d'), possible_values(LogDriver::iter().map(|x| x.into()).collect::>()), - value_name("DRIVER") + value_name("DRIVERS") )] - /// The logging driver used by the conmon server. - log_driver: LogDriver, + /// The logging drivers used by the server. Can be specified multiple times. + log_drivers: Vec, #[get = "pub"] #[clap( @@ -128,11 +129,15 @@ pub struct Config { #[strum(serialize_all = "lowercase")] /// Available log drivers. pub enum LogDriver { - /// Log to stdout + /// Log to stdout. Stdout, - /// Use systemd journald as log driver + /// Use systemd journald as log driver. Systemd, + + /// Use a file destination as log driver. The log file path fill be generated to match + /// `[--runtime-dir]/logs/conmonrs.YYYY-MM-DD` and rotates on a daily basis. + File, } #[derive( diff --git a/conmon-rs/server/src/server.rs b/conmon-rs/server/src/server.rs index a6e51d6b32..a25c25f78f 100644 --- a/conmon-rs/server/src/server.rs +++ b/conmon-rs/server/src/server.rs @@ -11,6 +11,7 @@ use crate::{ use anyhow::{format_err, Context, Result}; use capnp::text_list::Reader; use capnp_rpc::{rpc_twoparty_capnp::Side, twoparty, RpcSystem}; +use clap::crate_name; use conmon_common::conmon_capnp::conmon; use futures::{AsyncReadExt, FutureExt}; use getset::Getters; @@ -58,8 +59,8 @@ impl Server { process::exit(0); } - server.init_logging().context("set log verbosity")?; server.config().validate().context("validate config")?; + server.init_logging().context("init logging")?; Self::init().context("init self")?; Ok(server) @@ -108,30 +109,47 @@ impl Server { LevelFilter::from_str(self.config().log_level()).context("convert log level filter")?; let registry = tracing_subscriber::registry(); - match self.config().log_driver() { - LogDriver::Stdout => { - let layer = tracing_subscriber::fmt::layer() - .with_target(true) - .with_line_number(true) - .with_filter(level); - registry - .with(layer) - .try_init() - .context("init stdout fmt layer")?; - info!("Using stdout logger"); - } - LogDriver::Systemd => { - let layer = tracing_journald::layer() - .context("unable to connect to journald")? - .with_filter(level); - registry - .with(layer) - .try_init() - .context("init journald layer")?; - info!("Using systemd/journald logger"); - } - } + let stdout = if self.config().log_drivers().contains(&LogDriver::Stdout) { + tracing_subscriber::fmt::layer() + .with_target(true) + .with_line_number(true) + .with_filter(level) + .into() + } else { + None + }; + + let systemd = if self.config().log_drivers().contains(&LogDriver::Systemd) { + tracing_journald::layer() + .context("unable to connect to journald")? + .with_filter(level) + .into() + } else { + None + }; + + let file = if self.config().log_drivers().contains(&LogDriver::File) { + let path = self.config().runtime_dir().join("logs"); + let appender = tracing_appender::rolling::daily(path, crate_name!()); + tracing_subscriber::fmt::layer() + .with_target(true) + .with_ansi(false) + .with_line_number(true) + .with_writer(appender) + .with_filter(level) + .into() + } else { + None + }; + + registry + .with(stdout) + .with(systemd) + .with(file) + .try_init() + .context("init registry")?; info!("Set log level to: {}", self.config().log_level()); + Ok(()) } diff --git a/pkg/client/client.go b/pkg/client/client.go index b2de21193f..9997f0d9b8 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -57,9 +57,9 @@ type ConmonServerConfig struct { // Can be "trace", "debug", "info", "warn", "error" or "off". LogLevel string - // LogDriver is the possible server logging driver. - // Can be "stdout" or "systemd". - LogDriver string + // LogDrivers are the possible server logging drivers. + // Can be "stdout", "systemd" or both. + LogDrivers []string // Runtime is the binary path of the OCI runtime to use to operate on the // containers. @@ -106,7 +106,7 @@ func NewConmonServerConfig( ) *ConmonServerConfig { return &ConmonServerConfig{ LogLevel: LogLevelDebug, - LogDriver: LogDriverStdout, + LogDrivers: []string{LogDriverStdout}, Runtime: runtime, RuntimeRoot: runtimeRoot, ServerRunDir: serverRunDir, @@ -212,7 +212,7 @@ func (c *ConmonClient) startServer(config *ConmonServerConfig) error { Setpgid: true, } - if config.LogDriver == LogDriverStdout { + if contains(config.LogDrivers, LogDriverStdout) { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if config.Stdout != nil { @@ -230,6 +230,16 @@ func (c *ConmonClient) startServer(config *ConmonServerConfig) error { return nil } +func contains[T comparable](haystack []T, needle T) bool { + for _, value := range haystack { + if value == needle { + return true + } + } + + return false +} + func (c *ConmonClient) toArgs(config *ConmonServerConfig) (entrypoint string, args []string, err error) { if c == nil { return "", args, nil @@ -263,11 +273,11 @@ func (c *ConmonClient) toArgs(config *ConmonServerConfig) (entrypoint string, ar args = append(args, "--log-level", config.LogLevel) } - if config.LogDriver != "" { - if err := validateLogDriver(config.LogDriver); err != nil { + for _, driver := range config.LogDrivers { + if err := validateLogDriver(driver); err != nil { return "", args, fmt.Errorf("validate log driver: %w", err) } - args = append(args, "--log-driver", config.LogDriver) + args = append(args, "--log-drivers", driver) } const cgroupManagerFlag = "--cgroup-manager" @@ -297,7 +307,7 @@ func validateLogDriver(driver string) error { return validateStringSlice( "log driver", driver, - LogDriverStdout, LogDriverSystemd, + LogDriverStdout, LogDriverSystemd, LogDriverFile, ) } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 0fc3da25ab..af2288644b 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -80,6 +80,15 @@ var _ = Describe("ConmonClient", func() { } else { Expect(version.CargoTree).To(BeEmpty()) } + + // Validate the log file + logPath := filepath.Join(tr.tmpDir, "logs") + files, err := os.ReadDir(logPath) + Expect(err).To(BeNil()) + Expect(files).To(HaveLen(1)) + content, err := os.ReadFile(filepath.Join(logPath, files[0].Name())) + Expect(err).To(BeNil()) + Expect(string(content)).To(ContainSubstring("Got a version request")) }) } }) diff --git a/pkg/client/consts.go b/pkg/client/consts.go index fde64dce48..e1ae08aefd 100644 --- a/pkg/client/consts.go +++ b/pkg/client/consts.go @@ -7,6 +7,11 @@ const ( // LogDriverSystemd is the log driver printing to systemd journald. LogDriverSystemd = "systemd" + // LogDriverFile is the file based log driver. The log file path fill be + // generated to match / `[config.ServerRunDir]/logs/conmonrs.YYYY-MM-DD` + // and rotates on a daily basis. + LogDriverFile = "file" + // LogLevelTrace is the log level printing only "trace" messages. LogLevelTrace = "trace" diff --git a/pkg/client/suite_test.go b/pkg/client/suite_test.go index a7b2db6205..b8956ea341 100644 --- a/pkg/client/suite_test.go +++ b/pkg/client/suite_test.go @@ -212,6 +212,7 @@ func (tr *testRunner) configGivenEnv() *client.ConmonClient { logger := logrus.StandardLogger() logger.Level = logrus.TraceLevel cfg.ClientLogger = logger + cfg.LogDrivers = []string{client.LogDriverStdout, client.LogDriverFile} sut, err := client.New(cfg) Expect(err).To(BeNil())