Search code examples
loggingrustactix-web

actix_web middleware logger output to file


I currently have the logger outputting to the terminal, using the code below. However, web pages aren't loading until a key has been pressed in the terminal. Is there a way to output the logs to a file. I can't seem to find anything in the official documentation. actix_web middleware documentation

use actix_web::{middleware::Logger, get, post, web, App, HttpResponse, HttpServer, Responder};
use env_logger::Env;

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

async fn manual_hello() -> impl Responder {
    HttpResponse::Ok().body("Hey there!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(Env::default().default_filter_or("info"));
    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .service(hello)
            .service(echo)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Solution

  • I created a mod file called logger.rs and brought it into scope from lib.rs. I used the crates "colored" and "chrono"

    • logger.rs
    use std::fs::OpenOptions;
    use std::io::Write;
    use std::path::Path;
    use colored::{Colorize, control};
    use chrono::Local;
    
    // Path to log file
    const LOG_PATH: &str = "ServerLogs.log";
    
    /// List of different types of log headers.
    pub enum Header {
        SUCCESS,
        INFO,
        WARNING,
        ERROR
    }
    
    /// Logs a message to the console.
    pub fn log(header: Header, message: &str) {
        // Type of message to log
        let header = match header {
            Header::SUCCESS => "SUCCESS".bold().bright_green(),
            Header::INFO => "INFO".bold().bright_blue(),
            Header::WARNING => "WARNING".bold().bright_yellow(),
            Header::ERROR => "ERROR".bold().bright_red()
        };
    
        // Print the log to the console
        control::set_virtual_terminal(true).unwrap();
        println!("[{}] {} {}", Local::now().format("%m-%d-%Y %H:%M:%S").to_string().bold(), header, message);
    
        // Write the log to a file
        if Path::new(LOG_PATH).exists() {
            let mut log_file = OpenOptions::new().append(true).open(LOG_PATH).unwrap();
            writeln!(log_file, "[{}] {} {}", Local::now().format("%m-%d-%Y %H:%M:%S").to_string(), header.clear(), message).unwrap();
        } else {
            let mut log_file = OpenOptions::new().create_new(true).append(true).open(LOG_PATH).unwrap();
            writeln!(log_file, "[{}] {} {}", Local::now().format("%m-%d-%Y %H:%M:%S").to_string(), header.clear(), message).unwrap();
        }
    }
    
    • lib.rs
    pub mod logger;
    

    and now you can call the log function from anywhere in your code like so:

    #[get("/")]
    async fn hello() -> impl Responder {
        HttpResponse::Ok().body("Hello world!")
        logger::log(Header::SUCCESS, "Hello world!");
    }
    
    #[post("/echo")]
    async fn echo(req_body: String) -> impl Responder {
        HttpResponse::Ok().body(req_body)
        logger::log(Header::INFO, format!("echo: {}", req_body).as_str());
    }
    
    async fn manual_hello() -> impl Responder {
        HttpResponse::Ok().body("Hey there!")
        logger::log(Header::WARNING, "Hey there!");
    }
    

    Here is what it will look like in the log file:

    [09-27-2022 13:32:56] SUCCESS Hello world!
    [09-27-2022 13:41:25] INFO echo: test
    [09-27-2022 13:59:48] WARNING Hey there!
    

    Please feel free to edit and modify the code however you'd like to make it work best in your project. I hope this helps.