/* * Copyright (C) 2023 Valentin Lorentz * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::collections::VecDeque; use std::sync::RwLock; use color_eyre::eyre::Result; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; use crate::utils::{get_data_dir, LOG_ENV, LOG_FILE}; pub struct LogWriter { sender: UnboundedSender, last_line: Vec, } impl LogWriter { fn new(sender: UnboundedSender) -> Self { LogWriter { sender, last_line: Vec::new(), } } } impl std::io::Write for LogWriter { fn write(&mut self, bytes: &[u8]) -> Result { self.last_line.extend(bytes); if bytes.contains(&b'\n') { // Need to do this because tracing_subscriber never ever call flush() itself? self.flush()?; } Ok(bytes.len()) } fn flush(&mut self) -> Result<(), std::io::Error> { let mut new_lines: Vec<&[u8]> = self.last_line.split(|&c| c == b'\n').collect(); if new_lines.len() == 1 { // No new line, nothing to do } else { let last_line = new_lines.pop().expect("Split returned empty vec").to_vec(); for new_line in new_lines.into_iter() { let new_line = String::from_utf8_lossy(new_line).to_string(); self .sender .send(new_line) .unwrap_or_else(|e| panic!("Could not push log line: {:?}", e)); } self.last_line.clear(); self.last_line.extend(&last_line[..]); } Ok(()) } } pub fn initialize_logging() -> Result> { let directory = get_data_dir(); std::fs::create_dir_all(directory.clone())?; let log_path = directory.join(LOG_FILE.clone()); let log_file = std::fs::File::create(log_path)?; std::env::set_var( "RUST_LOG", std::env::var("RUST_LOG") .or_else(|_| std::env::var(LOG_ENV.clone())) .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), ); let file_subscriber = tracing_subscriber::fmt::layer() .with_file(true) .with_line_number(true) .with_writer(log_file) .with_target(false) .with_ansi(false) .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); // We keep the log until the application stops, so we might as well return it as &'static let (sender, receiver) = unbounded_channel(); let mem_subscriber = tracing_subscriber::fmt::layer() .with_ansi(true) .with_file(true) .with_line_number(true) .with_target(false) .with_writer(move || LogWriter::new(sender.clone())) .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); tracing_subscriber::registry() .with(mem_subscriber) .with(file_subscriber) .with(ErrorLayer::default()) .init(); Ok(receiver) }