/prometheus-push

Extension for prometheus crates to push to a push-gateway

Primary LanguageRustMIT LicenseMIT

Prometheus Push ⏫

prometheus_push works as an extension to prometheus crates like prometheus or prometheus-client to be able to push non-blocking (default) or blocking to your Prometheus pushgateway with a less dependent setup of reqwest (no openssl for example) or with an implementation of your own http client or even another prometheus crate – this whole crate is completely generic so you are free to do whatever you want.

If you wanna use it with reqwest, prometheus or prometheus-client crates you literally do not have to implement anything (see scenarios below), as those common usages are already implemented as features within this crate.

In this crates stripped version you have to implement the Push trait (see here) to use it with your choice of http client or –as said– you can use the with_reqwest or with_reqwest_blocking features. These features already implement Push in a PushClient that leverages reqwest under the hood. Reqwest is set up without default features (minimal set) in this case so it should not interfere with your own applications reqwest setup (e.g. rust-tls).

Async functionality (feature non_blocking) is considered the standard in this crate but you can enable the blocking feature to get the implementation without async. You can enable the corresponding blocking reqwest implementation with the with_reqwest_blocking feature in which case you enable the blocking feature of the reqwest crate.

In terms of the underlying prometheus functionality you have to implement the ConvertMetrics trait yourself (see here) or you use the already implemented feature prometheus_crate that leverages the prometheus crate or prometheus_client_crate that uses the prometheus-client crate.

Scenarios

1. I use reqwest and prometheus crates in a non-blocking fashion

In your Cargo.toml:

[dependencies]
prometheus_push = { version = "<version>", default-features = false, features = ["with_reqwest", "prometheus_crate"] }
use prometheus::labels;
use prometheus_push::prometheus_crate::PrometheusMetricsPusher;
use reqwest::Client;
use url::Url;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let push_gateway: Url = Url::parse("<address to pushgateway>")?;
    let client = Client::new();
    let metrics_pusher = PrometheusMetricsPusher::from(client, &push_gateway)?;
    metrics_pusher
        .push_all(
            "<your push jobs name>",
            &labels! { "<label_name>" => "<label_value>" },
            prometheus::gather(),
        )
        .await?;

    Ok(())
}

2. I use reqwest and prometheus crates in a blocking fashion

In your Cargo.toml:

[dependencies]
prometheus_push = { version = "<version>", default-features = false, features = ["with_reqwest_blocking", "prometheus_crate"] }
use prometheus::labels;
use prometheus_push::prometheus_crate::PrometheusMetricsPusherBlocking;
use reqwest::blocking::Client;
use url::Url;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let push_gateway: Url = Url::parse("<address to pushgateway>")?;
    let client = Client::new();
    let metrics_pusher = PrometheusMetricsPusherBlocking::from(client, &push_gateway)?;
    metrics_pusher
        .push_all(
            "<your push jobs name>",
            &labels! { "<label_name>" => "<label_value>" },
            prometheus::gather(),
        )?;

    Ok(())
}

3. I use reqwest and prometheus-client crates in a non-blocking fashion

In your Cargo.toml:

[dependencies]
prometheus_push = { version = "<version>", default-features = false, features = ["with_reqwest", "prometheus_client_crate"] }
use prometheus_client::encoding::text::encode;
use prometheus_push::prometheus_client_crate::PrometheusClientMetricsPusher;
use reqwest::Client;
use url::Url;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let push_gateway: Url = Url::parse("<address to pushgateway>")?;
    let client = Client::new();
    let metrics_pusher = PrometheusClientMetricsPusher::create(client, &push_gateway)?;
    let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
    let mut metrics = String::new();
    encode(&mut metrics, &registry)?;

    metrics_pusher
        .push_all(
            "<your push jobs name>",
            &grouping,
            metrics,
        )
        .await?;

    Ok(())
}

4. I use reqwest and prometheus-client crates in a blocking fashion

In your Cargo.toml:

[dependencies]
prometheus_push = { version = "<version>", default-features = false, features = ["with_reqwest_blocking", "prometheus_client_crate"] }
use prometheus_client::encoding::text::encode;
use prometheus_push::prometheus_client_crate::PrometheusClientMetricsPusherBlocking;
use reqwest::blocking::Client;
use url::Url;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let push_gateway: Url = Url::parse("<address to pushgateway>")?;
    let client = Client::new();
    let metrics_pusher = PrometheusClientMetricsPusherBlocking::create(client, &push_gateway)?;
    let grouping: HashMap<&str, &str> = HashMap::from([("<label_name>", "<label_value>")]);
    let mut metrics = String::new();
    encode(&mut metrics, &registry)?;

    metrics_pusher
        .push_all(
            "<your push jobs name>",
            &grouping,
            metrics,
        )?;

    Ok(())
}

5. I want to implement everything myself

In case you wanna implement everything yourself you can do so by implementing the Push trait and the ConvertMetrics trait.

Implement Push yourself

If you are not using reqwest as an http client you are free to implement the Push traits two methods yourself. As a guide you can use the implementation of the with_reqwest feature (see here). Basically it is as simple as that.

use prometheus_push::Push;

pub struct YourPushClient;

impl Push<Vec<u8>> for YourPushClient {
    async fn push_all(&self, url: &Url, body: Vec<u8>, content_type: &str) -> Result<()> {
        // implement a PUT request with your client with this body and `content_type` in header
    }

    async fn push_add(&self, url: &Url, body: Vec<u8>, content_type: &str) -> Result<()> {
        // implement a POST request with your client with this body and `content_type` in header
    }
}

Implement ConvertMetrics yourself

In case you want to use another prometheus client implementation you can implement your own type that implements the ConvertMetrics trait to inject it into your instance of MetricsPusher.

impl ConvertMetrics<Vec<YourMetricFamily>, Vec<Box<dyn YourCollector>>, Vec<u8>> for YourMetricsConverter {
    fn metric_families_from(
        &self,
        collectors: Vec<Box<dyn YourCollector>>,
    ) -> Result<Vec<YourMetricFamily>> {
        // implement the conversion from your Collectors to your MetricsFamilies, or whatever
        // your generic `MF` type stands for
    }

    fn create_push_details(
        &self,
        job: &str,
        url: &Url,
        grouping: &HashMap<&str, &str>,
        metric_families: Vec<YourMetricFamily>,
    ) -> Result<(Url, Vec<u8>, String)> {
        // create your push details for the `Push` methods: Url, body and content type
    }
}

Features

  • default: by default async functionality and no reqwest is enabled
  • non_blocking: this ennables the async functionality
  • blocking: on top of the default feature you get the same functionality in a blocking fashion
  • with_reqwest: this feature enables the non_blocking feature as well as reqwest in minimal configuration and enables the alredy implemented PushClient
  • with_reqwest_blocking: like with_reqwest but including blocking instead of non_blocking
  • prometheus_crate: enables the functionality of the prometheus crate
  • prometheus_client_crate: enables the functionality of the prometheus-client crate

License

MIT