use clap::Parser; use figment::{ Figment, providers::{Format, Serialized, Yaml}, }; use log::warn; use serde::{Deserialize, Serialize}; use std::error::Error; use std::path::PathBuf; #[allow(unused)] use flexi_logger::{ FileSpec, LogSpecification, Logger, LoggerHandle, colored_with_thread, with_thread, writers::{SyslogConnection, SyslogLineHeader, SyslogWriter}, }; #[derive(Deserialize, Serialize, Debug)] pub(crate) struct DataFunctionsInterval { pub(crate) log_debug_watcher: Option, multipath: Option, meminfo: Option, processes: Option, bonding: Option, filesum: Option, } impl Default for DataFunctionsInterval { fn default() -> Self { DataFunctionsInterval { log_debug_watcher: Some(5), multipath: Some(60), meminfo: Some(30), processes: Some(30), bonding: Some(30), filesum: Some(60), } } } impl DataFunctionsInterval { pub fn meminfo(&self) -> Option { self.meminfo } pub fn processes(&self) -> Option { self.processes } pub fn filesum(&self) -> Option { self.filesum } pub fn bonding(&self) -> Option { self.bonding } pub fn multipath(&self) -> Option { self.multipath } } #[derive(Deserialize, Serialize, Debug, Clone)] pub(crate) struct DataFunctionsFilesum { pub(crate) passwd: Option, pub(crate) shadow: Option, pub(crate) group: Option, pub(crate) authorized_keys: Option, } #[derive(Deserialize, Serialize, Debug)] pub struct DataFunctionsExtra { multipath: Option, bonding: Option, filesum: DataFunctionsFilesum, } impl Default for DataFunctionsExtra { fn default() -> Self { DataFunctionsExtra { multipath: None, bonding: None, filesum: DataFunctionsFilesum { passwd: None, shadow: None, group: None, authorized_keys: None, }, } } } impl DataFunctionsExtra { pub(crate) fn filesum(&self) -> DataFunctionsFilesum { self.filesum.clone() } pub(crate) fn multipath(&self) -> Option { self.multipath.clone() } pub(crate) fn bonding(&self) -> Option { self.bonding.clone() } } #[derive(Deserialize, Serialize, Debug, Clone, clap::ValueEnum)] enum LogConfig { None, StdErr, Syslog, Logfile, LogfileAndSyslog, } #[derive(Deserialize, Serialize, Debug)] pub struct AppConfig { logoutput: LogConfig, logdir: String, logfile_basename: String, loglevel: String, pub(crate) base_oid: String, pub(crate) debug_log_marker: PathBuf, pub(crate) intervals: DataFunctionsInterval, pub(crate) extra_config: DataFunctionsExtra, } impl Default for AppConfig { fn default() -> Self { AppConfig { logoutput: LogConfig::Logfile, logdir: "log".to_string(), logfile_basename: "rsnmpagent".to_string(), loglevel: "info".to_string(), base_oid: ".1.3.6.1.4.1.8072.9999.9999".to_string(), debug_log_marker: "debug.marker".to_string().into(), intervals: DataFunctionsInterval::default(), extra_config: DataFunctionsExtra::default(), } } } impl AppConfig { pub fn base_oid(&self) -> &str { &self.base_oid } } // Define the struct to hold the command-line arguments. #[derive(Parser, Deserialize, Serialize, Debug)] #[command(author, version, about = "rsnmpd, snmpd passpersist extension", long_about = None)] struct Cli { /// Optional path to a configuration file. #[arg(short, long, value_name = "FILENAME", default_value = "config.yml")] configfile: PathBuf, #[arg(short, long, value_name = "LOGOUTPUT", value_enum, default_value_t = LogConfig::Logfile)] logoutput: LogConfig, #[arg(short, long)] show_parsed_config: bool, } pub fn build_config() -> Result> { let cli = Cli::parse(); #[cfg(debug_assertions)] if cli.show_parsed_config { eprintln!("Parsed config line options: {:#?}", cli); } let config: AppConfig = Figment::new() .merge(Serialized::defaults(AppConfig::default())) .merge(Yaml::file(&cli.configfile)) .merge(Serialized::defaults(&cli)) .extract()?; if cli.show_parsed_config { eprintln!("Loaded configuration: {:#?}", config); } Ok(config) } pub fn start_logging(config: &AppConfig) -> Result> { let r = match config.logoutput { LogConfig::None => { let handle = Logger::with(LogSpecification::off()).do_not_log().start()?; warn!("Starting None logging config"); handle } LogConfig::StdErr => { let handle = Logger::try_with_str(&config.loglevel)? .format(colored_with_thread) .start()?; warn!("Starting StdErr logging config with level: {}", config.loglevel); handle } LogConfig::Syslog => { #[cfg(target_os = "windows")] { Err("Syslog in windows not implemented")? } #[cfg(target_os = "linux")] { let syslog_writer = SyslogWriter::builder( SyslogConnection::syslog_call(), SyslogLineHeader::Rfc3164, flexi_logger::writers::SyslogFacility::SystemDaemons, ) .build()?; let handle = Logger::try_with_str(&config.loglevel)? .log_to_writer(syslog_writer) .format(with_thread) .start()?; warn!("Starting Syslog logging config with level: {}", config.loglevel); handle } } LogConfig::Logfile => { let handle = Logger::try_with_str(&config.loglevel)? .log_to_file( FileSpec::default() .directory(&config.logdir) .basename(&config.logfile_basename) .suppress_timestamp(), ) .append() .format(with_thread) .start()?; warn!( "Starting Logfile logging to {} with level: {}", config.logdir, config.loglevel ); handle } LogConfig::LogfileAndSyslog => { #[cfg(target_os = "windows")] { eprintln!( "Starting LogfileAndSyslog logging to {} with level: {}", config.logdir, config.loglevel ); Err("Syslog in windows not implemented")? } #[cfg(target_os = "linux")] { let handle = Logger::try_with_str(&config.loglevel) .expect("Log start failed") .log_to_file( FileSpec::default() .directory(&config.logdir) .basename(&config.logfile_basename) .suppress_timestamp(), ) .append() .format(with_thread) .add_writer( "syslog", SyslogWriter::builder( SyslogConnection::syslog_call(), SyslogLineHeader::Rfc3164, flexi_logger::writers::SyslogFacility::SystemDaemons, ) .build()?, ) .start()?; warn!( "Starting LogfileAndSyslog logging to {} with level: {}", config.logdir, config.loglevel ); handle } } }; Ok(r) }