diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f8d578d8..07a7ef29 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,18 +18,21 @@ jobs:
- uses: Swatinem/rust-cache@v2
# Lint
- - name: Run Clippy
- # GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed.
- run: cargo clippy --all-targets --features=prometheus-exporter,opentelemetry,metrics,prometheus
+ # Note: GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed.
+ - run: cargo clippy --features=metrics,prometheus-exporter
+ - run: cargo clippy --features=prometheus
+ - run: cargo clippy --features=prometheus-client
+ - run: cargo clippy --features=opentelemetry
# Build the packages
- run: cargo build
- - run: cargo build --features=metrics
- - run: cargo build --features=prometheus
- run: cargo build --features=custom-objective-percentile,custom-objective-latency
- # Run the tests
+ # Run the tests with each of the different metrics libraries
- run: cargo test --features=prometheus-exporter
+ - run: cargo test --no-default-features --features=prometheus-exporter,metrics --tests
+ - run: cargo test --no-default-features --features=prometheus-exporter,prometheus --tests
+ - run: cargo test --no-default-features --features=prometheus-exporter,prometheus-client --tests
# Compile the examples
- run: cargo build --package example-axum
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d96d63dc..2322818a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ResultLabels` derive macro allows to specify on an enum whether variants should
always be "ok", or "error" for the success rate metrics of functions using them. (#61)
+- Support the official `prometheus-client` crate for producing metrics
### Changed
diff --git a/README.md b/README.md
index 2136c485..0996e933 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ https://github.com/autometrics-dev/autometrics-rs/assets/3262610/966ed140-1d6c-4
- [🔍 Identify commits](https://docs.rs/autometrics/latest/autometrics/#identifying-commits-that-introduced-problems) that introduced errors or increased latency
- [🚨 Define alerts](https://docs.rs/autometrics/latest/autometrics/objectives/index.html) using SLO best practices directly in your source code
- [📊 Grafana dashboards](https://github.com/autometrics-dev#5-configuring-prometheus) work out of the box to visualize the performance of instrumented functions & SLOs
-- [⚙️ Configurable](https://docs.rs/autometrics/latest/autometrics/#metrics-libraries) metric collection library ([`opentelemetry`](https://crates.io/crates/opentelemetry), [`prometheus`](https://crates.io/crates/prometheus), or [`metrics`](https://crates.io/crates/metrics))
+- [⚙️ Configurable](https://docs.rs/autometrics/latest/autometrics/#metrics-libraries) metric collection library ([`opentelemetry`](https://crates.io/crates/opentelemetry), [`prometheus`](https://crates.io/crates/prometheus), [`prometheus-client`](https://crates.io/crates/prometheus-client) or [`metrics`](https://crates.io/crates/metrics))
- ⚡ Minimal runtime overhead
See [Why Autometrics?](https://github.com/autometrics-dev#4-why-autometrics) for more details on the ideas behind autometrics.
@@ -156,7 +156,7 @@ See [Why Autometrics?](https://github.com/autometrics-dev#4-why-autometrics) for
- [Configure `autometrics`](https://docs.rs/autometrics/latest/autometrics/#metrics-libraries) to use the same underlying metrics library you use with the appropriate feature flag: `opentelemetry`, `prometheus`, or `metrics`.
+ [Configure `autometrics`](https://docs.rs/autometrics/latest/autometrics/#metrics-libraries) to use the same underlying metrics library you use with the appropriate feature flag: `opentelemetry`, `prometheus`, `prometheus-client`, or `metrics`.
```toml
[dependencies]
diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml
index 851a0c14..19090642 100644
--- a/autometrics/Cargo.toml
+++ b/autometrics/Cargo.toml
@@ -16,10 +16,10 @@ readme = "README.md"
default = ["opentelemetry"]
metrics = ["dep:metrics"]
opentelemetry = ["opentelemetry_api"]
-prometheus = ["const_format", "dep:prometheus", "once_cell"]
+prometheus = ["dep:prometheus"]
+prometheus-client = ["dep:prometheus-client"]
prometheus-exporter = [
"metrics-exporter-prometheus",
- "once_cell",
"opentelemetry-prometheus",
"opentelemetry_sdk",
"prometheus"
@@ -29,7 +29,8 @@ custom-objective-latency = []
[dependencies]
autometrics-macros = { workspace = true }
-spez = { version = "0.1.2" }
+spez = "0.1.2"
+once_cell = "1.17"
# Used for opentelemetry feature
opentelemetry_api = { version = "0.19.0", default-features = false, features = ["metrics"], optional = true }
@@ -39,17 +40,15 @@ metrics = { version = "0.21", default-features = false, optional = true }
# Used for prometheus-exporter feature
metrics-exporter-prometheus = { version = "0.12", default-features = false, optional = true }
-once_cell = { version = "1.17", optional = true }
opentelemetry-prometheus = { version = "0.12.0", optional = true }
opentelemetry_sdk = { version = "0.19", default-features = false, features = ["metrics"], optional = true }
prometheus = { version = "0.13", default-features = false, optional = true }
-# Used for prometheus feature
-const_format = { version = "0.2", features = ["rust_1_51"], optional = true }
+# Used for prometheus-client feature
+prometheus-client = { version = "0.21.1", optional = true }
[dev-dependencies]
axum = { version = "0.6", features = ["tokio"] }
-regex = "1.7"
http = "0.2"
tokio = { version = "1", features = ["full"] }
trybuild = "1.0"
diff --git a/autometrics/README.md b/autometrics/README.md
index c539606b..e56b11d7 100644
--- a/autometrics/README.md
+++ b/autometrics/README.md
@@ -53,7 +53,7 @@ pub async fn main() {
- [🔍 Identify commits](#identifying-commits-that-introduced-problems) that introduced errors or increased latency
- [🚨 Define alerts](https://docs.rs/autometrics/latest/autometrics/objectives/index.html) using SLO best practices directly in your source code
- [📊 Grafana dashboards](https://github.com/autometrics-dev#5-configuring-prometheus) work out of the box to visualize the performance of instrumented functions & SLOs
-- [⚙️ Configurable](#metrics-libraries) metric collection library ([`opentelemetry`](https://crates.io/crates/opentelemetry), [`prometheus`](https://crates.io/crates/prometheus), or [`metrics`](https://crates.io/crates/metrics))
+- [⚙️ Configurable](#metrics-libraries) metric collection library ([`opentelemetry`](https://crates.io/crates/opentelemetry), [`prometheus`](https://crates.io/crates/prometheus), [`prometheus-client`](https://crates.io/crates/prometheus-client) or [`metrics`](https://crates.io/crates/metrics))
- ⚡ Minimal runtime overhead
See [Why Autometrics?](https://github.com/autometrics-dev#4-why-autometrics) for more details on the ideas behind autometrics.
diff --git a/autometrics/src/constants.rs b/autometrics/src/constants.rs
index 176fc23e..3311424d 100644
--- a/autometrics/src/constants.rs
+++ b/autometrics/src/constants.rs
@@ -4,6 +4,11 @@ pub const HISTOGRAM_NAME: &str = "function.calls.duration";
pub const GAUGE_NAME: &str = "function.calls.concurrent";
pub const BUILD_INFO_NAME: &str = "build_info";
+// Prometheus-flavored metric names
+pub const COUNTER_NAME_PROMETHEUS: &str = "function_calls_count";
+pub const HISTOGRAM_NAME_PROMETHEUS: &str = "function_calls_duration";
+pub const GAUGE_NAME_PROMETHEUS: &str = "function_calls_concurrent";
+
// Descriptions
pub const COUNTER_DESCRIPTION: &str = "Autometrics counter for tracking function calls";
pub const HISTOGRAM_DESCRIPTION: &str = "Autometrics histogram for tracking function call duration";
diff --git a/autometrics/src/labels.rs b/autometrics/src/labels.rs
index b27a2a3d..8462de0c 100644
--- a/autometrics/src/labels.rs
+++ b/autometrics/src/labels.rs
@@ -1,14 +1,20 @@
use crate::{constants::*, objectives::*};
+#[cfg(feature = "prometheus-client")]
+use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue, LabelValueEncoder};
use std::ops::Deref;
pub(crate) type Label = (&'static str, &'static str);
pub type ResultAndReturnTypeLabels = (&'static str, Option<&'static str>);
/// These are the labels used for the `build_info` metric.
+#[cfg_attr(
+ feature = "prometheus-client",
+ derive(EncodeLabelSet, Debug, Clone, PartialEq, Eq, Hash)
+)]
pub struct BuildInfoLabels {
- pub(crate) version: &'static str,
- pub(crate) commit: &'static str,
pub(crate) branch: &'static str,
+ pub(crate) commit: &'static str,
+ pub(crate) version: &'static str,
}
impl BuildInfoLabels {
@@ -30,12 +36,47 @@ impl BuildInfoLabels {
}
/// These are the labels used for the `function.calls.count` metric.
+#[cfg_attr(
+ feature = "prometheus-client",
+ derive(EncodeLabelSet, Debug, Clone, PartialEq, Eq, Hash)
+)]
pub struct CounterLabels {
pub(crate) function: &'static str,
pub(crate) module: &'static str,
pub(crate) caller: &'static str,
- pub(crate) result: Option,
- pub(crate) objective: Option<(&'static str, ObjectivePercentile)>,
+ pub(crate) result: Option,
+ pub(crate) ok: Option<&'static str>,
+ pub(crate) error: Option<&'static str>,
+ pub(crate) objective_name: Option<&'static str>,
+ pub(crate) objective_percentile: Option,
+}
+
+#[cfg_attr(
+ feature = "prometheus-client",
+ derive(Debug, Clone, PartialEq, Eq, Hash)
+)]
+pub(crate) enum ResultLabel {
+ Ok,
+ Error,
+}
+
+impl ResultLabel {
+ pub(crate) const fn as_str(&self) -> &'static str {
+ match self {
+ ResultLabel::Ok => OK_KEY,
+ ResultLabel::Error => ERROR_KEY,
+ }
+ }
+}
+
+#[cfg(feature = "prometheus-client")]
+impl EncodeLabelValue for ResultLabel {
+ fn encode(&self, encoder: &mut LabelValueEncoder) -> Result<(), std::fmt::Error> {
+ match self {
+ ResultLabel::Ok => EncodeLabelValue::encode(&OK_KEY, encoder),
+ ResultLabel::Error => EncodeLabelValue::encode(&ERROR_KEY, encoder),
+ }
+ }
}
impl CounterLabels {
@@ -46,21 +87,33 @@ impl CounterLabels {
result: Option,
objective: Option,
) -> Self {
- let objective = if let Some(objective) = objective {
+ let (objective_name, objective_percentile) = if let Some(objective) = objective {
if let Some(success_rate) = objective.success_rate {
- Some((objective.name, success_rate))
+ (Some(objective.name), Some(success_rate))
} else {
- None
+ (None, None)
+ }
+ } else {
+ (None, None)
+ };
+ let (result, ok, error) = if let Some((result, return_value_type)) = result {
+ match result {
+ OK_KEY => (Some(ResultLabel::Ok), return_value_type, None),
+ ERROR_KEY => (Some(ResultLabel::Error), None, return_value_type),
+ _ => (None, None, None),
}
} else {
- None
+ (None, None, None)
};
Self {
function,
module,
caller,
+ objective_name,
+ objective_percentile,
result,
- objective,
+ ok,
+ error,
}
}
@@ -70,15 +123,20 @@ impl CounterLabels {
(MODULE_KEY, self.module),
(CALLER_KEY, self.caller),
];
- if let Some((result, return_value_type)) = self.result {
- labels.push((RESULT_KEY, result));
- if let Some(return_value_type) = return_value_type {
- labels.push((result, return_value_type));
- }
+ if let Some(result) = &self.result {
+ labels.push((RESULT_KEY, result.as_str()));
+ }
+ if let Some(ok) = self.ok {
+ labels.push((OK_KEY, ok));
+ }
+ if let Some(error) = self.error {
+ labels.push((ERROR_KEY, error));
+ }
+ if let Some(objective_name) = self.objective_name {
+ labels.push((OBJECTIVE_NAME, objective_name));
}
- if let Some((name, percentile)) = &self.objective {
- labels.push((OBJECTIVE_NAME, name));
- labels.push((OBJECTIVE_PERCENTILE, percentile.as_str()));
+ if let Some(objective_percentile) = &self.objective_percentile {
+ labels.push((OBJECTIVE_PERCENTILE, objective_percentile.as_str()));
}
labels
@@ -86,39 +144,54 @@ impl CounterLabels {
}
/// These are the labels used for the `function.calls.duration` metric.
+#[cfg_attr(
+ feature = "prometheus-client",
+ derive(EncodeLabelSet, Debug, Clone, PartialEq, Eq, Hash)
+)]
pub struct HistogramLabels {
pub function: &'static str,
pub module: &'static str,
- /// The SLO name, objective percentile, and latency threshold
- pub objective: Option<(&'static str, ObjectivePercentile, ObjectiveLatency)>,
+ pub objective_name: Option<&'static str>,
+ pub objective_percentile: Option,
+ pub objective_latency_threshold: Option,
}
impl HistogramLabels {
pub fn new(function: &'static str, module: &'static str, objective: Option) -> Self {
- let objective = if let Some(objective) = objective {
- if let Some((latency, percentile)) = objective.latency {
- Some((objective.name, percentile, latency))
+ let (objective_name, objective_percentile, objective_latency_threshold) =
+ if let Some(objective) = objective {
+ if let Some((latency, percentile)) = objective.latency {
+ (Some(objective.name), Some(percentile), Some(latency))
+ } else {
+ (None, None, None)
+ }
} else {
- None
- }
- } else {
- None
- };
+ (None, None, None)
+ };
Self {
function,
module,
- objective,
+ objective_name,
+ objective_percentile,
+ objective_latency_threshold,
}
}
pub fn to_vec(&self) -> Vec