Step-by-Step Guide to Adding Logging to an Actix Web Application
Tags: rust, actix, logs, tracing
Published: 2023-06-20 17:50:05

---
title: Step-by-Step Guide to Adding Logging to an Actix Web Application
published: true
date: 2023-05-09 01:25:31 UTC
tags: #rust #actix #logs #tracing
canonical_url: https://carlosmv.hashnode.dev/step-by-step-guide-to-adding-logging-to-an-actix-web-application
---

I recently started working on a project that uses Actix, it is the first time I touch this web framework. And I have an issue that every time I start the server didn't show me a message that the server is running or when an endpoint is called.

Probably I have this issue because I'm used to using other web frameworks in other languages that have a default logging feature.

So I started to write this article just to learn how to add logging to an Actix application and have a resource to use in case I face the same problem again.

### Requirements

- Rust installed

**Cargo.toml**

```
[dependencies]
actix-web = "4"
tracing-actix-web = "0.7"
tracing = "0.1"
log = "0.4"
env_logger = "0.9"

```

**main.rs**

First, we start writing a minimum web server example that just shows a "Hello World!" message. And then, add logging to see when the server is called.

```
use actix_web::{get, App, HttpResponse, HttpServer, Responder};

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

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    HttpServer::new(|| {
        App::new()
            .service(hello)   
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

```

Now, we run the `cargo run` command to start the server.

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

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

#[get("/hi")]
async fn hi() -> impl Responder {
    HttpResponse::Ok().body("Greetings")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

   env_logger::init_from_env(Env::default().default_filter_or("info"));
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(hi)
            .wrap(Logger::default())

    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1682441717742/b10a567a-851f-4ae4-a7d6-fe844ed36d7c.png)

Here, Logging is implemented as a middleware. It is common to register a logging middleware as the first middleware for the application. Logging middleware must be registered for each application.

The `env_logger::init_from_env(Env::default().default_filter_or("info"));` line initializes the logger with the default log level of "info", which will output logs to the console in a standard format.

`.wrap(Logger::default())` attaches the `Logger` middleware to the app, which logs requests and responses automatically.

The `Logger` middleware uses the standard log crate to log information. You should enable logger for the _actix\_web_ package to see access log ([env\_logger](https://docs.rs/env_logger/*/env_logger/) or similar).

Create Logger middleware with the specified format. Default Logger can be created with the default method, it uses the default format:

```
  %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T

```

### Format

- `%%` The percent sign

- `%a` Remote IP-address (IP-address of proxy if using reverse proxy)

- `%t` Time when the request was started to process

- `%P` The process ID of the child that serviced the request

- `%r` First line of request

- `%s` Response status code

- `%b` Size of response in bytes, including HTTP headers

- `%T` Time taken to serve the request, in seconds with floating fraction in .06f format

- `%D` Time taken to serve the request, in milliseconds

- `%{FOO}i` request.headers['FOO']

- `%{FOO}o` response.headers['FOO']

- `%{FOO}e` os.environ['FOO']

### Adding Logging Files

Also, just for curiosity, I was looking for a way to generate logging files. But the solution is a little bit different, without using Actix middleware.

We have to add `simplelog` to the `Cargo.toml` file

**Cargo.toml**

```
...
simplelog = "0.6"

```

**main.rs**

```
use log::{info, error};
use simplelog::{CombinedLogger, Config, LevelFilter, WriteLogger};
use std::fs::File;

...

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let log_file = File::create("./app.log").unwrap();
    CombinedLogger::init(
        vec![
            WriteLogger::new(
                LevelFilter::Debug,
                Config::default(),
                log_file,
            ),

            WriteLogger::new(LevelFilter::Debug, Config::default(), std::io::stdout()),
        ]
    ).unwrap();

    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(hi)

    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

```

This solution uses the [simplelog](https://crates.io/crates/simplelog) crate, which is a crate that does not aim to provide a rich set of features, nor to provide the best logging solution. It aims to be a maintainable, easy-to-integrate facility for small to medium-sized projects, that find `env_logger` to be somewhat lacking in features. In those cases, `simplelog` should provide an easy alternative, according to its documentation.

I have to remove the code that uses Actix middleware to show the logs in the command line. Because it uses `env_logger`, and the main thread panicked, I got the following message:

> "thread 'main' panicked at 'env\_logger::init\_from\_env should not be called after logger initialized: SetLoggerError(())', C:\Users\gate1.cargo\registry\src\github.com-1ecc6299db9ec823\env\_logger-0.9.3\src\lib.rs:1222:10".

So, I decide only to use `simplelog`, for this example for generating logging files and showing logs in the command line.

### Tracing

To add tracing to the Actix web server, we will use `tracing-actix-web` crate.

**Tracing-actix-web**

According to its [documentation](https://docs.rs/tracing-actix-web/latest/tracing_actix_web/index.html), `tracing-actix-web` provides `TracingLogger`, a middleware to collect telemetry data from applications built on top of the Actix-web framework.

**main.rs**

```
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use actix_web::middleware::Logger;
use env_logger::Env;
use tracing_actix_web::TracingLogger;

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

#[get("/hi")]
async fn hi() -> impl Responder {
    HttpResponse::Ok().body("Greetings")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

   env_logger::init_from_env(Env::default().default_filter_or("trace")); 

    HttpServer::new(|| {
        App::new()
            .wrap(TracingLogger::default())
            .service(hello)
            .service(hi)

    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

```

For tracing we just add `TracingLogger::default()` and wrap it using the `wrap()` function to use it as a middleware.

`TracingLogger` is a middleware to capture structured diagnostics when processing an HTTP request.

`TracingLogger` is designed as a drop-in replacement of [`actix-web`](https://docs.rs/actix-web)s [`Logger`](https://docs.rs/actix-web/3.0.2/actix_web/middleware/struct.Logger.html).

As you can see, I remove Actix's Logger middleware.

We can use this crate to add instrumentation to our application and be collected with OpenTelemetry for further analysis.

To know more about this crate you can read "Are we observable yet?", it provides an in-depth introduction to the crate and the problems it solves within the bigger picture of observability.

### Conclusion

Before writing this article I just want to add simple loggings to my Actix application. But, in the journey, I was more curious about what more I can add. I was hoping to find a crate or an All-in-One solution, but I didn't find it, it doesn't mean that doesn't exist. Probably I would find one if I do deep research or have better skills in Rust.

However, I think this allows more flexibility and customization. If we want just to show logs in the command line, using `Logger` middleware is fine. If we want to collect the telemetry for further analysis, `TracingLogger`. Write the logs in a file, `simplelog`.

Sometimes having a unique library with a lot of features that we don't want or are not going to use, just adds unnecessary dependencies.

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [**Twitter**](https://twitter.com/Carlos_marcv), or [**LinkedIn**](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

The source code is [here](https://github.com/carlosm27/actix-logging-demo).

### Resources

[Actix Documentation.](https://actix.rs/docs)

[Actix Middleware documentation.](https://actix.rs/docs/middleware/)

[tracing-actix-web documentation.](https://docs.rs/tracing-actix-web/0.7.4/tracing_actix_web/)

[simplelog crate documentation.](https://crates.io/crates/simplelog)

[Are we observable yet? An introduction to Rust Telemetry.](https://www.lpalmieri.com/posts/2020-09-27-zero-to-production-4-are-we-observable-yet/#5-15-leveraging-the-tracing-ecosystem)