Skip to content

Add more detailed description #24

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

Merged
merged 4 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ prometheus-exporter = [
"opentelemetry_sdk",
"prometheus"
]
doc-images = []

[dependencies]
autometrics-macros = { version = "0.2.0", path = "./macros" }
Expand All @@ -41,6 +42,9 @@ opentelemetry-prometheus = { version = "0.11", optional = true }
opentelemetry_sdk = { version = "0.18", default-features = false, features = ["metrics"], optional = true }
prometheus = { version = "0.13", default-features = false, optional = true }

# Use for doc-images feature
embed-doc-image = { version = "0.1.4", optional = true }

[dev-dependencies]
axum = "0.6.4"
opentelemetry-prometheus = { version = "0.11" }
Expand Down
87 changes: 51 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# Autometrics 📈✨
> Easily add metrics to your system -- and actually understand them using automatically customized Prometheus queries.

[![Documentation](https://docs.rs/autometrics/badge.svg)](https://docs.rs/autometrics)
[![Crates.io](https://img.shields.io/crates/v/autometrics.svg)](https://crates.io/crates/autometrics)
[![Discord Shield](https://discordapp.com/api/guilds/950489382626951178/widget.png?style=shield)](https://discord.gg/kHtwcH8As9)

Metrics are a powerful and relatively inexpensive tool for understanding your system in production. However, they are still hard to use. Developers need to:
- Think about what metrics to track and which metric type to use (counter, histogram... 😕)
- 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 is a macro that makes it trivial to add useful metrics to any function in your codebase.**

**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.
Easily understand and debug your production system using automatically generated queries. Autometrics adds links to Prometheus charts directly into each function's doc comments.

(Coming Soon!) Autometrics will also generate dashboards ([#15](https://github.com/fiberplane/autometrics-rs/issues/15)) and alerts ([#16](https://github.com/fiberplane/autometrics-rs/issues/16)) from simple annotations in your code. Implementations in other programming languages are also in the works!

### 1️⃣ Add `#[autometrics]` to any function or `impl` block

Expand Down Expand Up @@ -50,35 +46,46 @@ cargo run --features="prometheus-exporter" --example axum
3. Hover over the [function names](./examples/axum.rs#L21) to see the generated query links
(like in the image above) and try clicking on them to go straight to that Prometheus chart.

## How it works
## Why Autometrics?

### Metrics today are hard to use

Metrics are a powerful and relatively inexpensive tool for understanding your system in production.

However, they are still hard to use. Developers need to:
- Think about what metrics to track and which metric type to use (counter, histogram... 😕)
- Figure out how to write PromQL or another query language to get some data 😖
- Verify that the data returned actually answers the right question 😫

### Simplifying code-level observability

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, optionally, a gauge for concurrent requests.
Many modern observability tools promise to make life "easy for developers" by automatically instrumenting your code with an agent or eBPF. Others ingest tons of logs or traces -- and charge high fees for the processing and storage.

Autometrics supports using different underlying libraries for producing the metrics. See [below](#metrics-collection-libraries) for how to configure the metrics library.
Most of these tools treat your system as a black box and use complex and pricey processing to build up a model of your system. This, however, means that you need to map their model onto your mental model of the system in order to navigate the mountains of data.

Finally, autometrics can generate the PromQL queries and Prometheus links for each function because it is creating the metrics using specific names and labeling conventions.
Autometrics takes the opposite approach. Instead of throwing away valuable context and then using compute power to recreate it, it starts inside your code. It enables you to understand your production system at one of the most fundamental levels: from the function.

## API
### Standardizing function-level metrics

### `#[autometrics]` Macro
Functions are one of the most fundamental building blocks of code. Why not use them as the building block for observability?

Add the `#[autometrics]` attribute to any function or `impl` block you want to collect metrics for.
A core part of Autometrics is the simple idea of using standard metric names and a consistent scheme for tagging/labeling metrics. The three metrics currently used are: `function.calls.count`, `function.calls.duration`, and `function.calls.concurrent`.

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.
### Labeling metrics with useful, low-cardinality function details

#### Optional Parameters
The following labels are added automatically to all three of the metrics: `function` and `module`.

- `track_concurrency` - if enabled (by passing `#[autometrics(track_concurrency)]`), autometrics will also track the number of concurrent calls to that function using a gauge. This may be most useful for top-level functions such as the main HTTP handler that passes off requests to other functions.
For the function call counter, the following labels are also added:

### Result Type Labels
- `caller` - (see ["Tracing Lite"](#tracing-lite) below)
- `result` - either `ok` or `error` if the function returns a `Result`
- `ok` / `error` - see the next section

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`).
#### Static return type labels

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>`.
If the concrete `Result` types implement `Into<&'static str>`, the that string will also be added as a label value under the key `ok` or `error`.

For example, if you have an `Error` enum to define specific error types, you can have the enum variant names included as labels:
For example, you can have the variant names of your error enum included as labels:
```rust
use strum::IntoStaticStr;

Expand All @@ -92,12 +99,26 @@ pub enum MyError {
```
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`.

#### Why no dynamic labels?
This is more useful than tracking external errors like HTTP status codes because multiple logical errors might map to the same status code.

Autometrics only supports `&'static str`s as labels to avoid the footgun of attaching labels with too many possible values. The [Prometheus docs](https://prometheus.io/docs/practices/naming/#labels) 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.

### "Tracing Lite"

A slightly unusual idea baked into autometrics is that by tracking more granular metrics, you can debug some issues that we would traditionally need to turn to tracing for.

Autometrics can be added to any function in your codebase, from HTTP handlers down to database methods.

This means that if you are looking into a problem with a specific HTTP handler, you can browse through the metrics of the functions _called by the misbehaving function_.

Simply hover over the function names of the nested function calls in your IDE to look at their metrics. Or, you can directly open the chart of the request or error rate of all functions called by a specific function.

### More to come!

Stay tuned for automatically generated dashboards, alerts, and more!

## Exporting Prometheus Metrics

Autometrics includes optional functions to help collect and prepare metrics to be collected by Prometheus.
Expand Down Expand Up @@ -143,16 +164,10 @@ fn main() {
```
Note that when using Rust Analyzer, you may need to reload the workspace in order for URL changes to take effect.

### Metrics Collection Libraries

By default, autometrics uses the [`opentelemetry`](https://crates.io/crates/opentelemetry) crate to collect metrics.
### Feature flags

If you are already using one of the following crates, you can configure autometrics to use that instead:
- [`prometheus`](https://crates.io/crates/prometheus):
```toml
autometrics = { version = "*", features = ["prometheus"], default-features = false }
```
- [`metrics`](https://crates.io/crates/metrics):
```toml
autometrics = { version = "*", features = ["metrics"], default-features = false }
```
- `metrics` - use the [metrics](https://crates.io/crates/metrics) crate for producing metrics
- `opentelemetry` (enabled by default) - use the [opentelemetry](https://crates.io/crates/opentelemetry) crate for producing metrics
- `prometheus` - use the [prometheus](https://crates.io/crates/prometheus) crate for producing metrics
- `prometheus-exporter` - exports a Prometheus metrics collector and exporter (compatible with any of the `metrics`/`opentelemetry`/`prometheus` features)
58 changes: 5 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg_hide))]
#![cfg_attr(docsrs, doc(cfg_hide(doc)))]

//! # Autometrics
//!
//! Autometrics is a library that makes it easy to collect metrics for any function
//! -- and easily understand the data with automatically generated Prometheus queries for each function.
//!
//! ## Example
//! See the [example](https://github.com/fiberplane/autometrics-rs/blob/main/examples/axum.rs) for a full example of how to use Autometrics.
//!
//! ## Usage
//! 1. Annotate any function with `#[autometrics]` to collect metrics for that function.
//! You can also annotate an entire `impl` block to collect metrics for all of the functions in that block.
//!
//! ```rust
//! #[autometrics]
//! async fn create_user(Json(payload): Json<CreateUser>) -> Result<Json<User>, ApiError> {
//! // ...
//! }
//!
//! #[autometrics]
//! impl Database {
//! async fn save_user(&self, user: User) -> Result<User, DbError> {
//! // ...
//! }
//! }
//!
//! ```
//!
//! 2. Call the `global_metrics_exporter` function in your `main` function:
//! ```rust
//! pub fn main() {
//! let _exporter = autometrics::global_metrics_exporter();
//! // ...
//! }
//! ```
//!
//! 3. Create a route on your API (probably mounted under `/metrics`) for Prometheus to scrape:
//! ```rust
//! pub fn get_metrics() -> (StatusCode, String) {
//! match autometrics::encode_global_metrics() {
//! Ok(metrics) => (StatusCode::OK, metrics),
//! Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", err))
//! }
//! }
//! ```
//!
//! 4. Hover over any autometrics-enabled function to see the links to graphs of the generated metrics
//! 5. Click on the link to see the graph of the live metrics for that function
//!
//! ## Feature flags
//!
//! - `prometheus-exporter`: Exports a Prometheus metrics collector and exporter
//!
#![doc = include_str!("../README.md")]
#![cfg_attr(feature = "doc-images",
cfg_attr(all(),
doc = ::embed_doc_image::embed_image!("vs-code-example", "assets/vs-code-example.png"),
doc = ::embed_doc_image::embed_image!("prometheus-chart", "assets/prometheus-chart.png")))]

mod constants;
mod labels;
Expand Down
1 change: 1 addition & 0 deletions src/prometheus_exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const HISTOGRAM_BUCKETS: [f64; 10] = [
static GLOBAL_EXPORTER: Lazy<GlobalPrometheus> = Lazy::new(|| initialize_metrics_exporter());

#[derive(Clone)]
#[doc(hidden)]
pub struct GlobalPrometheus {
exporter: PrometheusExporter,
#[cfg(feature = "metrics")]
Expand Down