diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d4680633..4882bfaadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4862](https://github.com/open-telemetry/opentelemetry-python/pull/4862)) - `opentelemetry-exporter-otlp-proto-http`: fix retry logic and error handling for connection failures in trace, metric, and log exporters ([#4709](https://github.com/open-telemetry/opentelemetry-python/pull/4709)) +- `opentelemetry-exporter-prometheus`: Fix metric name prefix + ([#4895](https://github.com/open-telemetry/opentelemetry-python/pull/4895)) ## Version 1.39.0/0.60b0 (2025-12-03) diff --git a/docs/examples/metrics/prometheus-grafana/prometheus-monitor.py b/docs/examples/metrics/prometheus-grafana/prometheus-monitor.py index 709b0b9e75..941be3bcd3 100644 --- a/docs/examples/metrics/prometheus-grafana/prometheus-monitor.py +++ b/docs/examples/metrics/prometheus-grafana/prometheus-monitor.py @@ -11,7 +11,7 @@ start_http_server(port=8000, addr="localhost") # Exporter to export metrics to Prometheus prefix = "MyAppPrefix" -reader = PrometheusMetricReader(prefix) +reader = PrometheusMetricReader(prefix=prefix) # Meter is responsible for creating and recording metrics set_meter_provider(MeterProvider(metric_readers=[reader])) meter = get_meter_provider().get_meter("view-name-change", "0.1.2") diff --git a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py index fa89da4e71..4cfc626fa7 100644 --- a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py +++ b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py @@ -38,7 +38,7 @@ # Exporter to export metrics to Prometheus prefix = "MyAppPrefix" - reader = PrometheusMetricReader(prefix) + reader = PrometheusMetricReader(prefix=prefix) # Meter is responsible for creating and recording metrics set_meter_provider(MeterProvider(metric_readers=[reader])) @@ -131,7 +131,7 @@ def _convert_buckets( class PrometheusMetricReader(MetricReader): """Prometheus metric exporter for OpenTelemetry.""" - def __init__(self, disable_target_info: bool = False) -> None: + def __init__(self, disable_target_info: bool = False, prefix: str = "") -> None: super().__init__( preferred_temporality={ Counter: AggregationTemporality.CUMULATIVE, @@ -142,9 +142,10 @@ def __init__(self, disable_target_info: bool = False) -> None: ObservableGauge: AggregationTemporality.CUMULATIVE, } ) - self._collector = _CustomCollector(disable_target_info) + self._collector = _CustomCollector(disable_target_info=disable_target_info, prefix=prefix) REGISTRY.register(self._collector) self._collector._callback = self.collect + self._prefix = prefix def _receive_metrics( self, @@ -167,11 +168,12 @@ class _CustomCollector: https://github.com/prometheus/client_python#custom-collectors """ - def __init__(self, disable_target_info: bool = False): + def __init__(self, disable_target_info: bool = False, prefix: str = ""): self._callback = None self._metrics_datas: Deque[MetricsData] = deque() self._disable_target_info = disable_target_info self._target_info = None + self._prefix = prefix def add_metrics_data(self, metrics_data: MetricsData) -> None: """Add metrics to Prometheus data""" @@ -227,7 +229,10 @@ def _translate_to_prometheus( label_values_data_points = [] values = [] - metric_name = sanitize_full_name(metric.name) + metric_name = metric.name + if self._prefix: + metric_name = self._prefix + "_" + metric_name + metric_name = sanitize_full_name(metric_name) metric_description = metric.description or "" metric_unit = map_unit(metric.unit) diff --git a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py index d98c69cb86..1aa7143eec 100644 --- a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py +++ b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py @@ -55,7 +55,7 @@ def setUp(self): ) def verify_text_format( - self, metric: Metric, expect_prometheus_text: str + self, metric: Metric, expect_prometheus_text: str, prefix: str = "" ) -> None: metrics_data = MetricsData( resource_metrics=[ @@ -73,7 +73,7 @@ def verify_text_format( ] ) - collector = _CustomCollector(disable_target_info=True) + collector = _CustomCollector(disable_target_info=True, prefix=prefix) collector.add_metrics_data(metrics_data) result_bytes = generate_latest(collector) result = result_bytes.decode("utf-8") @@ -463,6 +463,28 @@ def test_metric_name(self): """ ), ) + self.verify_text_format( + _generate_sum(name="test_counter_w_prefix", value=1, unit=""), + dedent( + """\ + # HELP foo_test_counter_w_prefix_total foo + # TYPE foo_test_counter_w_prefix_total counter + foo_test_counter_w_prefix_total{a="1",b="true"} 1.0 + """ + ), + prefix="foo" + ) + self.verify_text_format( + _generate_sum(name="test_counter_w_invalid_chars_prefix", value=1, unit=""), + dedent( + """\ + # HELP _foo_test_counter_w_invalid_chars_prefix_total foo + # TYPE _foo_test_counter_w_invalid_chars_prefix_total counter + _foo_test_counter_w_invalid_chars_prefix_total{a="1",b="true"} 1.0 + """ + ), + prefix="#foo" + ) self.verify_text_format( _generate_sum(name="1leading_digit", value=1, unit=""), dedent(