use std::io::IsTerminal;
use std::path::PathBuf;
use tracing::Level;
use tracing_subscriber::Registry;
use tracing_subscriber::layer::Filter;
use tracing_subscriber::{Layer, filter::filter_fn, layer::SubscriberExt, util::SubscriberInitExt};
use super::layer::StructuredLogLayer;
use super::{LogHandles, StructuredLog, is_structured_target};
#[derive(Debug, bon::Builder)]
pub struct LogSettings {
level: Level,
streams: Option<FileStreams>,
}
#[derive(Debug, bon::Builder)]
pub struct FileStreams {
directory: PathBuf,
synchronization: bool,
ffevents: bool,
}
impl FileStreams {
fn enables(&self, stream: StructuredLog) -> bool {
match stream {
StructuredLog::FFEvents => self.ffevents,
StructuredLog::Synchronization => self.synchronization,
StructuredLog::AlgoAnalysis => false,
}
}
}
pub fn init(settings: &LogSettings) -> LogHandles {
let cargo_version = env!("CARGO_PKG_VERSION");
let streams = settings.streams.as_ref();
let daemon_streams = [StructuredLog::FFEvents, StructuredLog::Synchronization];
let mut structured_layers = Vec::new();
let mut handles = Vec::new();
if let Some(streams) = streams {
for stream in daemon_streams
.into_iter()
.filter(|stream| streams.enables(*stream))
{
let (layer, handle) =
StructuredLogLayer::for_stream(&streams.directory, stream, cargo_version);
structured_layers.push(layer);
handles.push(handle);
}
}
let log_layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stdout) .with_ansi(std::io::stdout().is_terminal())
.with_filter(clockbound_filter(settings.level))
.with_filter(filter_fn(|md| !is_structured_target(md.target())));
let structured_layers: Option<Vec<StructuredLogLayer>> =
(!structured_layers.is_empty()).then_some(structured_layers);
tracing_subscriber::registry()
.with(log_layer)
.with(structured_layers)
.init();
tracing::debug!("Initialized tracing subscriber");
if let Some(streams) = streams {
tracing::info!(log_directory = %streams.directory.display(), "Initialized log directory");
}
LogHandles { handles }
}
#[cfg(feature = "env-filter")]
fn clockbound_filter(level: Level) -> impl Filter<Registry> {
use tracing_subscriber::filter::EnvFilter;
EnvFilter::builder()
.with_default_directive(level.into())
.from_env_lossy()
}
#[cfg(not(feature = "env-filter"))]
fn clockbound_filter(level: Level) -> impl Filter<Registry> {
use tracing_subscriber::filter::Targets;
Targets::new().with_default(level)
}