Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

axum additional metrics #174

Open
horseinthesky opened this issue Mar 1, 2024 · 3 comments
Open

axum additional metrics #174

horseinthesky opened this issue Mar 1, 2024 · 3 comments

Comments

@horseinthesky
Copy link

horseinthesky commented Mar 1, 2024

Hello.

I've checked examples but could find a way to instrument the axum app to include additional metrics.

Here is the code:
main.rs
Instrument handler with #[autometrics] macro work great - I get the defaults

use autometrics::{autometrics, prometheus_exporter};
use axum::{routing::get, Json, Router};
use serde::Serialize;
use serde_json::{json, Value};

mod images;
use images::get_images;

const DEFAULT_PORT: &str = "8000";
const DEFAULT_HOST: &str = "0.0.0.0";

#[derive(Serialize)]
#[serde(rename_all(serialize = "lowercase"))]
enum Status {
    OK,
    ERROR,
}

#[derive(Serialize)]
struct AppResponse<'a> {
    status: Status,
    #[serde(skip_serializing_if = "Option::is_none")]
    message: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    result: Option<Value>,
}

impl<'a> AppResponse<'a> {
    fn ok() -> Self {
        Self {
            status: Status::OK,
            message: None,
            result: None,
        }
    }

    fn error() -> Self {
        Self {
            status: Status::ERROR,
            message: None,
            result: None,
        }
    }

    fn with_message(self, message: &'a str) -> Self {
        Self {
            status: self.status,
            message: Some(message),
            result: self.result,
        }
    }

    fn with_result(self, result: Option<Value>) -> Self {
        Self {
            status: self.status,
            message: self.message,
            result,
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), axum::BoxError> {
    let app = Router::new()
        .route("/api/images", get(images))
        .route(
            "/metrics",
            get(|| async { prometheus_exporter::encode_http_response() }),
        );

    let port = std::env::var("PORT").unwrap_or(DEFAULT_PORT.to_string());
    let host = std::env::var("PORT").unwrap_or(DEFAULT_HOST.to_string());

    let listener =
        tokio::net::TcpListener::bind(format!("{}:{}", host, port)).await?;

    axum::serve(listener, app).await?;

    Ok(())
}

async fn health() -> Json<AppResponse<'static>> {
    Json(AppResponse::ok().with_message("up"))
}

#[autometrics]
async fn images() -> Json<AppResponse<'static>> {
    get_images().await;

    Json(AppResponse::ok().with_message("saved"))
}

images.rs
I'm trying to add additional metrics to measure some parts of the handler

use std::time::{Instant, SystemTime};
use tokio::time::{sleep, Duration};
use uuid::Uuid;

#[allow(dead_code)]
#[derive(Debug)]
struct Image {
    uuid: Uuid,
    modified: SystemTime,
}

impl Image {
    fn new() -> Self {
        Image {
            uuid: Uuid::new_v4(),
            modified: SystemTime::now(),
        }
    }
}

async fn download() {
    sleep(Duration::from_millis(5)).await;
}

async fn save(_image: Image) {
    sleep(Duration::from_millis(2)).await;
}

pub async fn get_images() {
    let start = Instant::now();
    let latency = start.elapsed().as_secs_f64();
    let labels = [("operation", "s3".to_string())];

    download().await;

    metrics::counter!("http_requests_total", &labels).increment(1);
    metrics::histogram!(
        "myapp_request_duration_seconds",
        &labels
    )
    .record(latency);

    let image = Image::new();
    save(image).await;
}

But this does nothing.

Is there a way to include new metrics to the storage? Thank you.

@emschwartz
Copy link
Contributor

Hi @horseinthesky, if you enable the metrics feature for autometrics, it should pick up the custom metrics you're creating with the metrics crate. You can take a look at https://github.com/autometrics-dev/autometrics-rs/tree/main/examples/custom-metrics for an example of this.

@emschwartz
Copy link
Contributor

Sorry, the feature is called metrics-0_21 (and you need to use that version of the metrics crate): https://docs.rs/autometrics/latest/autometrics/#metrics-backends

@horseinthesky
Copy link
Author

@emschwartz This is my `Cargo.toml at the moment:

[package]
name = "rust-app"
version = "0.1.0"
edition = "2021"

[dependencies]
autometrics = { version = "1.0.1", features = ["prometheus-exporter"] }
axum = "0.7.4"
metrics = "0.22.1"
metrics-exporter-prometheus = "0.13.1"
opentelemetry = "0.21.0"
opentelemetry-otlp = { version = "0.14.0", features = ["grpc-sys"] }
opentelemetry-semantic-conventions = "0.13.0"
opentelemetry_sdk = { version = "0.21.2", features = ["rt-tokio"] }
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["full"] }
tower-http = { version = "0.5.1", features = ["trace"] }
tracing = "0.1.40"
tracing-opentelemetry = "0.22.0"
tracing-subscriber = "0.3.18"
uuid = { version = "1.7.0", features = ["v4", "serde"] }

If I run:

cargo run . --features=metrics-0_22

it changes nothing.

Docs say:

If you are exporting metrics yourself rather than using the prometheus-exporter, you must ensure that you are using the exact same version of the metrics library as autometrics (and it must come from crates.io rather than git or another source). If not, the autometrics metrics will not appear in your exported metrics.

but I do use prometheus-exporter. There is a

        .route(
            "/metrics",
            get(|| async { prometheus_exporter::encode_http_response() }),
        );

in the code to handle prometheus requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants