use std::path::Path;
use std::time::Instant;
pub struct SimpleTracer;
impl SimpleTracer {
pub fn is_enabled() -> bool {
std::env::var("RGEN_TRACE").is_ok()
}
pub fn trace_level() -> TraceLevel {
match std::env::var("RGEN_TRACE")
.unwrap_or_default()
.to_lowercase()
.as_str()
{
"error" => TraceLevel::Error,
"warn" => TraceLevel::Warn,
"info" => TraceLevel::Info,
"debug" => TraceLevel::Debug,
"trace" => TraceLevel::Trace,
"1" | "true" | "yes" => TraceLevel::Debug,
"0" | "false" | "no" => TraceLevel::Error,
_ => TraceLevel::Info,
}
}
pub fn trace(level: TraceLevel, message: &str, context: Option<&str>) {
if !Self::is_enabled() {
return;
}
let current_level = Self::trace_level();
if level as u8 > current_level as u8 {
return;
}
let prefix = match level {
TraceLevel::Error => "ERROR",
TraceLevel::Warn => "WARN ",
TraceLevel::Info => "INFO ",
TraceLevel::Debug => "DEBUG",
TraceLevel::Trace => "TRACE",
};
if let Some(ctx) = context {
eprintln!("[RGEN {}] {}: {}", prefix, ctx, message);
} else {
eprintln!("[RGEN {}] {}", prefix, message);
}
}
pub fn template_start(template_path: &Path) {
Self::trace(
TraceLevel::Info,
&format!("Starting template processing: {}", template_path.display()),
None,
);
}
pub fn template_complete(template_path: &Path, output_path: &Path, content_size: usize) {
Self::trace(
TraceLevel::Info,
&format!(
"Template processing complete: {} -> {} ({} bytes)",
template_path.display(),
output_path.display(),
content_size
),
None,
);
}
pub fn frontmatter_processed(frontmatter: &crate::template::Frontmatter) {
Self::trace(
TraceLevel::Debug,
&format!(
"Frontmatter processed: to={:?}, inject={}, vars={}",
frontmatter.to,
frontmatter.inject,
frontmatter.vars.len()
),
Some("frontmatter"),
);
}
pub fn context_blessed(vars_count: usize) {
Self::trace(
TraceLevel::Debug,
&format!(
"Context blessed: {} variables (Name, locals added)",
vars_count
),
Some("context"),
);
}
pub fn rdf_loading(files: &[String], inline_blocks: usize, triples: usize) {
Self::trace(
TraceLevel::Info,
&format!(
"RDF loaded: {} files, {} inline blocks, {} triples",
files.len(),
inline_blocks,
triples
),
Some("rdf"),
);
}
pub fn sparql_query(query: &str, result_count: Option<usize>) {
let count_str = result_count
.map(|c| c.to_string())
.unwrap_or_else(|| "N/A".to_string());
Self::trace(
TraceLevel::Debug,
&format!("SPARQL query: {} results", count_str),
Some("sparql"),
);
Self::trace(TraceLevel::Trace, query, Some("sparql"));
}
pub fn file_injection(target_path: &Path, mode: &str, success: bool) {
let status = if success { "completed" } else { "failed" };
Self::trace(
TraceLevel::Info,
&format!("File injection {}: {} mode", status, mode),
Some(&format!("injection:{}", target_path.display())),
);
}
pub fn shell_hook(command: &str, timing: &str, exit_code: i32) {
let status = if exit_code == 0 {
"completed"
} else {
"failed"
};
Self::trace(
TraceLevel::Info,
&format!(
"Shell hook {}: {} (exit code: {})",
status, timing, exit_code
),
Some(&format!("hook:{}", command)),
);
}
pub fn performance(operation: &str, duration_ms: u64) {
Self::trace(
TraceLevel::Debug,
&format!("Performance: {} took {}ms", operation, duration_ms),
Some("performance"),
);
}
pub fn dry_run(output_path: &Path, content_size: usize) {
Self::trace(
TraceLevel::Info,
&format!(
"DRY RUN: Would generate {} ({} bytes)",
output_path.display(),
content_size
),
Some("dry_run"),
);
}
pub fn backup_created(original_path: &Path, backup_path: &Path) {
Self::trace(
TraceLevel::Info,
&format!(
"Backup created: {} -> {}",
original_path.display(),
backup_path.display()
),
Some("backup"),
);
}
pub fn skip_condition(condition: &str, reason: &str) {
Self::trace(
TraceLevel::Info,
&format!("Skipped: {} ({})", condition, reason),
Some("skip"),
);
}
pub fn error(error: &anyhow::Error, context: &str) {
Self::trace(
TraceLevel::Error,
&format!("Error in {}: {}", context, error),
Some("error"),
);
}
pub fn warning(message: &str, context: Option<&str>) {
Self::trace(TraceLevel::Warn, message, context);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TraceLevel {
Error = 0,
Warn = 1,
Info = 2,
Debug = 3,
Trace = 4,
}
pub struct SimpleTimer {
start: Instant,
operation: String,
}
impl SimpleTimer {
pub fn start(operation: &str) -> Self {
Self {
start: Instant::now(),
operation: operation.to_string(),
}
}
pub fn finish(self) {
let duration = self.start.elapsed();
SimpleTracer::performance(&self.operation, duration.as_millis() as u64);
}
}
#[macro_export]
macro_rules! time_operation {
($name:expr, $block:block) => {{
let _timer = $crate::simple_tracing::SimpleTimer::start($name);
let result = $block;
_timer.finish();
result
}};
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_trace_level_ordering() {
assert!(TraceLevel::Error < TraceLevel::Warn);
assert!(TraceLevel::Warn < TraceLevel::Info);
assert!(TraceLevel::Info < TraceLevel::Debug);
assert!(TraceLevel::Debug < TraceLevel::Trace);
}
#[test]
fn test_simple_timer() {
let timer = SimpleTimer::start("test_operation");
std::thread::sleep(std::time::Duration::from_millis(10));
timer.finish(); }
#[test]
fn test_tracing_methods() {
let temp_dir = TempDir::new().unwrap();
let test_path = temp_dir.path().join("test.tmpl");
fs::write(&test_path, "test content").unwrap();
SimpleTracer::template_start(&test_path);
SimpleTracer::template_complete(&test_path, &test_path, 100);
let frontmatter = crate::template::Frontmatter::default();
SimpleTracer::frontmatter_processed(&frontmatter);
SimpleTracer::context_blessed(5);
SimpleTracer::rdf_loading(&["file1.ttl".to_string()], 2, 100);
SimpleTracer::sparql_query("SELECT * WHERE { ?s ?p ?o }", Some(10));
SimpleTracer::file_injection(&test_path, "append", true);
SimpleTracer::shell_hook("echo 'test'", "before", 0);
SimpleTracer::performance("test_operation", 50);
SimpleTracer::dry_run(&test_path, 500);
SimpleTracer::backup_created(&test_path, &temp_dir.path().join("backup.tmpl"));
SimpleTracer::skip_condition("skip_if", "pattern found");
let error = anyhow::anyhow!("Test error");
SimpleTracer::error(&error, "test context");
SimpleTracer::warning("Test warning", Some("test context"));
SimpleTracer::warning("Test warning", None);
}
#[test]
fn test_tracing_environment_variables() {
let test_values = [
"error", "warn", "info", "debug", "trace", "1", "0", "true", "false",
];
for value in &test_values {
std::env::set_var("RGEN_TRACE", value);
let level = SimpleTracer::trace_level();
assert!(matches!(
level,
TraceLevel::Error
| TraceLevel::Warn
| TraceLevel::Info
| TraceLevel::Debug
| TraceLevel::Trace
));
}
}
#[test]
fn test_time_operation_macro() {
let result = crate::time_operation!("test_op", {
std::thread::sleep(std::time::Duration::from_millis(2));
42
});
assert_eq!(result, 42);
}
}