francoisgib_webserver 1.0.3

HTTP Webserver
Documentation
use std::{fs::OpenOptions, net::SocketAddr, sync::Arc};

use chrono::Utc;
use tokio::{fs::File, io::AsyncWriteExt, sync::Mutex};

use crate::http::{requests::HttpRequest, responses::HttpResponse};

/// A simple asynchronous logger for HTTP requests and responses.
///
/// # Fields
/// - `log_file`: Optional file to which logs are written asynchronously.
/// - `level`: Log level (currently not fully implemented, reserved for future use).
/// - `debug`: Whether to print logs to stdout as well.
#[allow(dead_code)] // Level usage TODO
pub struct Logger {
    log_file: Option<Arc<Mutex<File>>>,
    level: u8,
    debug: bool,
}

impl Logger {
    /// Creates a new `Logger`.
    ///
    /// # Arguments
    /// - `log_file`: Optional path to a log file. If provided, logs will be appended here.
    /// - `level`: Optional log level (default is `1`).
    /// - `debug`: Optional debug flag to also print logs to stdout (default is `false`).
    ///
    /// # Panics
    /// Panics if the file cannot be opened or created.
    pub fn new(log_file: Option<String>, level: Option<u8>, debug: Option<bool>) -> Self {
        let log_file = if let Some(log_file_path) = log_file {
            let file = OpenOptions::new()
                .create(true)
                .append(true)
                .open(log_file_path)
                .unwrap();
            Some(Arc::new(Mutex::new(File::from_std(file))))
        } else {
            None
        };

        Self {
            log_file,
            level: level.or(Some(1)).unwrap(),
            debug: debug.or(Some(false)).unwrap(),
        }
    }

    /// Logs an HTTP request and response, optionally to a file and/or stdout.
    ///
    /// # Arguments
    /// - `request`: The HTTP request to log.
    /// - `response`: The HTTP response to log.
    /// - `stream`: The TCP stream (used for getting the client address).
    ///
    /// If debug mode is enabled, it prints the log to stdout.
    /// If a log file is configured, it asynchronously writes the log to the file.
    pub fn write_req_res(
        &self,
        request: &HttpRequest,
        response: &HttpResponse,
        socket_addr: &SocketAddr,
    ) {
        let response_format = format!(
            "{} | {} {} | {} {}\r\n",
            Utc::now().to_rfc2822(),
            socket_addr,
            request.uri,
            response.status as u16,
            response.status.to_string()
        );

        // let res = format!(
        //     "{} / {}\n----------------\n{}\n----------------\n{}\n--------------------------------\n",
        //     socket_addr,
        //     Utc::now().to_rfc2822(),
        //     request.to_string(),
        //     response.to_string()
        // );

        // if self.debug {
        //     println!("{res}");
        // }

        if self.debug {
            println!("{response_format}");
        }

        if let Some(log_file) = &self.log_file {
            let log_file = log_file.clone();
            tokio::spawn(async move {
                let mut f = log_file.lock().await;
                let _ = f.write_all(response_format.as_bytes()).await;
            });
        }
    }
}