use crate::SessionSettings;
use crate::fix::mem::MsgBuf;
use chrono::offset::Local;
use chrono::{DateTime, Duration};
use std::fs::{File, OpenOptions};
use std::future::Future;
use std::io::{self, Write};
use std::sync::mpsc;
use std::time::Instant;
const LOG_FILE_TYPE: &str = "txt";
pub trait Logger: Send + 'static {
fn log_message(&mut self, msg: &MsgBuf) -> Result<(), io::Error>;
fn disconnect(self) -> impl Future<Output = Result<(), io::Error>> + Send;
}
pub trait LoggerFactory {
fn build(&self, settings: &SessionSettings) -> Result<impl Logger, io::Error>;
}
enum LoggerRequest {
Log(String, Instant),
Disconnect,
}
pub struct FileLoggerFactory;
impl LoggerFactory for FileLoggerFactory {
fn build(&self, settings: &SessionSettings) -> Result<impl Logger, io::Error> {
FileLogger::build(settings)
}
}
pub(super) struct FileLogger {
sender: mpsc::Sender<LoggerRequest>,
handle: tokio::task::JoinHandle<Result<(), io::Error>>,
}
impl Logger for FileLogger {
fn log_message(&mut self, buf: &MsgBuf) -> Result<(), io::Error> {
let req = LoggerRequest::Log(format!("{}", buf), Instant::now());
self.sender.send(req).map_err(io_err)?;
Ok(())
}
async fn disconnect(self) -> Result<(), io::Error> {
let _ = self.sender.send(LoggerRequest::Disconnect);
self.handle.await.map_err(|_| io_err(()))?
}
}
impl FileLogger {
pub(super) fn build(settings: &SessionSettings) -> Result<FileLogger, io::Error> {
let log_path = &settings.log_dir;
let sendercompid = settings.expected_sender_comp_id();
let targetcompid = settings.expected_target_comp_id();
std::fs::create_dir_all(log_path)?;
let mut logs = OpenOptions::new().create(true).append(true).open(
log_path
.join(format!("{}-{}", sendercompid, targetcompid))
.with_extension(LOG_FILE_TYPE),
)?;
let (sender, receiver) = mpsc::channel();
let handle = tokio::task::spawn_blocking(move || {
let begin_time = Local::now();
let begin_instant = Instant::now();
while let Ok(req) = receiver.recv() {
match req {
LoggerRequest::Log(msg, instant) => {
let send_time =
match Duration::from_std(instant.duration_since(begin_instant)) {
Ok(d) => begin_time + d,
Err(_) => Local::now(),
};
if let Err(e) = do_log_message(&mut logs, msg, send_time) {
eprintln!("error logging message: {e:?}")
}
}
LoggerRequest::Disconnect => {
break;
}
}
}
do_disconnect(&mut logs)
});
Ok(FileLogger { sender, handle })
}
}
fn do_log_message(logs: &mut File, buf: String, time: DateTime<Local>) -> Result<(), io::Error> {
writeln!(logs, "{} : {}", time.format("%Y%m%d-%H:%M:%S%.9f"), buf)
}
fn do_disconnect(logs: &mut File) -> Result<(), io::Error> {
logs.flush()
}
fn io_err<T>(_: T) -> io::Error {
io::Error::other("logger thread failed")
}