use crate::config::LevelVar;
use crate::logs::default_values::*;
use crate::logs::env_variables::*;
use ockam_core::env::{get_env, get_env_with_default, FromString};
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use tracing_core::Level;
use tracing_subscriber::EnvFilter;
use super::{Colored, LoggingEnabled, OckamUserLogFormat};
use crate::logs::LogFormat;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoggingConfiguration {
enabled: LoggingEnabled,
level: Level,
max_size_bytes: u64,
max_files: u64,
format: LogFormat,
colored: Colored,
log_dir: Option<PathBuf>,
crates: Option<Vec<String>>,
}
impl LoggingConfiguration {
#[allow(clippy::too_many_arguments)]
pub fn new(
enabled: LoggingEnabled,
level: Level,
max_size_bytes: u64,
max_files: u64,
format: LogFormat,
colored: Colored,
log_dir: Option<PathBuf>,
crates_filter: CratesFilter,
) -> LoggingConfiguration {
LoggingConfiguration {
enabled,
level,
max_size_bytes,
max_files,
format,
colored,
log_dir,
crates: crates_filter.crates(),
}
}
pub fn is_enabled(&self) -> bool {
self.enabled == LoggingEnabled::On
}
pub fn level(&self) -> Level {
self.level
}
pub fn max_file_size_bytes(&self) -> u64 {
self.max_size_bytes
}
pub fn max_files(&self) -> u64 {
self.max_files
}
pub fn format(&self) -> LogFormat {
self.format.clone()
}
pub fn is_colored(&self) -> bool {
self.colored == Colored::On
}
pub fn log_dir(&self) -> Option<PathBuf> {
self.log_dir.clone()
}
pub fn crates(&self) -> Option<Vec<String>> {
self.crates.clone()
}
pub fn set_log_directory(self, log_dir: PathBuf) -> LoggingConfiguration {
LoggingConfiguration {
log_dir: Some(log_dir),
..self
}
}
pub fn set_crates(self, crates: &[&str]) -> LoggingConfiguration {
LoggingConfiguration {
crates: Some(crates.iter().map(|c| c.to_string()).collect()),
..self
}
}
pub fn set_all_crates(self) -> LoggingConfiguration {
LoggingConfiguration {
crates: None,
..self
}
}
pub fn set_log_level(self, level: Level) -> LoggingConfiguration {
LoggingConfiguration { level, ..self }
}
pub fn env_filter(&self) -> EnvFilter {
match &self.crates {
Some(crates) => {
let builder = EnvFilter::builder();
builder
.with_default_directive(self.level().into())
.parse_lossy(
crates
.iter()
.map(|c| format!("{c}={}", self.level()))
.collect::<Vec<_>>()
.join(","),
)
}
None => EnvFilter::default().add_directive(self.level.into()),
}
}
pub fn off() -> ockam_core::Result<LoggingConfiguration> {
let level_and_crates = LogLevelWithCratesFilter::new()?;
Ok(LoggingConfiguration::new(
LoggingEnabled::Off,
level_and_crates.level,
0,
0,
LogFormat::Default,
Colored::Off,
None,
level_and_crates.crates_filter,
))
}
pub fn background(log_dir: Option<PathBuf>) -> ockam_core::Result<LoggingConfiguration> {
let level_and_crates = LogLevelWithCratesFilter::new()?;
Ok(LoggingConfiguration::new(
LoggingEnabled::On,
level_and_crates.level,
log_max_size_bytes()?,
log_max_files()?,
log_format()?,
Colored::Off,
log_dir,
level_and_crates.crates_filter.clone(),
))
}
}
impl Display for LoggingConfiguration {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LoggingConfiguration")
.field("enabled", &self.enabled.to_string())
.field("level", &self.level().to_string())
.field("max_size_bytes", &self.max_size_bytes)
.field("max_files", &self.max_files)
.field("format", &self.format)
.field("colored", &self.colored)
.field("log_dir", &self.log_dir)
.field("crates", &self.crates)
.finish()
}
}
pub fn logging_configuration(
level_and_crates: LogLevelWithCratesFilter,
log_dir: Option<PathBuf>,
colored: Colored,
default_log_format: LogFormat,
enabled: LoggingEnabled,
) -> ockam_core::Result<LoggingConfiguration> {
let enabled = if level_and_crates.explicit_verbose_flag {
LoggingEnabled::On
} else {
enabled
};
Ok(LoggingConfiguration::new(
enabled,
level_and_crates.level,
log_max_size_bytes()?,
log_max_files()?,
get_env_with_default(OCKAM_LOG_FORMAT, default_log_format)?,
colored,
log_dir,
level_and_crates.crates_filter.clone(),
))
}
fn log_max_size_bytes() -> ockam_core::Result<u64> {
get_env_with_default(OCKAM_LOG_MAX_SIZE_MB, DEFAULT_LOG_MAX_SIZE_MB).map(|v| v * 1024 * 1024)
}
fn log_max_files() -> ockam_core::Result<u64> {
get_env_with_default(OCKAM_LOG_MAX_FILES, DEFAULT_LOG_MAX_FILES)
}
fn log_format() -> ockam_core::Result<LogFormat> {
get_env_with_default(OCKAM_LOG_FORMAT, DEFAULT_LOG_FORMAT)
}
pub fn logging_enabled() -> ockam_core::Result<LoggingEnabled> {
match get_env::<bool>(OCKAM_LOGGING)? {
Some(v) => Ok(if v {
LoggingEnabled::On
} else {
LoggingEnabled::Off
}),
None => Ok(LoggingEnabled::Off),
}
}
pub struct LogLevelWithCratesFilter {
pub level: Level,
pub crates_filter: CratesFilter,
pub explicit_verbose_flag: bool,
}
impl LogLevelWithCratesFilter {
pub fn new() -> ockam_core::Result<Self> {
let level = Self::get_log_level_from_env()?;
let crates_filter = match CratesFilter::try_from_env()? {
None => match level {
Level::INFO => CratesFilter::Basic,
_ => CratesFilter::Core,
},
Some(f) => f,
};
Ok(Self {
level,
crates_filter,
explicit_verbose_flag: false,
})
}
pub fn from_verbose(verbose: u8) -> ockam_core::Result<Self> {
let level = match verbose {
0 => Self::get_log_level_from_env()?,
1 | 2 => Level::INFO,
3 => Level::DEBUG,
_ => Level::TRACE,
};
let crates_filter = CratesFilter::from_verbose(verbose)?;
let explicit_verbose_flag = verbose > 0;
Ok(Self {
level,
crates_filter,
explicit_verbose_flag,
})
}
fn get_log_level_from_env() -> ockam_core::Result<Level> {
get_env_with_default(
OCKAM_LOG_LEVEL,
LevelVar {
level: Level::DEBUG,
},
)
.map(|l| l.level)
}
pub fn add_crates(self, new: Vec<impl Into<String>>) -> Self {
let crates = self
.crates_filter
.crates()
.unwrap_or_default()
.into_iter()
.chain(new.into_iter().map(Into::into))
.collect();
Self {
crates_filter: CratesFilter::Selected(crates),
..self
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CratesFilter {
All,
Basic,
Core,
Selected(Vec<String>),
}
impl CratesFilter {
pub fn try_from_env() -> ockam_core::Result<Option<Self>> {
get_env_with_default(OCKAM_LOG_CRATES_FILTER, None)
}
pub fn from_verbose(verbose: u8) -> ockam_core::Result<Self> {
Ok(match verbose {
0 => get_env_with_default(OCKAM_LOG_CRATES_FILTER, CratesFilter::Basic)?,
1 => CratesFilter::Basic,
2..=4 => CratesFilter::Core,
_ => CratesFilter::All,
})
}
pub fn crates(&self) -> Option<Vec<String>> {
match self {
CratesFilter::All => None,
CratesFilter::Basic => Some(vec![
"ockam_api::ui::terminal".to_string(),
"ockam_command".to_string(),
OckamUserLogFormat::TARGET.to_string(),
]),
CratesFilter::Core => Some(vec![
"ockam".to_string(),
"ockam_node".to_string(),
"ockam_core".to_string(),
"ockam_vault".to_string(),
"ockam_identity".to_string(),
"ockam_transport_tcp".to_string(),
"ockam_api".to_string(),
"ockam_command".to_string(),
OckamUserLogFormat::TARGET.to_string(),
]),
CratesFilter::Selected(list) => Some(list.clone()),
}
}
}
impl FromString for CratesFilter {
fn from_string(s: &str) -> ockam_core::Result<Self> {
match s {
"all" => Ok(CratesFilter::All),
"basic" => Ok(CratesFilter::Basic),
"core" => Ok(CratesFilter::Core),
other => Ok(CratesFilter::Selected(<Vec<String>>::from_string(other)?)),
}
}
}
impl Display for CratesFilter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CratesFilter::All => f.write_str("all"),
CratesFilter::Basic => f.write_str("basic"),
CratesFilter::Core => f.write_str("core"),
CratesFilter::Selected(s) => f.write_str(s.join(",").as_str()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn log_level_with_crates_filter_new() {
std::env::remove_var(OCKAM_LOG_LEVEL);
std::env::remove_var(OCKAM_LOG_CRATES_FILTER);
let sut = LogLevelWithCratesFilter::new().unwrap();
assert_eq!(sut.level, Level::DEBUG);
assert_eq!(sut.crates_filter, CratesFilter::Core);
assert!(!sut.explicit_verbose_flag);
std::env::set_var(OCKAM_LOG_LEVEL, "info");
std::env::set_var(OCKAM_LOG_CRATES_FILTER, "my_crate,other_crate");
let sut = LogLevelWithCratesFilter::new().unwrap();
assert_eq!(sut.level, Level::INFO);
assert_eq!(
sut.crates_filter,
CratesFilter::Selected(vec!["my_crate".to_string(), "other_crate".to_string()])
);
assert!(!sut.explicit_verbose_flag);
}
#[test]
#[serial]
fn log_level_with_crates_filter_with_verbose() {
std::env::remove_var(OCKAM_LOG_LEVEL);
std::env::remove_var(OCKAM_LOG_CRATES_FILTER);
let sut = LogLevelWithCratesFilter::from_verbose(1).unwrap();
assert_eq!(sut.level, Level::INFO);
assert_eq!(sut.crates_filter, CratesFilter::Basic);
assert!(sut.explicit_verbose_flag);
std::env::set_var(OCKAM_LOG_LEVEL, "trace");
std::env::set_var(OCKAM_LOG_CRATES_FILTER, "my_crate");
let sut = LogLevelWithCratesFilter::from_verbose(0).unwrap();
assert_eq!(sut.level, Level::TRACE);
assert_eq!(
sut.crates_filter,
CratesFilter::Selected(vec!["my_crate".to_string()])
);
assert!(!sut.explicit_verbose_flag);
let sut = LogLevelWithCratesFilter::from_verbose(1).unwrap();
assert_eq!(sut.level, Level::INFO);
assert_eq!(sut.crates_filter, CratesFilter::Basic);
assert!(sut.explicit_verbose_flag);
}
}