metrics-rs / metrics

A metrics ecosystem for Rust.
MIT License
1.11k stars 156 forks source link

`IntoLabels` for `BTreeMap<String, String>` #469

Closed jpds closed 5 months ago

jpds commented 5 months ago

I'm trying to use this library along with serde_yaml to create an exporter which will read a YAML file with targets and then polls those for data.

Here's an example YAML file:

metrics:
- metric: my_metrics
  help: My metric help
  labels:
    customer: name
    foo: bar
  location:
   - location_url

And then leverages code which then looks like this:

use metrics::gauge;
use metrics_exporter_prometheus::PrometheusBuilder;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::thread;
use std::time::Duration;

#[derive(Debug, Serialize, Deserialize)]
struct MetricsFile {
    metrics: Vec<Metric>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct Metric {
    metric: String,
    help: Option<String>,
    //labels: Option<serde_yaml::Mapping>,
    labels: Option<BTreeMap<String, String>>,
    location: Vec<String>,
}

fn main() {
    // Read file with metric locations
    let contents = fs::read_to_string("test.yaml").expect("Unable to read locations file");

    // Parse YAML contents
    let yaml_file: MetricsFile = serde_yaml::from_str::<MetricsFile>(&contents)
        .expect("Error parsing locations file");

    let builder = PrometheusBuilder::new()
        .install()
        .expect("failed to install Prometheus recorder");

    loop {
        for target in yaml_file.metrics.clone() {
            // Read data from location here
            // [...]

            if !target.labels.is_none() {
                //let _gauge = gauge!(target.metric.clone()).set(/* Fake location reading */ 42.0); // This works
                let _gauge = gauge!(target.metric, target.labels.expect("Should have a BTreeMap here")).set(/* Fake location reading */ 42.0);
            }
        }

        thread::sleep(Duration::from_millis(10000));
    }
}

However, this fails because:

Compiling example v0.1.0
error[E0277]: the trait bound `BTreeMap<std::string::String, std::string::String>: IntoLabels` is not satisfied
  --> src/main.rs:43:51
   |
43 |                 let _gauge = gauge!(target.metric, target.labels.expect("Should have a BTreeMap here")).set(/* Fake location reading */ 42.0);
   |                              ----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
   |                              |                     |
   |                              |                     the trait `IntoLabels` is not implemented for `BTreeMap<std::string::String, std::string::String>`
   |                              required by a bound introduced by this call
   |
   = help: the following other types implement trait `IntoLabels`:
             Vec<Label>
             std::slice::Iter<'_, Label>
             &T
note: required by a bound in `metrics::Key::from_parts`
  --> /home/username/.cargo/registry/src/index.crates.io-6f17d22bba15001f/metrics-0.22.3/src/key.rs:83:12
   |
80 |     pub fn from_parts<N, L>(name: N, labels: L) -> Self
   |            ---------- required by a bound in this associated function
...
83 |         L: IntoLabels,
   |            ^^^^^^^^^^ required by this bound in `Key::from_parts`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `example` (bin "example") due to previous error

I don't know of any other way of representing the labels YAML field - so could this BTreeMap support be added?

tobz commented 5 months ago

Off the top of my head... did you try borrowing the map (i.e. &target.labels.expect(...)? That would get you the &T which should then hopefully allow the implementation of IntoLabels on T: IntoIterator<Item = Into<Label>> to kick in.

jpds commented 5 months ago

That did work! But then triggers a:

error[E0277]: the trait bound `Label: From<(&std::string::String, &std::string::String)>` is not satisfied
  --> src/main.rs:43:52
   |
43 | ...et gauge = gauge!(target.metric, &target.labels.expect("Should have gotten a BTreeMap here")).set(/* Fake loc...
   |               -----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
   |               |                      |
   |               |                      the trait `From<(&std::string::String, &std::string::String)>` is not implemented for `Label`
   |               required by a bound introduced by this call
   |
   = help: the trait `From<&(K, V)>` is implemented for `Label`
   = note: required for `(&std::string::String, &std::string::String)` to implement `Into<Label>`
   = note: required for `&BTreeMap<std::string::String, std::string::String>` to implement `IntoLabels`
note: required by a bound in `metrics::Key::from_parts`
  --> /home/username/.cargo/registry/src/index.crates.io-6f17d22bba15001f/metrics-0.22.3/src/key.rs:83:12
   |
80 |     pub fn from_parts<N, L>(name: N, labels: L) -> Self
   |            ---------- required by a bound in this associated function
...
83 |         L: IntoLabels,
   |            ^^^^^^^^^^ required by this bound in `Key::from_parts`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `example` (bin "example") due to previous error
tobz commented 5 months ago

Ah drats, yeah: we only have Into<Label> for &(K, V).

We could probably add another impl for (&K, &V).