light_program_test/logging/
mod.rs1pub mod config;
17pub mod decoder;
18pub mod formatter;
19pub mod types;
20
21use std::{
22 fs::OpenOptions,
23 io::Write,
24 path::PathBuf,
25 time::{SystemTime, UNIX_EPOCH},
26};
27
28use chrono;
29pub use config::{EnhancedLoggingConfig, LogVerbosity};
30pub use formatter::TransactionFormatter;
31use litesvm::types::TransactionResult;
32use solana_sdk::{signature::Signature, transaction::Transaction};
33pub use types::{
34 AccountChange, EnhancedInstructionLog, EnhancedTransactionLog, ParsedInstructionData,
35 TransactionStatus,
36};
37
38use crate::program_test::config::ProgramTestConfig;
39
40static SESSION_STARTED: std::sync::Once = std::sync::Once::new();
41
42fn get_log_file_path() -> PathBuf {
44 use std::process::Command;
46 if let Ok(output) = Command::new("cargo")
47 .arg("metadata")
48 .arg("--format-version=1")
49 .arg("--no-deps")
50 .output()
51 {
52 if output.status.success() {
53 if let Ok(metadata) = String::from_utf8(output.stdout) {
54 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&metadata) {
55 if let Some(target_directory) = json["target_directory"].as_str() {
56 let mut path = PathBuf::from(target_directory);
57 path.push("light_program_test.log");
58 return path;
59 }
60 }
61 }
62 }
63 }
64
65 let mut path = PathBuf::from("target");
67 path.push("light_program_test.log");
68 path
69}
70
71fn initialize_log_file() {
73 SESSION_STARTED.call_once(|| {
74 let log_path = get_log_file_path();
75 let timestamp = SystemTime::now()
76 .duration_since(UNIX_EPOCH)
77 .unwrap_or_default()
78 .as_secs();
79
80 if let Ok(mut file) = OpenOptions::new()
82 .create(true)
83 .write(true)
84 .truncate(true)
85 .open(&log_path)
86 {
87 let datetime =
89 chrono::DateTime::from_timestamp(timestamp as i64, 0).unwrap_or(chrono::Utc::now());
90 let formatted_date = datetime.format("%Y-%m-%d %H:%M:%S UTC");
91
92 let _ = writeln!(
93 file,
94 "=== Light Program Test Session Started at {} ===\n",
95 formatted_date
96 );
97 }
98 });
99}
100
101fn strip_ansi_codes(text: &str) -> String {
103 let mut result = String::with_capacity(text.len());
105 let mut chars = text.chars();
106
107 while let Some(ch) = chars.next() {
108 if ch == '\x1b' {
109 for next_ch in chars.by_ref() {
111 if next_ch == 'm' {
112 break;
113 }
114 }
115 } else {
116 result.push(ch);
117 }
118 }
119
120 result
121}
122
123fn write_to_log_file(content: &str) {
125 initialize_log_file();
127
128 let log_path = get_log_file_path();
129
130 if let Some(parent) = log_path.parent() {
132 let _ = std::fs::create_dir_all(parent);
133 }
134
135 let clean_content = strip_ansi_codes(content);
137
138 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
140 let _ = writeln!(file, "{}", clean_content);
141 }
142}
143
144pub fn log_transaction_enhanced(
146 config: &ProgramTestConfig,
147 transaction: &Transaction,
148 result: &TransactionResult,
149 signature: &Signature,
150 slot: u64,
151 transaction_counter: usize,
152) {
153 log_transaction_enhanced_with_console(
154 config,
155 transaction,
156 result,
157 signature,
158 slot,
159 transaction_counter,
160 false,
161 )
162}
163
164pub fn log_transaction_enhanced_with_console(
166 config: &ProgramTestConfig,
167 transaction: &Transaction,
168 result: &TransactionResult,
169 signature: &Signature,
170 slot: u64,
171 transaction_counter: usize,
172 print_to_console: bool,
173) {
174 if !config.enhanced_logging.enabled {
175 return;
176 }
177
178 let enhanced_log = EnhancedTransactionLog::from_transaction_result(
179 transaction,
180 result,
181 signature,
182 slot,
183 &config.enhanced_logging,
184 );
185
186 let formatter = TransactionFormatter::new(&config.enhanced_logging);
187 let formatted_log = formatter.format(&enhanced_log, transaction_counter);
188
189 write_to_log_file(&formatted_log);
191
192 if print_to_console {
194 println!("{}", formatted_log);
195 }
196}
197
198pub fn should_use_enhanced_logging(config: &ProgramTestConfig) -> bool {
200 config.enhanced_logging.enabled && !config.no_logs
201}