Easily add metrics to your system -- and actually understand them using automatically customized Prometheus queries.
Metrics are powerful and relatively inexpensive, but they are still hard to use. Developers need to:
- Think about what metrics to track and which metric type to use (counter, gauge... 😕)
- Figure out how to write PromQL or another query language to get some data 😖
- Verify that the data returned actually answers the right question 😫
Autometrics makes it easy to add metrics to any function in your codebase. Then, it automatically generates common Prometheus for each function to help you easily understand the data. Explore your production metrics directly from your editor/IDE.
#[autometrics]
async fn create_user(Json(payload): Json<CreateUser>) -> Result<Json<User>, ApiError> {
// ...
}
- Install prometheus locally
- Run the axum example:
cargo run --features="prometheus-exporter" --example axum
- Hover over the function names to see the generated query links (like in the image above) and try clicking on them to go straight to that Prometheus chart.
The autometrics
macro rewrites your functions to include a variety of useful metrics.
It adds a counter for tracking function calls and errors (for functions that return Result
s),
a histogram for latency, and a gauge for concurrent requests.
We currently use the opentelemetry
crate for producing metrics
in the OpenTelemetry format. This can be converted to the Prometheus export format, as well
as others, using different exporters.
Autometrics can generate the PromQL queries and Prometheus links for each function because it is creating the metrics using specific names and labeling conventions.
For most use cases, you can simply add the #[autometrics]
attribute to any function you want to collect metrics for. We recommend using it for any important function in your code base (HTTP handlers, database calls, etc), possibly excluding simple utilities that are infallible or have negligible execution time.
By default, the metrics generated will have labels for the function
, module
, and result
(where the value is ok
or error
if the function returns a Result
).
The concrete result type(s) (the T
and E
in Result<T, E>
) can also be included as labels if the types implement Into<&'static str>
.
For example, if you have an Error
enum to define specific error types, you can have the enum variant names included as labels:
use strum::IntoStaticStr;
#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum MyError {
SomethingBad(String),
Unknown,
ComplexType { message: String },
}
In the above example, functions that return Result<_, MyError>
would have an additional label error
added with the values something_bad
, unknown
, or complex_type
.
Autometrics only supports &'static str
s as labels to avoid the footgun of attaching labels with too many possible values. The Prometheus docs explain why this is important in the following warning:
CAUTION: Remember that every unique combination of key-value label pairs represents a new time series, which can dramatically increase the amount of data stored. Do not use labels to store dimensions with high cardinality (many different label values), such as user IDs, email addresses, or other unbounded sets of values.
Autometrics includes optional functions to help collect and prepare metrics to be collected by Prometheus.
In your Cargo.toml
file, enable the optional prometheus-exporter
feature:
autometrics = { version = "*", features = ["prometheus-exporter"] }
Then, call the global_metrics_exporter
function in your main
function:
pub fn main() {
let _exporter = autometrics::global_metrics_exporter();
// ...
}
And create a route on your API (probably mounted under /metrics
) that returns the following:
pub fn get_metrics() -> (StatusCode, String) {
match autometrics::encode_global_metrics() {
Ok(metrics) => (StatusCode::OK, metrics),
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", err))
}
}
By default, Autometrics creates Prometheus query links that point to http://localhost:9090
.
You can configure a custom Prometheus URL using a build-time environment in your build.rs
file:
// build.rs
fn main() {
let prometheus_url = "https://your-prometheus-url.example";
println!("cargo:rustc-env=PROMETHEUS_URL={prometheus_url}");
}
Note that when using Rust Analyzer, you may need to reload the workspace in order for URL changes to take effect.