Skip to content

Feature Request: Add #[prometheus(skip_encoding_if = "...")] for conditional label encoding in EncodeLabelSet derive #284

@cristinel24

Description

@cristinel24

Is your feature request related to a problem? Please describe.

Currently, when using #[derive(EncodeLabelSet)], there is no straightforward way to conditionally skip a field from being encoded as a label based on its value. A common use case is for fields of type Option<T>, where you might only want to include the label if the value is Some(T) and omit it entirely if it's None.

Without this feature, an Option<String> field that is None gets encoded to an empty string, resulting in an output like my_metric{..., optional_label=""} 1. This can be misleading or add unnecessary cardinality/noise to the metrics.

The only current workaround is to manually implement EncodeLabelSet for the struct. This is verbose and negates the convenience of using the derive macro, especially for structs with many fields.

Describe the solution you'd like

I propose adding a new helper attribute, #[prometheus(skip_encoding_if = "path::to::function")], that can be applied to fields within a struct deriving EncodeLabelSet.

This attribute would work as follows:

  • It accepts a string literal containing a path to a function.
  • The derive macro will generate code that calls this function, passing it a reference to the field's value.
  • The function must have a signature like fn(&T) -> bool, where T is the type of the field.
  • If the function returns true, the field is completely skipped during label encoding.
  • If it returns false, the field is encoded as usual.

This provides a flexible and powerful way to control label generation. The most obvious use case is with Option::is_none.

Example:

use prometheus_client::encoding::EncodeLabelSet;

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct RequestLabels {
    pub http_method: String,
    pub path: String,
    
    // This label should only be present if the value is `Some`.
    #[prometheus(skip_encoding_if = "Option::is_none")]
    pub user_id: Option<u64>,
}

// With a `user_id` of `Some(123)`, the output would be:
// http_requests_total{http_method="GET",path="/api/v1/users",user_id="123"} 1

// With a `user_id` of `None`, the desired output would be:
// http_requests_total{http_method="GET",path="/api/v1/users"} 1
// (The `user_id` label is omitted entirely, not just empty.)

This functionality would be similar to the #[serde(skip_serializing_if = "...")] attribute in serde, which is a well-understood and highly useful pattern in the Rust ecosystem. Implementing this would involve extending the EncodeLabelSet derive macro to parse this new attribute and wrap the encoding logic for the specific field in a conditional block.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions