mod args;
use clap::Parser;
use pacsea::{app, theme, util};
use std::sync::OnceLock;
use std::{fmt, str::FromStr, time::SystemTime};
struct PacseaTimer;
impl tracing_subscriber::fmt::time::FormatTime for PacseaTimer {
fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> fmt::Result {
#[allow(clippy::cast_possible_wrap)]
let secs = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs() as i64);
let s = util::ts_to_date(Some(secs)); let ts = s.replacen(' ', "-T", 1); w.write_str(&ts)
}
}
static LOG_GUARD: OnceLock<tracing_appender::non_blocking::WorkerGuard> = OnceLock::new();
fn build_env_filter(log_level: &str) -> tracing_subscriber::EnvFilter {
let mut filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(log_level));
for directive in [
"pacsea::logic::services=warn",
"html5ever=warn",
"html5ever::tokenizer=warn",
"html5ever::tree_builder=warn",
"selectors=warn",
"selectors::matching=warn",
"markup5ever=warn",
"cssparser=warn",
"kuchiki=warn",
"scraper=warn",
] {
if let Ok(parsed) = tracing_subscriber::filter::Directive::from_str(directive) {
filter = filter.add_directive(parsed);
}
}
filter
}
#[tokio::main]
async fn main() {
let args = args::Args::parse();
let log_level = args::determine_log_level(&args);
{
let mut log_path = theme::logs_dir();
log_path.push("pacsea.log");
match std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
{
Ok(file) => {
let (non_blocking, guard) = tracing_appender::non_blocking(file);
let env_filter = build_env_filter(&log_level);
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_target(false)
.with_ansi(false) .with_timer(PacseaTimer)
.with_writer(non_blocking)
.init();
let _ = LOG_GUARD.set(guard);
tracing::info!(path = %log_path.display(), "logging initialized");
}
Err(e) => {
let env_filter = build_env_filter(&log_level);
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_target(false)
.with_ansi(!args.no_color)
.with_timer(PacseaTimer)
.init();
tracing::warn!(error = %e, "failed to open log file; using stderr");
}
}
}
let _ = args::process_args(&args);
tracing::info!(dry_run = args.dry_run, "Pacsea starting");
if let Err(err) = app::run(args.dry_run).await {
tracing::error!(error = ?err, "Application error");
}
tracing::info!("Pacsea exited");
}
#[cfg(test)]
mod tests {
#[test]
fn pacsea_timer_formats_time_without_panic() {
use tracing_subscriber::fmt::time::FormatTime;
let mut buf = String::new();
let mut writer = tracing_subscriber::fmt::format::Writer::new(&mut buf);
let t = super::PacseaTimer;
let _ = t.format_time(&mut writer);
assert!(!buf.is_empty());
}
}