diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9ef1720a..3faa61e06a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3251](https://github.com/open-telemetry/opentelemetry-python/pull/3251)) - Add missing schema_url in global api for logging and metrics ([#3251](https://github.com/open-telemetry/opentelemetry-python/pull/3251)) +- Prometheus exporter support for auto instrumentation + ([#3413](https://github.com/open-telemetry/opentelemetry-python/pull/3413)) + ## Version 1.20.0/0.41b0 (2023-09-04) diff --git a/exporter/opentelemetry-exporter-prometheus/pyproject.toml b/exporter/opentelemetry-exporter-prometheus/pyproject.toml index f7018469ba..48f4afb90f 100644 --- a/exporter/opentelemetry-exporter-prometheus/pyproject.toml +++ b/exporter/opentelemetry-exporter-prometheus/pyproject.toml @@ -26,15 +26,16 @@ classifiers = [ ] dependencies = [ "opentelemetry-api ~= 1.12", - "opentelemetry-sdk ~= 1.12", + # DONOTMERGE: confirm that this will becomes ~= 1.21 in the next release + "opentelemetry-sdk ~= 1.21.0.dev", "prometheus_client >= 0.5.0, < 1.0.0", ] [project.optional-dependencies] test = [] -[project.entry-points.opentelemetry_metric_reader] -prometheus = "opentelemetry.exporter.prometheus:PrometheusMetricReader" +[project.entry-points.opentelemetry_metrics_exporter] +prometheus = "opentelemetry.exporter.prometheus:_AutoPrometheusMetricReader" [project.urls] Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/exporter/opentelemetry-exporter-prometheus" 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 252f240b35..8d9e18d733 100644 --- a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py +++ b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py @@ -66,9 +66,11 @@ from itertools import chain from json import dumps from logging import getLogger +from os import environ from re import IGNORECASE, UNICODE, compile from typing import Dict, Sequence, Tuple, Union +from prometheus_client import start_http_server from prometheus_client.core import ( REGISTRY, CounterMetricFamily, @@ -78,6 +80,10 @@ ) from prometheus_client.core import Metric as PrometheusMetric +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_PROMETHEUS_HOST, + OTEL_EXPORTER_PROMETHEUS_PORT, +) from opentelemetry.sdk.metrics import Counter from opentelemetry.sdk.metrics import Histogram as HistogramInstrument from opentelemetry.sdk.metrics import ( @@ -375,3 +381,21 @@ def _create_info_metric( info = InfoMetricFamily(name, description, labels=attributes) info.add_metric(labels=list(attributes.keys()), value=attributes) return info + + +class _AutoPrometheusMetricReader(PrometheusMetricReader): + """Thin wrapper around PrometheusMetricReader used for the opentelemetry_metrics_exporter entry point. + + This allows users to use the prometheus exporter with opentelemetry-instrument. It handles + starting the Prometheus http server on the the correct port and host. + """ + + def __init__(self) -> None: + super().__init__() + + # Default values are specified in + # https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/configuration/sdk-environment-variables.md#prometheus-exporter + start_http_server( + port=int(environ.get(OTEL_EXPORTER_PROMETHEUS_PORT, "9464")), + addr=environ.get(OTEL_EXPORTER_PROMETHEUS_HOST, "localhost"), + ) diff --git a/exporter/opentelemetry-exporter-prometheus/tests/test_entrypoints.py b/exporter/opentelemetry-exporter-prometheus/tests/test_entrypoints.py new file mode 100644 index 0000000000..96846e0759 --- /dev/null +++ b/exporter/opentelemetry-exporter-prometheus/tests/test_entrypoints.py @@ -0,0 +1,75 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=no-self-use + +import os +from unittest import TestCase +from unittest.mock import ANY, Mock, patch + +from opentelemetry.exporter.prometheus import _AutoPrometheusMetricReader +from opentelemetry.sdk._configuration import _import_exporters +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_PROMETHEUS_HOST, + OTEL_EXPORTER_PROMETHEUS_PORT, +) + + +class TestEntrypoints(TestCase): + def test_import_exporters(self) -> None: + """ + Tests that the entrypoint can be loaded and doesn't have a typo in the name + """ + ( + _trace_exporters, + metric_exporters, + _logs_exporters, + ) = _import_exporters( + trace_exporter_names=[], + metric_exporter_names=["prometheus"], + log_exporter_names=[], + ) + + self.assertIs( + metric_exporters["prometheus"], + _AutoPrometheusMetricReader, + ) + + @patch("opentelemetry.exporter.prometheus.start_http_server") + @patch.dict(os.environ) + def test_starts_http_server_defaults( + self, mock_start_http_server: Mock + ) -> None: + _AutoPrometheusMetricReader() + mock_start_http_server.assert_called_once_with( + port=9464, addr="localhost" + ) + + @patch("opentelemetry.exporter.prometheus.start_http_server") + @patch.dict(os.environ, {OTEL_EXPORTER_PROMETHEUS_HOST: "1.2.3.4"}) + def test_starts_http_server_host_envvar( + self, mock_start_http_server: Mock + ) -> None: + _AutoPrometheusMetricReader() + mock_start_http_server.assert_called_once_with( + port=ANY, addr="1.2.3.4" + ) + + @patch("opentelemetry.exporter.prometheus.start_http_server") + @patch.dict(os.environ, {OTEL_EXPORTER_PROMETHEUS_PORT: "9999"}) + def test_starts_http_server_port_envvar( + self, mock_start_http_server: Mock + ) -> None: + _AutoPrometheusMetricReader() + mock_start_http_server.assert_called_once_with(port=9999, addr=ANY) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py index 10031e5ef3..dc8f91f600 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py @@ -678,3 +678,27 @@ experimental feature and the name of this variable and its behavior can change in a non-backwards compatible way. """ + +OTEL_EXPORTER_PROMETHEUS_HOST = "OTEL_EXPORTER_PROMETHEUS_HOST" +""" +.. envvar:: OTEL_EXPORTER_PROMETHEUS_HOST + +The :envvar:`OTEL_EXPORTER_PROMETHEUS_HOST` environment variable configures the host used by +the Prometheus exporter. +Default: "localhost" + +This is an experimental environment variable and the name of this variable and its behavior can +change in a non-backwards compatible way. +""" + +OTEL_EXPORTER_PROMETHEUS_PORT = "OTEL_EXPORTER_PROMETHEUS_PORT" +""" +.. envvar:: OTEL_EXPORTER_PROMETHEUS_PORT + +The :envvar:`OTEL_EXPORTER_PROMETHEUS_PORT` environment variable configures the port used by +the Prometheus exporter. +Default: 9464 + +This is an experimental environment variable and the name of this variable and its behavior can +change in a non-backwards compatible way. +"""