diff --git a/CHANGELOG.md b/CHANGELOG.md index 2725e4d..d7d6272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `kp_reuse` flag allow to reuse a key pair instead of creating a new one at each renewal. - It is now possible to define hook groups that can reference either hooks or other hook groups. - Hooks can be defined when before and after a file is created or edited (`file_pre_create_hooks`, `file_post_create_hooks`, `file_pre_edit_hooks` and `file_post_edit_hooks`). +- It is now possible to send logs either to syslog or stderr using the `--to-syslog` and `--to-stderr` arguments. ### Changed - `post_operation_hook` has been renamed `post_operation_hooks`. +- By default, logs are now sent to syslog instead of stderr. diff --git a/acmed/Cargo.toml b/acmed/Cargo.toml index 9b32003..327a91a 100644 --- a/acmed/Cargo.toml +++ b/acmed/Cargo.toml @@ -19,6 +19,7 @@ log = "0.4" openssl = "0.10" pem = "0.5" serde = { version = "1.0", features = ["derive"] } +syslog = "4.0" time = "0.1" toml = "0.5" x509-parser = "0.4" diff --git a/acmed/src/errors.rs b/acmed/src/errors.rs index 1aa679e..350b3ce 100644 --- a/acmed/src/errors.rs +++ b/acmed/src/errors.rs @@ -1,5 +1,6 @@ use std::fmt; +#[derive(Debug)] pub struct Error { pub message: String, } @@ -30,6 +31,12 @@ impl From<&str> for Error { } } +impl From for Error { + fn from(error: syslog::Error) -> Self { + Error::new(&format!("syslog error: {}", error)) + } +} + impl From for Error { fn from(error: toml::de::Error) -> Self { Error::new(&format!("IO error: {}", error)) diff --git a/acmed/src/logs.rs b/acmed/src/logs.rs new file mode 100644 index 0000000..431c3ab --- /dev/null +++ b/acmed/src/logs.rs @@ -0,0 +1,79 @@ +use crate::errors::Error; +use env_logger::Builder; +use log::LevelFilter; +use syslog::Facility; + +#[derive(Debug, PartialEq, Eq)] +pub enum LogSystem { + SysLog, + StdErr, +} + +fn get_loglevel(log_level: Option<&str>) -> Result { + let level = match log_level { + Some(v) => match v { + "error" => LevelFilter::Error, + "warn" => LevelFilter::Warn, + "info" => LevelFilter::Info, + "debug" => LevelFilter::Debug, + "trace" => LevelFilter::Trace, + _ => { + return Err(Error::new(&format!("{}: invalid log level", v))); + } + }, + None => crate::DEFAULT_LOG_LEVEL, + }; + Ok(level) +} + +fn set_log_syslog(log_level: LevelFilter) -> Result<(), Error> { + syslog::init(Facility::LOG_DAEMON, log_level, Some(crate::APP_NAME))?; + Ok(()) +} + +fn set_log_stderr(log_level: LevelFilter) -> Result<(), Error> { + let mut builder = Builder::from_env("ACMED_LOG_LEVEL"); + builder.filter_level(log_level); + builder.init(); + Ok(()) +} + +pub fn set_log_system( + log_level: Option<&str>, + has_syslog: bool, + has_stderr: bool, +) -> Result<(LogSystem, LevelFilter), Error> { + let log_level = get_loglevel(log_level)?; + let mut logtype = crate::DEFAULT_LOG_SYSTEM; + if has_stderr { + logtype = LogSystem::StdErr; + } + if has_syslog { + logtype = LogSystem::SysLog; + } + match logtype { + LogSystem::SysLog => set_log_syslog(log_level)?, + LogSystem::StdErr => set_log_stderr(log_level)?, + }; + Ok((logtype, log_level)) +} + +#[cfg(test)] +mod tests { + use super::set_log_system; + + #[test] + fn test_invalid_level() { + let ret = set_log_system(Some("invalid"), false, false); + assert!(ret.is_err()); + } + + #[test] + fn test_default_values() { + let ret = set_log_system(None, false, false); + assert!(ret.is_ok()); + let (logtype, log_level) = ret.unwrap(); + assert_eq!(logtype, crate::DEFAULT_LOG_SYSTEM); + assert_eq!(log_level, crate::DEFAULT_LOG_LEVEL); + } +} diff --git a/acmed/src/main.rs b/acmed/src/main.rs index e252df3..2d1410a 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -1,5 +1,4 @@ use clap::{App, Arg}; -use env_logger::Builder; use log::{error, LevelFilter}; mod acmed; @@ -7,8 +6,10 @@ mod config; mod encoding; mod errors; mod hooks; +mod logs; mod storage; +pub const APP_NAME: &str = "acmed"; pub const DEFAULT_CONFIG_FILE: &str = "/etc/acmed/acmed.toml"; pub const DEFAULT_ACCOUNTS_DIR: &str = "/etc/acmed/accounts"; pub const DEFAULT_CERT_DIR: &str = "/etc/acmed/certs"; @@ -20,6 +21,8 @@ pub const DEFAULT_POOL_TIME: u64 = 5000; pub const DEFAULT_CERT_FILE_MODE: u32 = 0o644; pub const DEFAULT_PK_FILE_MODE: u32 = 0o600; pub const DEFAULT_KP_REUSE: bool = false; +pub const DEFAULT_LOG_SYSTEM: logs::LogSystem = logs::LogSystem::SysLog; +pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Warn; fn main() { let matches = App::new("acmed") @@ -40,30 +43,31 @@ fn main() { .value_name("LEVEL") .possible_values(&["error", "warn", "info", "debug", "trace"]), ) + .arg( + Arg::with_name("to-syslog") + .long("log-syslog") + .help("Send log messages via syslog.") + .conflicts_with("to-stderr"), + ) + .arg( + Arg::with_name("to-stderr") + .long("log-stderr") + .help("Print log messages to the standard error output.") + .conflicts_with("log-syslog"), + ) .get_matches(); - let mut builder = Builder::from_env("ACMED_LOG_LEVEL"); - if let Some(v) = matches.value_of("log-level") { - match v { - "error" => { - builder.filter_level(LevelFilter::Error); - } - "warn" => { - builder.filter_level(LevelFilter::Warn); - } - "info" => { - builder.filter_level(LevelFilter::Info); - } - "debug" => { - builder.filter_level(LevelFilter::Debug); - } - "trace" => { - builder.filter_level(LevelFilter::Trace); - } - _ => {} + match logs::set_log_system( + matches.value_of("log-level"), + matches.is_present("log-syslog"), + matches.is_present("to-stderr"), + ) { + Ok(_) => {} + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); } }; - builder.init(); let config_file = matches.value_of("config").unwrap_or(DEFAULT_CONFIG_FILE); let mut srv = match acmed::Acmed::new(&config_file) {