balius_runtime/logging/
file.rs

1/// Log to a file for development.
2use std::{
3    collections::BTreeMap,
4    fs::{File, OpenOptions},
5    io::{self, Write}, // Import io for Result
6    path::PathBuf,
7};
8
9use crate::wit::balius::app::logging as wit;
10
11use super::{level_to_string, LoggerProvider};
12
13/// A logger that writes logs to files.
14pub struct FileLogger {
15    files: BTreeMap<String, File>,
16    folder: PathBuf,
17}
18
19impl FileLogger {
20    /// Tries to create a new `FileLogger` instance.
21    ///
22    /// Logs will be saved in the specified `folder`. If `None` is provided,
23    /// the current directory will be used.
24    pub fn try_new(folder: Option<PathBuf>) -> io::Result<Self> {
25        let folder = folder.unwrap_or(std::env::current_dir()?);
26
27        // Ensure the log folder exists
28        std::fs::create_dir_all(&folder)?; // Use ? for error propagation
29
30        Ok(FileLogger {
31            files: BTreeMap::new(),
32            folder,
33        })
34    }
35}
36
37#[async_trait::async_trait]
38impl LoggerProvider for FileLogger {
39    async fn log(&mut self, worker_id: &str, level: wit::Level, context: String, message: String) {
40        let mut file = match self.files.get(worker_id) {
41            Some(file) => file,
42            None => {
43                let Ok(file) = OpenOptions::new()
44                    .create(true)
45                    .append(true)
46                    .open(self.folder.join(format!("{worker_id}.log")))
47                else {
48                    return;
49                };
50                self.files.entry(worker_id.to_string()).or_insert(file)
51            }
52        };
53        let _ = file.write(
54            format!(
55                "{}: {} - {} - {}\n",
56                chrono::Utc::now().to_rfc3339(),
57                level_to_string(&level),
58                context,
59                message
60            )
61            .as_bytes(),
62        );
63    }
64}