use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use chrono::{FixedOffset, Utc};
use tracing_subscriber::fmt::time::FormatTime;
use tracing_subscriber::{fmt, EnvFilter};
const JST_OFFSET_SECS: i32 = 9 * 3600;
fn jst() -> FixedOffset {
FixedOffset::east_opt(JST_OFFSET_SECS).expect("invalid JST offset")
}
struct JstTimer;
impl FormatTime for JstTimer {
fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> std::fmt::Result {
let now = Utc::now().with_timezone(&jst());
write!(w, "{}", now.format("%Y-%m-%dT%H:%M:%S%.3f+09:00"))
}
}
struct JstRollingAppender {
dir: PathBuf,
prefix: String,
current_date: chrono::NaiveDate,
file: File,
}
impl JstRollingAppender {
fn new(dir: PathBuf, prefix: &str) -> std::io::Result<Self> {
let today = Utc::now().with_timezone(&jst()).date_naive();
let file = Self::open_log_file(&dir, prefix, today)?;
Ok(Self {
dir,
prefix: prefix.to_string(),
current_date: today,
file,
})
}
fn open_log_file(dir: &Path, prefix: &str, date: chrono::NaiveDate) -> std::io::Result<File> {
let filename = format!("{}_{}.log", prefix, date.format("%Y-%m-%d"));
OpenOptions::new()
.create(true)
.append(true)
.open(dir.join(filename))
}
}
impl Write for JstRollingAppender {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let today = Utc::now().with_timezone(&jst()).date_naive();
if today != self.current_date {
self.file = Self::open_log_file(&self.dir, &self.prefix, today)?;
self.current_date = today;
}
self.file.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.file.flush()
}
}
fn log_dir() -> PathBuf {
crate::storage::BlackBox::data_dir().join("logs")
}
pub fn init_logging(
log_dir_override: Option<PathBuf>,
) -> tracing_appender::non_blocking::WorkerGuard {
let log_dir = log_dir_override.unwrap_or_else(log_dir);
if let Err(e) = std::fs::create_dir_all(&log_dir) {
eprintln!(
"jarvish: warning: failed to create log directory {}: {e}",
log_dir.display()
);
}
let file_appender = JstRollingAppender::new(log_dir.clone(), "jarvish").unwrap_or_else(|e| {
panic!(
"jarvish: failed to create log file in {}: {e}",
log_dir.display()
)
});
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let env_filter =
EnvFilter::try_from_env("JARVISH_LOG").unwrap_or_else(|_| EnvFilter::new("debug"));
fmt()
.with_env_filter(env_filter)
.with_writer(non_blocking)
.with_timer(JstTimer) .with_ansi(false) .with_target(true) .with_thread_ids(false)
.with_line_number(true) .with_file(true) .init();
guard
}