Skip to content

Commit 8a2067c

Browse files
feat: Implement scope_info_enabled (#3503)
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> Co-authored-by: Cijo Thomas <cijo.thomas@gmail.com>
1 parent 2571776 commit 8a2067c

33 files changed

Lines changed: 374 additions & 408 deletions

opentelemetry-prometheus/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## vNext
44

5+
- Replace `without_scope_info` with `scope_info_enabled` to configure Prometheus instrumentation scope labels, inverting the option from disabling scope info to enabling it. Before this change, the exporter emitted an `otel_scope_info` metric and only added `otel_scope_name`/`otel_scope_version` labels to metric points. Now scope info is enabled by default on metric points with `otel_scope_name`, `otel_scope_version`, `otel_scope_schema_url`, and scope attributes prefixed with `otel_scope_`; setting `scope_info_enabled(false)` suppresses those labels. [#3503](https://github.com/open-telemetry/opentelemetry-rust/pull/3503)
6+
57
## 0.32.0
68

79
Released 2026-May-08

opentelemetry-prometheus/src/config.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,32 @@ use std::sync::{Arc, Mutex};
66
use crate::{Collector, PrometheusExporter, ResourceSelector};
77

88
/// [PrometheusExporter] configuration options
9-
#[derive(Default)]
109
pub struct ExporterBuilder {
1110
registry: Option<prometheus::Registry>,
1211
disable_target_info: bool,
1312
without_units: bool,
1413
without_counter_suffixes: bool,
1514
namespace: Option<String>,
16-
disable_scope_info: bool,
15+
scope_info_enabled: bool,
1716
reader: ManualReaderBuilder,
1817
resource_selector: ResourceSelector,
1918
}
2019

20+
impl Default for ExporterBuilder {
21+
fn default() -> Self {
22+
ExporterBuilder {
23+
registry: None,
24+
disable_target_info: false,
25+
without_units: false,
26+
without_counter_suffixes: false,
27+
namespace: None,
28+
scope_info_enabled: true,
29+
reader: ManualReaderBuilder::default(),
30+
resource_selector: ResourceSelector::default(),
31+
}
32+
}
33+
}
34+
2135
impl fmt::Debug for ExporterBuilder {
2236
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2337
f.debug_struct("ExporterBuilder")
@@ -26,7 +40,7 @@ impl fmt::Debug for ExporterBuilder {
2640
.field("without_units", &self.without_units)
2741
.field("without_counter_suffixes", &self.without_counter_suffixes)
2842
.field("namespace", &self.namespace)
29-
.field("disable_scope_info", &self.disable_scope_info)
43+
.field("scope_info_enabled", &self.scope_info_enabled)
3044
.finish()
3145
}
3246
}
@@ -66,20 +80,19 @@ impl ExporterBuilder {
6680
self
6781
}
6882

69-
/// Configures the exporter to not export the `otel_scope_info` metric.
83+
/// Configures whether to export instrumentation scope labels on metric points.
7084
///
71-
/// If not specified, the exporter will create a `otel_scope_info` metric
72-
/// containing the metrics' Instrumentation Scope, and also add labels about
73-
/// Instrumentation Scope to all metric points.
74-
pub fn without_scope_info(mut self) -> Self {
75-
self.disable_scope_info = true;
85+
/// If not specified, scope info is enabled and the exporter adds
86+
/// `otel_scope_*` labels to all metric points.
87+
pub fn scope_info_enabled(mut self, enabled: bool) -> Self {
88+
self.scope_info_enabled = enabled;
7689
self
7790
}
7891

7992
/// Configures the exporter to prefix metrics with the given namespace.
8093
///
81-
/// Metrics such as `target_info` and `otel_scope_info` are not prefixed since
82-
/// these have special behavior based on their name.
94+
/// Metrics such as `target_info` are not prefixed since these have special
95+
/// behavior based on their name.
8396
pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
8497
let mut namespace = namespace.into();
8598

@@ -123,7 +136,7 @@ impl ExporterBuilder {
123136
disable_target_info: self.disable_target_info,
124137
without_units: self.without_units,
125138
without_counter_suffixes: self.without_counter_suffixes,
126-
disable_scope_info: self.disable_scope_info,
139+
scope_info_enabled: self.scope_info_enabled,
127140
create_target_info_once: OnceCell::new(),
128141
namespace: self.namespace,
129142
inner: Mutex::new(Default::default()),

opentelemetry-prometheus/src/lib.rs

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@
6565
//! // a_histogram_bucket{key="value",otel_scope_name="my-app",le="+Inf"} 1
6666
//! // a_histogram_sum{key="value",otel_scope_name="my-app"} 100
6767
//! // a_histogram_count{key="value",otel_scope_name="my-app"} 1
68-
//! // # HELP otel_scope_info Instrumentation Scope metadata
69-
//! // # TYPE otel_scope_info gauge
70-
//! // otel_scope_info{otel_scope_name="my-app"} 1
7168
//! // # HELP target_info Target metadata
7269
//! // # TYPE target_info gauge
7370
//! // target_info{service_name="unknown_service"} 1
@@ -114,10 +111,15 @@ use std::{fmt, sync::Weak};
114111
const TARGET_INFO_NAME: &str = "target_info";
115112
const TARGET_INFO_DESCRIPTION: &str = "Target metadata";
116113

117-
const SCOPE_INFO_METRIC_NAME: &str = "otel_scope_info";
118-
const SCOPE_INFO_DESCRIPTION: &str = "Instrumentation Scope metadata";
119-
120-
const SCOPE_INFO_KEYS: [&str; 2] = ["otel_scope_name", "otel_scope_version"];
114+
const SCOPE_NAME_LABEL: &str = "otel_scope_name";
115+
const SCOPE_VERSION_LABEL: &str = "otel_scope_version";
116+
const SCOPE_SCHEMA_URL_LABEL: &str = "otel_scope_schema_url";
117+
const SCOPE_ATTRIBUTE_PREFIX: &str = "otel_scope_";
118+
const RESERVED_SCOPE_LABELS: [&str; 3] = [
119+
SCOPE_NAME_LABEL,
120+
SCOPE_VERSION_LABEL,
121+
SCOPE_SCHEMA_URL_LABEL,
122+
];
121123

122124
// prometheus counters MUST have a _total suffix by default:
123125
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/compatibility/prometheus_and_openmetrics.md
@@ -170,7 +172,7 @@ struct Collector {
170172
disable_target_info: bool,
171173
without_units: bool,
172174
without_counter_suffixes: bool,
173-
disable_scope_info: bool,
175+
scope_info_enabled: bool,
174176
create_target_info_once: OnceCell<MetricFamily>,
175177
resource_labels_once: OnceCell<Vec<LabelPair>>,
176178
namespace: Option<String>,
@@ -180,7 +182,6 @@ struct Collector {
180182

181183
#[derive(Default)]
182184
struct CollectorInner {
183-
scope_infos: HashMap<InstrumentationScope, MetricFamily>,
184185
metric_families: HashMap<String, MetricFamily>,
185186
}
186187

@@ -301,27 +302,8 @@ impl prometheus::core::Collector for Collector {
301302
.get_or_init(|| self.resource_selector.select(metrics.resource()));
302303

303304
for scope_metrics in metrics.scope_metrics() {
304-
let scope_labels = if !self.disable_scope_info {
305-
if scope_metrics.scope().attributes().count() > 0 {
306-
let scope_info = inner
307-
.scope_infos
308-
.entry(scope_metrics.scope().clone())
309-
.or_insert_with_key(create_scope_info_metric);
310-
res.push(scope_info.clone());
311-
}
312-
313-
let mut labels =
314-
Vec::with_capacity(1 + scope_metrics.scope().version().is_some() as usize);
315-
let mut name = LabelPair::default();
316-
name.set_name(SCOPE_INFO_KEYS[0].into());
317-
name.set_value(scope_metrics.scope().name().to_string());
318-
labels.push(name);
319-
if let Some(version) = &scope_metrics.scope().version() {
320-
let mut l_version = LabelPair::default();
321-
l_version.set_name(SCOPE_INFO_KEYS[1].into());
322-
l_version.set_value(version.to_string());
323-
labels.push(l_version);
324-
}
305+
let scope_labels = if self.scope_info_enabled {
306+
let mut labels = get_scope_labels(scope_metrics.scope());
325307

326308
if !resource_labels.is_empty() {
327309
labels.extend(resource_labels.iter().cloned());
@@ -422,6 +404,48 @@ fn get_attrs(kvs: &mut dyn Iterator<Item = (&Key, &Value)>, extra: &[LabelPair])
422404
res
423405
}
424406

407+
fn get_scope_labels(scope: &InstrumentationScope) -> Vec<LabelPair> {
408+
let mut labels = Vec::with_capacity(
409+
1 + scope.version().is_some() as usize
410+
+ scope.schema_url().is_some() as usize
411+
+ scope.attributes().count(),
412+
);
413+
labels.push(label_pair(SCOPE_NAME_LABEL, scope.name()));
414+
415+
if let Some(version) = scope.version() {
416+
labels.push(label_pair(SCOPE_VERSION_LABEL, version));
417+
}
418+
419+
if let Some(schema_url) = scope.schema_url() {
420+
labels.push(label_pair(SCOPE_SCHEMA_URL_LABEL, schema_url));
421+
}
422+
423+
let mut attr_labels = BTreeMap::<String, Vec<String>>::new();
424+
for kv in scope.attributes() {
425+
let label_name = utils::sanitize_prom_kv(&format!("{SCOPE_ATTRIBUTE_PREFIX}{}", kv.key));
426+
if RESERVED_SCOPE_LABELS.contains(&label_name.as_str()) {
427+
continue;
428+
}
429+
attr_labels
430+
.entry(label_name)
431+
.and_modify(|values| values.push(kv.value.to_string()))
432+
.or_insert_with(|| vec![kv.value.to_string()]);
433+
}
434+
435+
for (label_name, values) in attr_labels {
436+
labels.push(label_pair(label_name, values.join(";")));
437+
}
438+
439+
labels
440+
}
441+
442+
fn label_pair(name: impl Into<String>, value: impl Into<String>) -> LabelPair {
443+
let mut label = LabelPair::default();
444+
label.set_name(name.into());
445+
label.set_value(value.into());
446+
label
447+
}
448+
425449
fn validate_metrics(
426450
name: &str,
427451
description: &str,
@@ -585,34 +609,6 @@ fn create_info_metric(
585609
mf
586610
}
587611

588-
fn create_scope_info_metric(scope: &InstrumentationScope) -> MetricFamily {
589-
let mut g = prometheus::proto::Gauge::default();
590-
g.set_value(1.0);
591-
592-
let mut labels = Vec::with_capacity(1 + scope.version().is_some() as usize);
593-
let mut name = LabelPair::default();
594-
name.set_name(SCOPE_INFO_KEYS[0].into());
595-
name.set_value(scope.name().to_string());
596-
labels.push(name);
597-
if let Some(version) = &scope.version() {
598-
let mut v_label = LabelPair::default();
599-
v_label.set_name(SCOPE_INFO_KEYS[1].into());
600-
v_label.set_value(version.to_string());
601-
labels.push(v_label);
602-
}
603-
604-
let mut m = prometheus::proto::Metric::default();
605-
m.set_label(labels);
606-
m.set_gauge(g);
607-
608-
let mut mf = MetricFamily::default();
609-
mf.set_name(SCOPE_INFO_METRIC_NAME.into());
610-
mf.set_help(SCOPE_INFO_DESCRIPTION.into());
611-
mf.set_field_type(MetricType::GAUGE);
612-
mf.set_metric(vec![m]);
613-
mf
614-
}
615-
616612
trait Numeric: fmt::Debug {
617613
// lossy at large values for u64 and i64 but prometheus only handles floats
618614
fn as_f64(&self) -> f64;
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
# HELP bar_bytes_total meter a bar
22
# TYPE bar_bytes_total counter
3-
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0"} 100
4-
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0"} 100
5-
# HELP otel_scope_info Instrumentation Scope metadata
6-
# TYPE otel_scope_info gauge
7-
otel_scope_info{otel_scope_name="ma",otel_scope_version="v0.1.0"} 1
8-
otel_scope_info{otel_scope_name="mb",otel_scope_version="v0.1.0"} 1
3+
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0",otel_scope_schema_url="https://opentelemetry.io/schemas/1.0.0",otel_scope_k="v"} 100
4+
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0",otel_scope_schema_url="https://opentelemetry.io/schemas/1.0.0",otel_scope_k="v"} 100
95
# HELP target_info Target metadata
106
# TYPE target_info gauge
117
target_info{service_name="prometheus_test",telemetry_sdk_language="rust",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
# HELP bar_bytes_total meter b bar
22
# TYPE bar_bytes_total counter
3-
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0"} 100
4-
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0"} 100
5-
# HELP otel_scope_info Instrumentation Scope metadata
6-
# TYPE otel_scope_info gauge
7-
otel_scope_info{otel_scope_name="ma",otel_scope_version="v0.1.0"} 1
8-
otel_scope_info{otel_scope_name="mb",otel_scope_version="v0.1.0"} 1
3+
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0",otel_scope_schema_url="https://opentelemetry.io/schemas/1.0.0",otel_scope_k="v"} 100
4+
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0",otel_scope_schema_url="https://opentelemetry.io/schemas/1.0.0",otel_scope_k="v"} 100
95
# HELP target_info Target metadata
106
# TYPE target_info gauge
117
target_info{service_name="prometheus_test",telemetry_sdk_language="rust",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1

0 commit comments

Comments
 (0)