/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use std::{path::PathBuf, time::SystemTime}; use crate::config::telemetry::{LogTracer, RotationStrategy}; use mail_parser::DateTime; use tokio::{ fs::{File, OpenOptions}, io::BufWriter, }; use trc::{TelemetryEvent, ipc::subscriber::SubscriberBuilder, serializers::text::FmtWriter}; pub(crate) fn spawn_log_tracer(builder: SubscriberBuilder, settings: LogTracer) { let (_, mut rx) = builder.register(); tokio::spawn(async move { if let Some(writer) = settings.build_writer().await { let mut buf = FmtWriter::new(writer) .with_ansi(settings.ansi) .with_multiline(settings.multiline); let mut roatation_timestamp = settings.next_rotation(); while let Some(events) = rx.recv().await { for event in events { // Check if we need to rotate the log file if roatation_timestamp != 0 && event.inner.timestamp > roatation_timestamp { if let Err(err) = buf.flush().await { trc::event!( Telemetry(TelemetryEvent::LogError), Reason = err.to_string(), Details = "Failed to flush log buffer" ); } if let Some(writer) = settings.build_writer().await { buf.update_writer(writer); roatation_timestamp = settings.next_rotation(); } else { return; }; } if let Err(err) = buf.write(&event).await { trc::event!( Telemetry(TelemetryEvent::LogError), Reason = err.to_string(), Details = "Failed to write event to log" ); return; } } if let Err(err) = buf.flush().await { trc::event!( Telemetry(TelemetryEvent::LogError), Reason = err.to_string(), Details = "Failed to flush log buffer" ); } } } }); } impl LogTracer { pub async fn build_writer(&self) -> Option> { let now = DateTime::from_timestamp( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map_or(0, |d| d.as_secs()) as i64, ); let file_name = match self.rotate { RotationStrategy::Daily => { format!( "{}.{:04}-{:02}-{:02}", self.prefix, now.year, now.month, now.day ) } RotationStrategy::Hourly => { format!( "{}.{:04}-{:02}-{:02}T{:02}", self.prefix, now.year, now.month, now.day, now.hour ) } RotationStrategy::Minutely => { format!( "{}.{:04}-{:02}-{:02}T{:02}:{:02}", self.prefix, now.year, now.month, now.day, now.hour, now.minute ) } RotationStrategy::Never => self.prefix.clone(), }; let path = PathBuf::from(&self.path).join(file_name); match OpenOptions::new() .create(true) .append(true) .open(&path) .await { Ok(writer) => Some(BufWriter::new(writer)), Err(err) => { trc::event!( Telemetry(TelemetryEvent::LogError), Details = "Failed to create log file", Path = path.to_string_lossy().into_owned(), Reason = err.to_string(), ); None } } } pub fn next_rotation(&self) -> u64 { let mut now = DateTime::from_timestamp( SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map_or(0, |d| d.as_secs()) as i64, ); now.second = 0; match self.rotate { RotationStrategy::Daily => { now.hour = 0; now.minute = 0; now.to_timestamp() as u64 + 86400 } RotationStrategy::Hourly => { now.minute = 0; now.to_timestamp() as u64 + 3600 } RotationStrategy::Minutely => now.to_timestamp() as u64 + 60, RotationStrategy::Never => 0, } } }