Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion opentelemetry-prometheus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
once_cell = { workspace = true }
opentelemetry = { workspace = true, features = ["metrics", "internal-logs"] }
opentelemetry_sdk = { workspace = true, features = ["metrics", "experimental_metrics_custom_reader"] }
opentelemetry_sdk = { workspace = true, features = ["metrics", "experimental_metrics_custom_reader", "spec_unstable_metrics_views"] }
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly don't understand why the LLM decided to add this here lol. I tried doing some research, but it seems way beyond my Rust knowledge

prometheus = { version = "0.14", default-features = false }
tracing = { workspace = true, optional = true } # optional for opentelemetry internal logging

Expand Down
43 changes: 32 additions & 11 deletions opentelemetry-prometheus/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,32 @@ use std::sync::{Arc, Mutex};
use crate::{Collector, PrometheusExporter, ResourceSelector};

/// [PrometheusExporter] configuration options
#[derive(Default)]
pub struct ExporterBuilder {
registry: Option<prometheus::Registry>,
disable_target_info: bool,
without_units: bool,
without_counter_suffixes: bool,
namespace: Option<String>,
disable_scope_info: bool,
scope_info_enabled: bool,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ :)

reader: ManualReaderBuilder,
resource_selector: ResourceSelector,
}

impl Default for ExporterBuilder {
fn default() -> Self {
ExporterBuilder {
registry: None,
disable_target_info: false,
without_units: false,
without_counter_suffixes: false,
namespace: None,
scope_info_enabled: true,
reader: ManualReaderBuilder::default(),
resource_selector: ResourceSelector::default(),
}
}
}

impl fmt::Debug for ExporterBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExporterBuilder")
Expand All @@ -26,7 +40,7 @@ impl fmt::Debug for ExporterBuilder {
.field("without_units", &self.without_units)
.field("without_counter_suffixes", &self.without_counter_suffixes)
.field("namespace", &self.namespace)
.field("disable_scope_info", &self.disable_scope_info)
.field("scope_info_enabled", &self.scope_info_enabled)
.finish()
}
}
Expand Down Expand Up @@ -66,20 +80,27 @@ impl ExporterBuilder {
self
}

/// Configures the exporter to not export the `otel_scope_info` metric.
/// Configures whether to export instrumentation scope labels on metric points.
///
/// If not specified, scope info is enabled and the exporter adds
/// `otel_scope_*` labels to all metric points.
pub fn scope_info_enabled(mut self, enabled: bool) -> Self {
self.scope_info_enabled = enabled;
self
}

/// Configures the exporter to not export instrumentation scope labels on metric points.
///
/// If not specified, the exporter will create a `otel_scope_info` metric
/// containing the metrics' Instrumentation Scope, and also add labels about
/// Instrumentation Scope to all metric points.
/// Deprecated: use [`scope_info_enabled(false)`](Self::scope_info_enabled) instead.
pub fn without_scope_info(mut self) -> Self {
self.disable_scope_info = true;
self.scope_info_enabled = false;
self
}
Comment on lines 95 to 98
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the function just for backwards compatibility, but happy to remove it it isn't needed


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

Expand Down Expand Up @@ -123,7 +144,7 @@ impl ExporterBuilder {
disable_target_info: self.disable_target_info,
without_units: self.without_units,
without_counter_suffixes: self.without_counter_suffixes,
disable_scope_info: self.disable_scope_info,
scope_info_enabled: self.scope_info_enabled,
create_target_info_once: OnceCell::new(),
namespace: self.namespace,
inner: Mutex::new(Default::default()),
Expand Down
113 changes: 55 additions & 58 deletions opentelemetry-prometheus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@
//! // a_histogram_bucket{key="value",otel_scope_name="my-app",le="+Inf"} 1
//! // a_histogram_sum{key="value",otel_scope_name="my-app"} 100
//! // a_histogram_count{key="value",otel_scope_name="my-app"} 1
//! // # HELP otel_scope_info Instrumentation Scope metadata
//! // # TYPE otel_scope_info gauge
//! // otel_scope_info{otel_scope_name="my-app"} 1
//! // # HELP target_info Target metadata
//! // # TYPE target_info gauge
//! // target_info{service_name="unknown_service"} 1
Expand Down Expand Up @@ -114,10 +111,15 @@ use std::{fmt, sync::Weak};
const TARGET_INFO_NAME: &str = "target_info";
const TARGET_INFO_DESCRIPTION: &str = "Target metadata";

const SCOPE_INFO_METRIC_NAME: &str = "otel_scope_info";
const SCOPE_INFO_DESCRIPTION: &str = "Instrumentation Scope metadata";

const SCOPE_INFO_KEYS: [&str; 2] = ["otel_scope_name", "otel_scope_version"];
const SCOPE_NAME_LABEL: &str = "otel_scope_name";
const SCOPE_VERSION_LABEL: &str = "otel_scope_version";
const SCOPE_SCHEMA_URL_LABEL: &str = "otel_scope_schema_url";
const SCOPE_ATTRIBUTE_PREFIX: &str = "otel_scope_";
const RESERVED_SCOPE_LABELS: [&str; 3] = [
SCOPE_NAME_LABEL,
SCOPE_VERSION_LABEL,
SCOPE_SCHEMA_URL_LABEL,
];

// prometheus counters MUST have a _total suffix by default:
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/compatibility/prometheus_and_openmetrics.md
Expand Down Expand Up @@ -170,7 +172,7 @@ struct Collector {
disable_target_info: bool,
without_units: bool,
without_counter_suffixes: bool,
disable_scope_info: bool,
scope_info_enabled: bool,
create_target_info_once: OnceCell<MetricFamily>,
resource_labels_once: OnceCell<Vec<LabelPair>>,
namespace: Option<String>,
Expand All @@ -180,7 +182,6 @@ struct Collector {

#[derive(Default)]
struct CollectorInner {
scope_infos: HashMap<InstrumentationScope, MetricFamily>,
metric_families: HashMap<String, MetricFamily>,
}

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

for scope_metrics in metrics.scope_metrics() {
let scope_labels = if !self.disable_scope_info {
if scope_metrics.scope().attributes().count() > 0 {
let scope_info = inner
.scope_infos
.entry(scope_metrics.scope().clone())
.or_insert_with_key(create_scope_info_metric);
res.push(scope_info.clone());
}

let mut labels =
Vec::with_capacity(1 + scope_metrics.scope().version().is_some() as usize);
let mut name = LabelPair::default();
name.set_name(SCOPE_INFO_KEYS[0].into());
name.set_value(scope_metrics.scope().name().to_string());
labels.push(name);
if let Some(version) = &scope_metrics.scope().version() {
let mut l_version = LabelPair::default();
l_version.set_name(SCOPE_INFO_KEYS[1].into());
l_version.set_value(version.to_string());
labels.push(l_version);
}
let scope_labels = if self.scope_info_enabled {
let mut labels = get_scope_labels(scope_metrics.scope());

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

fn get_scope_labels(scope: &InstrumentationScope) -> Vec<LabelPair> {
let mut labels = Vec::with_capacity(
1 + scope.version().is_some() as usize
+ scope.schema_url().is_some() as usize
+ scope.attributes().count(),
);
labels.push(label_pair(SCOPE_NAME_LABEL, scope.name()));

if let Some(version) = scope.version() {
labels.push(label_pair(SCOPE_VERSION_LABEL, version));
}

if let Some(schema_url) = scope.schema_url() {
labels.push(label_pair(SCOPE_SCHEMA_URL_LABEL, schema_url));
}

let mut attr_labels = BTreeMap::<String, Vec<String>>::new();
for kv in scope.attributes() {
let label_name = utils::sanitize_prom_kv(&format!("{SCOPE_ATTRIBUTE_PREFIX}{}", kv.key));
if RESERVED_SCOPE_LABELS.contains(&label_name.as_str()) {
continue;
}
attr_labels
.entry(label_name)
.and_modify(|values| values.push(kv.value.to_string()))
.or_insert_with(|| vec![kv.value.to_string()]);
}

for (label_name, mut values) in attr_labels {
values.sort_unstable();
labels.push(label_pair(label_name, values.join(";")));
}

labels
}

fn label_pair(name: impl Into<String>, value: impl Into<String>) -> LabelPair {
let mut label = LabelPair::default();
label.set_name(name.into());
label.set_value(value.into());
label
}

fn validate_metrics(
name: &str,
description: &str,
Expand Down Expand Up @@ -585,34 +610,6 @@ fn create_info_metric(
mf
}

fn create_scope_info_metric(scope: &InstrumentationScope) -> MetricFamily {
let mut g = prometheus::proto::Gauge::default();
g.set_value(1.0);

let mut labels = Vec::with_capacity(1 + scope.version().is_some() as usize);
let mut name = LabelPair::default();
name.set_name(SCOPE_INFO_KEYS[0].into());
name.set_value(scope.name().to_string());
labels.push(name);
if let Some(version) = &scope.version() {
let mut v_label = LabelPair::default();
v_label.set_name(SCOPE_INFO_KEYS[1].into());
v_label.set_value(version.to_string());
labels.push(v_label);
}

let mut m = prometheus::proto::Metric::default();
m.set_label(labels);
m.set_gauge(g);

let mut mf = MetricFamily::default();
mf.set_name(SCOPE_INFO_METRIC_NAME.into());
mf.set_help(SCOPE_INFO_DESCRIPTION.into());
mf.set_field_type(MetricType::GAUGE);
mf.set_metric(vec![m]);
mf
}

trait Numeric: fmt::Debug {
// lossy at large values for u64 and i64 but prometheus only handles floats
fn as_f64(&self) -> f64;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# HELP bar_bytes_total meter a bar
# TYPE bar_bytes_total counter
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0"} 100
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0"} 100
# HELP otel_scope_info Instrumentation Scope metadata
# TYPE otel_scope_info gauge
otel_scope_info{otel_scope_name="ma",otel_scope_version="v0.1.0"} 1
otel_scope_info{otel_scope_name="mb",otel_scope_version="v0.1.0"} 1
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
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
# HELP target_info Target metadata
# TYPE target_info gauge
target_info{service_name="prometheus_test",telemetry_sdk_language="rust",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# HELP bar_bytes_total meter b bar
# TYPE bar_bytes_total counter
bar_bytes_total{type="bar",otel_scope_name="ma",otel_scope_version="v0.1.0"} 100
bar_bytes_total{type="bar",otel_scope_name="mb",otel_scope_version="v0.1.0"} 100
# HELP otel_scope_info Instrumentation Scope metadata
# TYPE otel_scope_info gauge
otel_scope_info{otel_scope_name="ma",otel_scope_version="v0.1.0"} 1
otel_scope_info{otel_scope_name="mb",otel_scope_version="v0.1.0"} 1
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
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
# HELP target_info Target metadata
# TYPE target_info gauge
target_info{service_name="prometheus_test",telemetry_sdk_language="rust",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1
Loading
Loading