#prometheus #format #metrics #label #exposition #name #deserialize

serde_prometheus

serde-based serializer for prometheus' text-based exposition format

12 releases

0.2.4 Sep 8, 2023
0.2.3 Jun 22, 2023
0.2.2 Mar 20, 2023
0.1.6 Jun 10, 2021
0.1.4 Jul 16, 2020

#440 in Encoding

Download history 9394/week @ 2023-12-01 10126/week @ 2023-12-08 10034/week @ 2023-12-15 3644/week @ 2023-12-22 5368/week @ 2023-12-29 8811/week @ 2024-01-05 6563/week @ 2024-01-12 7531/week @ 2024-01-19 8189/week @ 2024-01-26 8672/week @ 2024-02-02 10878/week @ 2024-02-09 9675/week @ 2024-02-16 10407/week @ 2024-02-23 9765/week @ 2024-03-01 12000/week @ 2024-03-08 9216/week @ 2024-03-15

43,271 downloads per month
Used in 2 crates

WTFPL OR 0BSD

76KB
2K SLoC

Serde Prometheus Crate API

A serde implementation for Prometheus' text-based exposition format.


lib.rs:

A serde implementation for Prometheus' text-based exposition format.

Currently this library only supports serialisation to Prometheus' format for exporting metrics but this might be extended to deserialisation later on down the line.

serde_prometheus will work with most metric libraries' structs out of the box, however some work may be required to get them into a format expected by Prometheus.

Metric names exposed in the format are derived from the value's name in the struct or map that contains it.

Basic Usage

#[derive(Serialize)]
struct HitCount(u64);

#[derive(Serialize)]
struct MetricRegistry {
    my_struct: MyStructMetrics
}

#[derive(Serialize)]
struct MyStructMetrics {
    hit_count: HitCount
}

let metrics = MetricRegistry {
    my_struct: MyStructMetrics {
        hit_count: HitCount(30)
    }
};

assert_eq!(
   serde_prometheus::to_string(&metrics, None, HashMap::new())?,
   "hit_count{path = \"my_struct\"} 30\n"
);

Global Labels

Global labels can be added to all metrics exported by serde_prometheus using the HashMap (or any type resolving to IntoIterator<Borrow<(&str, &str)>>) passed into serde_prometheus::to_string for example:

#
#
#
let mut labels = HashMap::new();
labels.insert("my_key", "my_value");

let serialised = serde_prometheus::to_string(&metrics, None, &[("my_key", "my_value")])?;
assert_eq!(serialised, "hit_count{my_key = \"my_value\", path = \"my_struct\"} 30\n");

Global Prefix

And a global prefix can be added to all metrics:

#
#
#
assert_eq!(
   serde_prometheus::to_string(&metrics, Some("my_prefix"), HashMap::new())?,
   "my_prefix_hit_count{path = \"my_struct\"} 30\n"
);

Metadata/key manipulation

Serde's newtype implementation is (ab)used by serde_prometheus to add metadata to serialised fields without breaking backwards compatibility with serde_json and such.

For example, serde_prometheus support has been added to metered-rs's histograms whilst still keeping the same JSON schema, it does this by using a call to serialize_newtype_struct in a struct's Serialize trait impl, the format for the type names is as follows:

keymodifiers|key=value,key2=value2

Modifiers can also be used in labels using a == like so:

|key3==modifiers

The path stack is reset for each label value, however after applying the key modifiers, it is retained when writing the path label itself.

The modifiers that can be used are:

Modifier Description
< Pops a value off of the path stack and appends it to the name
! Pops the last value off of the path stack and drops it
- Moves the stack cursor back a position, when a value is popped off the stack, the position is reset back to the top of the stack
. The default behaviour of serde_prometheus 0.1 is to append the collected stack to the next value in path (as if an extra < was added to your modifiers), to prevent this use this modifier. This has no effect in labels.

These can be combined and are read from left to right, for example:

#
#
#
struct HitCount(u64);
impl Serialize for HitCount {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        // metric name: include our key (hit_count), ignore the second (my_method), and include the third (my_struct)
        // metric meta: for the method_name, ignore our key (hit_count), include the second (my_method)
        serializer.serialize_newtype_struct("<!<|my_key=my_value,method_name==!<", &self.0)
    }
}

let metrics = MetricRegistry {
    my_struct: MyStructMetrics {
        my_method: MyMethodMetrics {
            hit_count: HitCount(30)
        }
    }
};

let serialised = serde_prometheus::to_string(&metrics, None, HashMap::new())?;
assert_eq!(
   serialised,
   // would be `hit_count{path = "my_struct/my_method"}` without the Serialize impl
   "my_struct_hit_count{my_key = \"my_value\", method_name = \"my_method\"} 30\n"
);

Internal overrides

serialize_newtype_struct supports an extra section to allow overriding of internal operations, as follows:

keymodifiers|key=value,key2=value2|:internal=abc

Internal overrides are always prefixed with a : to make it plainly obvious they're not labels, a list of internal overrides available for use are as follows:

Override Description
:namespace Overrides the global namespace with a new value for that struct and any leaves under it

Label concatenation

By default, when added via a serialize_newtype_struct call, a new label added by a "deeper" value will override a previously set one set further up the stack. This can be overridden by the "deeper" value by appending [::] to the label name, this will concatenate the previously set label and the new label with a ::. The :: can be anything valid in a Prometheus label value except =.

fn add_my_cool_key<S: serde::Serializer, T: serde::Serialize>(
    value: &T,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    serializer.serialize_newtype_struct("!|my_cool_key[::]==<", value)
}

#[derive(Serialize)]
struct MetricRegistry {
    #[serde(serialize_with = "add_my_cool_key")]
    my_struct: MyStructMetrics
}

#[derive(Serialize)]
struct MyStructMetrics {
    #[serde(serialize_with = "add_my_cool_key")]
    my_method: MyMethodMetrics
}

#[derive(Serialize)]
struct MyMethodMetrics {
    hit_count: HitCount
}
#

let metrics = MetricRegistry {
    my_struct: MyStructMetrics {
        my_method: MyMethodMetrics {
            hit_count: HitCount(30)
        }
    }
};

let serialised = serde_prometheus::to_string(&metrics, None, HashMap::new())?;
assert_eq!(
   serialised,
   "hit_count{my_cool_key = \"my_struct::my_method\"} 30\n"
);

Dependencies

~2.3–3MB
~64K SLoC