1use std::{
9 fs::OpenOptions,
10 path::{Path, PathBuf},
11};
12
13use tracing::Level;
14use tracing_appender::non_blocking::WorkerGuard;
15use tracing_subscriber::{
16 filter::{LevelFilter, Targets},
17 fmt::format::FmtSpan,
18 layer::SubscriberExt,
19};
20
21use crate::{CommitGenError, Result};
22
23pub const TARGET: &str = "lgit";
25
26pub struct TraceGuard {
28 _guard: WorkerGuard,
29 path: PathBuf,
30}
31
32impl TraceGuard {
33 pub fn path(&self) -> &Path {
34 &self.path
35 }
36}
37
38pub fn init_file_tracing(path: &Path) -> Result<TraceGuard> {
43 if let Some(parent) = path
44 .parent()
45 .filter(|parent| !parent.as_os_str().is_empty())
46 {
47 std::fs::create_dir_all(parent)?;
48 }
49
50 let file = OpenOptions::new().create(true).append(true).open(path)?;
51 let (writer, guard) = tracing_appender::non_blocking(file);
52
53 let filter = Targets::new().with_target(TARGET, LevelFilter::TRACE);
54 let layer = tracing_subscriber::fmt::layer()
55 .json()
56 .with_ansi(false)
57 .with_current_span(true)
58 .with_span_list(true)
59 .with_target(true)
60 .with_level(true)
61 .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
62 .with_writer(writer);
63
64 let subscriber = tracing_subscriber::registry().with(filter).with(layer);
65 tracing::subscriber::set_global_default(subscriber).map_err(|error| {
66 CommitGenError::Other(format!("Failed to initialize profiling trace subscriber: {error}"))
67 })?;
68
69 tracing::info!(
70 target: TARGET,
71 event = "trace_started",
72 path = %path.display(),
73 pid = std::process::id(),
74 );
75
76 Ok(TraceGuard { _guard: guard, path: path.to_path_buf() })
77}
78
79#[inline]
80pub fn enabled() -> bool {
81 tracing::enabled!(target: TARGET, Level::INFO)
82}
83
84#[inline]
90pub fn section(section: &'static str) -> tracing::Span {
91 tracing::info_span!(target: TARGET, "profile.section", section)
92}