use std::path::{Path, PathBuf};
use thiserror::Error;
use tracing::level_filters::LevelFilter;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::{SubscriberInitExt, TryInitError};
use tracing_subscriber::{EnvFilter, Layer, Registry};
#[derive(Debug, Error)]
pub enum TelemetryError {
#[error("OXEN_LOG_DIR set but is empty, cannot enable JSON file logging.")]
EmptyLogDir,
#[error("Requested JSON file logging cannot be enabled because OXEN_LOG_DIR is a file: {0}")]
LogDirIsFile(PathBuf),
#[error("Failed to create log directory ({0}): {1}")]
CreateLogDir(PathBuf, std::io::Error),
#[error("Failed to initialize tracing: {0}")]
InitFail(#[from] TryInitError),
}
pub fn init_tracing(
app_name: &str,
default: LevelFilter,
) -> Result<Option<WorkerGuard>, TelemetryError> {
let log_level = log_env_filter(default);
let stderr_layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_target(true);
let maybe_log_dir = match std::env::var("OXEN_LOG_DIR").ok() {
Some(log_dir) => {
let created_log_dir = create_log_dir(&log_dir)?;
Some(created_log_dir)
}
None => None,
};
let (maybe_json_layer, maybe_worker_guard) = if let Some(log_dir) = maybe_log_dir {
let (jl, wg) = json_file_logging(app_name, &log_dir);
(Some(jl), Some(wg))
} else {
(None, None)
};
tracing_subscriber::registry()
.with(maybe_json_layer)
.with(stderr_layer)
.with(log_level)
.try_init()?;
Ok(maybe_worker_guard)
}
fn log_env_filter(default: LevelFilter) -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
EnvFilter::builder()
.with_default_directive(default.into())
.parse_lossy("")
})
}
fn create_log_dir(oxen_log_dir: &str) -> Result<PathBuf, TelemetryError> {
let oxen_log_dir = oxen_log_dir.trim();
if oxen_log_dir.is_empty() {
Err(TelemetryError::EmptyLogDir)
} else {
let log_dir = PathBuf::from(oxen_log_dir);
if log_dir.is_file() {
Err(TelemetryError::LogDirIsFile(log_dir))
} else {
match std::fs::create_dir_all(&log_dir) {
Ok(()) => Ok(log_dir),
Err(e) => Err(TelemetryError::CreateLogDir(log_dir, e)),
}
}
}
}
fn json_file_logging(app_name: &str, log_dir: &Path) -> (impl Layer<Registry>, WorkerGuard) {
let file_appender = tracing_appender::rolling::daily(log_dir, app_name);
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let layer = tracing_subscriber::fmt::layer()
.json()
.with_writer(non_blocking)
.with_target(true)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true);
(layer, guard)
}