use tracing::Level;
#[derive(Debug, Clone)]
pub struct LogConfig {
pub base_level: Level,
pub log_constraint_details: bool,
pub log_data_operations: bool,
pub log_metrics: bool,
pub max_field_length: usize,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
base_level: Level::INFO,
log_constraint_details: false,
log_data_operations: true,
log_metrics: true,
max_field_length: 256,
}
}
}
impl LogConfig {
pub fn verbose() -> Self {
Self {
base_level: Level::DEBUG,
log_constraint_details: true,
log_data_operations: true,
log_metrics: true,
max_field_length: 1024,
}
}
pub fn production() -> Self {
Self {
base_level: Level::WARN,
log_constraint_details: false,
log_data_operations: false,
log_metrics: false,
max_field_length: 128,
}
}
pub fn balanced() -> Self {
Self::default()
}
}
#[macro_export]
macro_rules! perf_debug {
($config:expr, $($arg:tt)*) => {
if $config.base_level <= tracing::Level::DEBUG {
tracing::debug!($($arg)*);
}
};
}
#[macro_export]
macro_rules! log_constraint {
($config:expr, $($arg:tt)*) => {
if $config.log_constraint_details {
tracing::debug!($($arg)*);
}
};
}
#[macro_export]
macro_rules! log_data_op {
($config:expr, $($arg:tt)*) => {
if $config.log_data_operations {
tracing::info!($($arg)*);
}
};
}
pub fn truncate_field(value: &str, max_length: usize) -> String {
if value.len() <= max_length {
value.to_string()
} else {
let truncated = &value[..max_length];
format!("{truncated}...(truncated)")
}
}
pub mod setup {
use tracing::Level;
#[derive(Debug, Clone)]
pub struct LoggingConfig {
pub level: Level,
pub term_level: Level,
pub json_format: bool,
pub trace_correlation: bool,
pub env_filter: Option<String>,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: Level::INFO,
term_level: Level::DEBUG,
json_format: false,
trace_correlation: false,
env_filter: None,
}
}
}
impl LoggingConfig {
pub fn production() -> Self {
Self {
level: Level::WARN,
term_level: Level::INFO,
json_format: true,
trace_correlation: true,
env_filter: None,
}
}
pub fn development() -> Self {
Self {
level: Level::DEBUG,
term_level: Level::DEBUG,
json_format: false,
trace_correlation: false,
env_filter: None,
}
}
pub fn structured() -> Self {
Self {
level: Level::INFO,
term_level: Level::DEBUG,
json_format: true,
trace_correlation: true,
env_filter: None,
}
}
pub fn with_level(mut self, level: Level) -> Self {
self.level = level;
self
}
pub fn with_term_level(mut self, level: Level) -> Self {
self.term_level = level;
self
}
pub fn with_json_format(mut self, enabled: bool) -> Self {
self.json_format = enabled;
self
}
pub fn with_trace_correlation(mut self, enabled: bool) -> Self {
self.trace_correlation = enabled;
self
}
pub fn with_env_filter(mut self, filter: impl Into<String>) -> Self {
self.env_filter = Some(filter.into());
self
}
pub fn env_filter(&self) -> String {
if let Some(ref filter) = self.env_filter {
filter.clone()
} else {
format!(
"{}={},term_guard={}",
self.level.as_str().to_lowercase(),
self.level.as_str().to_lowercase(),
self.term_level.as_str().to_lowercase()
)
}
}
}
pub fn init_logging(config: LoggingConfig) -> Result<(), Box<dyn std::error::Error>> {
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(config.env_filter()));
let fmt_layer = if config.json_format {
tracing_subscriber::fmt::layer().json().boxed()
} else {
tracing_subscriber::fmt::layer().boxed()
};
let subscriber = tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer);
subscriber.init();
Ok(())
}
#[cfg(feature = "telemetry")]
#[allow(dead_code)] pub fn init_logging_with_telemetry<T>(
_config: LoggingConfig,
_tracer: T,
) -> Result<(), Box<dyn std::error::Error>>
where
T: tracing_opentelemetry::PreSampledTracer + opentelemetry::trace::Tracer + 'static,
{
Err("init_logging_with_telemetry is temporarily disabled due to dependency version conflicts. Please initialize telemetry and logging separately.".into())
}
#[cfg(not(feature = "telemetry"))]
pub fn init_logging_with_telemetry(
config: LoggingConfig,
_tracer: (),
) -> Result<(), Box<dyn std::error::Error>> {
tracing::warn!("Telemetry feature not enabled, falling back to basic logging");
init_logging(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_config_defaults() {
let config = LogConfig::default();
assert_eq!(config.base_level, Level::INFO);
assert!(!config.log_constraint_details);
assert!(config.log_data_operations);
assert!(config.log_metrics);
assert_eq!(config.max_field_length, 256);
}
#[test]
fn test_log_config_verbose() {
let config = LogConfig::verbose();
assert_eq!(config.base_level, Level::DEBUG);
assert!(config.log_constraint_details);
assert!(config.log_data_operations);
assert!(config.log_metrics);
assert_eq!(config.max_field_length, 1024);
}
#[test]
fn test_log_config_production() {
let config = LogConfig::production();
assert_eq!(config.base_level, Level::WARN);
assert!(!config.log_constraint_details);
assert!(!config.log_data_operations);
assert!(!config.log_metrics);
assert_eq!(config.max_field_length, 128);
}
#[test]
fn test_truncate_field() {
let short_text = "hello";
assert_eq!(truncate_field(short_text, 10), "hello");
let long_text = "this is a very long text that should be truncated";
assert_eq!(truncate_field(long_text, 10), "this is a ...(truncated)");
}
}