use crate::__private::cargo::metadata;
use crate::__private::constant::{self, MACRO_EXPAND};
use crate::__private::model::{TraceEntry, TraceEvent};
use crate::__private::utils::now;
use std::cell::RefCell;
use std::fs::{self, File};
use std::io::{BufWriter, Write};
use std::path::PathBuf;
thread_local! {
static VACRO_DIRECTORY: RefCell<PathBuf> = {
let target_directory = if let Ok(dir) = std::env::var("CARGO_TARGET_DIR") {
std::path::PathBuf::from(dir)
} else {
match metadata() {
Ok(metadata) => metadata.target_directory,
Err(e) => {
eprintln!("[Vacro Trace Warning] Failed to get metadata: {}", e);
std::env::current_dir().ok().unwrap_or_else(|| PathBuf::from("target"))
}
}
};
let vacro_directory = target_directory.join("vacro");
RefCell::new(vacro_directory)
};
static WRITER: RefCell<Option<BufWriter<File>>> = const { RefCell::new(None) };
static CURRENT_CONTEXT: RefCell<Option<TraceSession>> = const { RefCell::new(None) };
}
fn create_writer(session: &TraceSession) -> Option<BufWriter<File>> {
let vacro_directory = VACRO_DIRECTORY.with(|dir| dir.borrow().clone());
if let Err(e) = fs::create_dir_all(&vacro_directory) {
eprintln!(
"[Vacro Trace Warning] Failed to create vacro directory: {}",
e
);
return None;
}
let trace_file = vacro_directory.join(format!("trace-{}.jsonl", session.id));
match File::create(trace_file) {
Ok(f) => Some(BufWriter::new(f)),
Err(e) => {
eprintln!("[Vacro Trace Error] Failed to create log file: {}", e);
None
}
}
}
#[derive(Debug, Clone)]
pub struct TraceSession {
pub id: String, pub macro_name: String, pub crate_name: String, pub timestamp: u64,
pub path: PathBuf,
}
#[allow(dead_code)]
impl TraceSession {
pub fn enter(macro_name: &str, crate_name: &str) -> SessionGuard {
let mut session = Self::new();
session.macro_name = macro_name.to_string();
session.crate_name = crate_name.to_string();
CURRENT_CONTEXT.with(|ctx| *ctx.borrow_mut() = Some(session));
let event = TraceEvent::PhaseStart {
name: MACRO_EXPAND.to_string(),
time: now(),
};
Self::emit(event);
SessionGuard
}
pub fn new() -> Self {
let id = uuid::Uuid::new_v4().to_string();
let macro_name = String::new();
let crate_name =
std::env::var(constant::CARGO_CRATE_NAME).unwrap_or_else(|_| "unknown".to_string());
let timestamp = now();
let path = VACRO_DIRECTORY.with(|dir| dir.borrow().clone());
Self {
id,
macro_name,
crate_name,
timestamp,
path,
}
}
pub fn macro_name(macro_name: &str) {
CURRENT_CONTEXT.with(|ctx| {
let mut borrow = ctx.borrow_mut();
if let Some(ctx) = borrow.as_mut() {
ctx.macro_name = macro_name.to_string();
}
});
}
pub fn crate_name(crate_name: &str) {
CURRENT_CONTEXT.with(|ctx| {
let mut borrow = ctx.borrow_mut();
if let Some(ctx) = borrow.as_mut() {
ctx.crate_name = crate_name.to_string();
}
});
}
pub fn get_session() -> Option<TraceSession> {
CURRENT_CONTEXT.with(|ctx| ctx.borrow().clone())
}
pub fn emit(event: TraceEvent) {
if let Some(session) = Self::get_session() {
WRITER.with(|cell| {
let mut borrow = cell.borrow_mut();
if borrow.is_none() {
*borrow = create_writer(&session);
}
let msg = TraceEntry {
id: session.id,
macro_name: session.macro_name,
crate_name: session.crate_name,
timestamp: session.timestamp,
message: event,
};
if let Some(writer) = borrow.as_mut() {
if let Err(e) = writeln!(writer, "{}", serde_json::to_string(&msg).unwrap()) {
eprintln!("[Vacro Trace Error] Failed to write to log: {}", e);
}
}
});
} else {
eprintln!("[Vacro Trace Warning] writeln called but no session found.");
}
}
}
impl Default for TraceSession {
fn default() -> Self {
TraceSession::new()
}
}
pub struct SessionGuard;
impl Drop for SessionGuard {
fn drop(&mut self) {
let event = TraceEvent::PhaseEnd {
name: MACRO_EXPAND.to_string(),
time: now(),
};
TraceSession::emit(event);
CURRENT_CONTEXT.with(|ctx| *ctx.borrow_mut() = None);
WRITER.with(|cell| {
if let Ok(mut borrow) = cell.try_borrow_mut() {
if let Some(mut writer) = borrow.take() {
if let Err(e) = writer.flush() {
eprintln!("[Vacro Trace Warning] Failed to flush trace log: {}", e);
}
}
}
});
}
}