Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Carlos Armando Marcano Vargas
Carlos Armando Marcano Vargas

Posted on • Originally published atcarlosmv.hashnode.dev on

Adding logging and tracing to an Axum App | Rust.

DISCLAIMER: This is not a comprehensive article about logging or tracing.

The service we will build is really simple, with an endpoint and with examples showing logs in our command line, JSON logs, tracing, and how to generate log files.

For this task, we will useTracing,tracing-subscriber, andtower-http packages.

cargo.toml

[dependencies]axum = "0.6.1"tokio = { version = "1.21.2", features = ["full"] }tracing = "0.1.37"tracing-subscriber = { version = "0.3.16", features = ["env-filter"]}tower-http = { version = "0.3.4", features = ["trace"] }
Enter fullscreen modeExit fullscreen mode

Logs

Here is a simple example of how to add logging.

main.rs

use axum:: {    routing::{get},    http::StatusCode,    Router};use std::net::SocketAddr;use tower_http::trace::{self, TraceLayer};use tracing::Level;#[tokio::main]async fn main() {    tracing_subscriber::fmt()        .with_target(false)        .compact()        .init(); let app = Router::new()        .route("/", get(hello_world))        .layer(            TraceLayer::new_for_http()                .make_span_with(trace::DefaultMakeSpan::new()                    .level(Level::INFO))                .on_response(trace::DefaultOnResponse::new()                    .level(Level::INFO)),);    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));    tracing::info!("listening on {}", addr);    axum::Server::bind(&addr)        .serve(app.into_make_service())        .await        .unwrap();}async fn hello_world() -> &'static str {    "Hello World!"}
Enter fullscreen modeExit fullscreen mode

According to thedocumentation, when we usecompact() the output is optimized for short line lengths. Fields from the current span context are appended to the fields of the formatted event.

If we executecargo run , and go tolocahost:3000, we should see the following message in our terminal.

Now, let me explain a little about the role of Tower-http, Tracing, and Tracing-subscriber.

Tracing

Tracing is a framework for instrumenting Rust programs to collect structured, event-based diagnostic information.

We can usetracing to:

  • Emit distributed traces to an Open Telemetry collector.

  • Debug your application with Tokio Console.

  • Log tostdout, a log file orjournald .

  • Profile where your application is spending time.

According to the articleGetting started with Tracing, thetracing crate provides the API we will use to emit traces. Thetracing-subscriber crate provides some basic utilities for forwarding those traces to external listeners(e.g.,stdout).

Also, the same article mentions thatsubscribers are types that process traces emitted by an application and its dependencies, and can perform metrics, monitoring for errors, and re-emitting traces to the outside world.

In our case, thetracing crate collects the events of our application, but we want to emit the traces and logs to our terminal, that is a task for thetracing-subscriber crate.

Tower-http

According to its documentation,tower-http is a library that provides HTTP-specific middleware and utilities built on top oftower.

The documentation also mentions that all middleware uses thehttp andhttp-body crates as the HTTP abstractions. That means they are compatible with any library or framework that also uses those crates, such as hyper, tonic, and warp.

Axum is built with hyper, so we can usetower-http .

This library aims to provide a rich set of middleware for solving common problems when building HTTP clients and servers, like adding high-level tracing and logging to our applications. Also, automatically compress or decompress response bodies and automatically follow redirection responses.

For information abouttower-http, in itsdocumentation and thisarticle.

Now, coming back to our code.

let app = Router::new()        .route("/", get(hello_world))        .layer(            TraceLayer::new_for_http()                .make_span_with(trace::DefaultMakeSpan::new()                    .level(Level::INFO))                .on_response(trace::DefaultOnResponse::new()                    .level(Level::INFO)),);
Enter fullscreen modeExit fullscreen mode

Here, we create an instance of a Router. And use thelayer method, which applies atower::Layer to all routes in the router. It is used to add middleware to the routes.

We passedTraceLayer as an argument. Quoting the official documentation:

Layer that adds high level tracing to aService.

The "service" the quote is referring to is a tower service. Also, we usenew_for_http() , it creates a newTraceLayer usingServerErrorsAsFailures which supports classifying regular HTTP responses based on the status code, according to thedocumentation.

To make this work, we have to convert our router instance to a tower service. And we did it when we usedinto_make_service().

 axum::Server::bind(&addr)        .serve(app.into_make_service())        .await        .unwrap();
Enter fullscreen modeExit fullscreen mode

Now we explain whatmake_span_with() andon_response() do.

Thedocumentation says thaton_response() customizes what to do when a response has been produced. Andmake_span_with() customizes how to makeSpans that all request handling will be wrapped in.

To these functions, we passed them the log level we want. Using thelevel method in both,DefaulMakeSpan andDefaultOnResponse, we set the level toLEVEL::INFO. The default isLEVEL::DEBUG.

For more details aboutDefaultMakeSpan andDefaultOnResponse,here andhere respectively.

Pretty

#[tokio::main]async fn main() {    tracing_subscriber::fmt()       .with_target(false)       .pretty()       .init();    let app = Router::new()        .route("/", get(hello_world))        .layer(            TraceLayer::new_for_http()                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),                );    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));    tracing::info!("listening on {}", addr);    axum::Server::bind(&addr)        .serve(app.into_make_service())        .await        .unwrap();}
Enter fullscreen modeExit fullscreen mode

Pretty emits excessively pretty, multi-line logs, optimized for human readability, according to its documentation. Is intended to be used in local development and debugging, or for command-line applications, where readability and visual appeal are a priority.

Json

To add JSON output, we have to modify ourCargo.toml file and add the featurejson totracing_subscriber.

cargo.toml

...tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"]}...#[tokio::main]async fn main() {    tracing_subscriber::fmt()       .with_target(false)       .json()       .init();    let app = Router::new()        .route("/", get(hello_world))        .layer(            TraceLayer::new_for_http()                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),                );    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));    tracing::info!("listening on {}", addr);    axum::Server::bind(&addr)        .serve(app.into_make_service())        .await        .unwrap();}
Enter fullscreen modeExit fullscreen mode

Json outputs newline-delimited JSON logs. Is intended for production use with systems where structured logs are consumed as JSON by analysis and viewing tools.

Registry

...use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};#[tokio::main]async fn main() {    tracing_subscriber::registry()        .with(tracing_subscriber::fmt::layer())        .init();    let app = Router::new()        .route("/", get(hello_world))        .layer(            TraceLayer::new_for_http()                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),                );    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));    tracing::info!("listening on {}", addr);    axum::Server::bind(&addr)        .serve(app.into_make_service())        .await        .unwrap();}
Enter fullscreen modeExit fullscreen mode

Quoting thedocumentation:

This module provides theRegistry type, aSubscriber implementation which tracks per-span data and exposes it toLayers. When aRegistry is used as the baseSubscriber of aLayer stack, thelayer::Context type will provide methods allowingLayers to look up span data stored in the registry. WhileRegistry is a reasonable default for storing spans and events, other stores that implementLookupSpan andSubscriber themselves (withSpanData implemented by the per-span data they store) can be used as a drop-in replacement.

Creating log files

To create log files we have to addtracing_appender to ourCargo.toml file.

Cargo.toml

...tracing-appender = "0.2"use tracing::Level;use tracing_appender::rolling;use tracing_subscriber::fmt::writer::MakeWriterExt;#[tokio::main]async fn main() {    let info_file = rolling::daily("./logs", "info").with_max_level(tracing::Level::INFO);    tracing_subscriber::fmt()      .with_writer(info_file)      .with_ansi(false)      .init();  ...}
Enter fullscreen modeExit fullscreen mode

I just modified thisexample from thetracing repository.

Here we create a variable to define the rolling period and passed it the directory where we want our files to be stored and the name of the files. And we set the log level, in this caseINFO.

Then we pass the variableinfo_file towith_writer() function.

with_writer() sets theMakeWriter, and it is used byfmt::Layer orfmt::Subscriber to print formatted text representations of Events.

Now, if we set the level totrace, like this.

use tracing::Level;use tracing_appender::rolling;use tracing_subscriber::fmt::writer::MakeWriterExt;#[tokio::main]async fn main() {    let info_file = rolling::daily("./logs", "info");    tracing_subscriber::fmt()      .with_writer(info_file)      .with_max_level(tracing::Level::TRACE)      .with_ansi(false)      .init();  ...}
Enter fullscreen modeExit fullscreen mode

The file will look like this.

Conclusion

Tracing offers a lot of customization. And it was a challenge for me to find what examples will be the simplest for this article. Fortunately, tracing_subscriber has good documentation and the repository has helpful examples. Also, I just useinfo andtrace as log levels, but they are not the only ones, we can usedebug, warn, error, andoff.

It was fun to write this article. I did it to learn more about tracing framework and tower-http crate and how they work together.

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 throughTwitter, orLinkedIn.

The source code ishere

Reference

Getting started with Trancing.

Tracing documentation.

Tracing-subscriber documentation.

Tower-http documentation.

Announcing tower-http.

tracing-appender documentation.

appender-multifiles example.

Issue #296: Where's the most basic example for HTTP request logging?

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a backend developer, I enjoy writing tutorials on how to use open-source projects in Python, Go, and Rust, that I use or find interesting.
  • Location
    Maturin/Venezuela
  • Joined

More fromCarlos Armando Marcano Vargas

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp