Repository: open-telemetry/opentelemetry-python

Issue:
Cannot run opentelemetry-instrument with prometheus as metric exporter
**Describe your environment** 
Python 3.9.13
opentelemetry-api                            1.12.0rc2
opentelemetry-distro                         0.32b0
opentelemetry-exporter-prometheus            1.12.0rc1
opentelemetry-instrumentation                0.32b0
opentelemetry-instrumentation-flask          0.32b0
opentelemetry-instrumentation-logging        0.32b0
opentelemetry-sdk                            1.12.0rc2

**Steps to reproduce**
opentelemetry-instrument --traces_exporter console --metrics_exporter prometheus python3 ./main.py

**What is the expected behavior?**
What did you expect to see?


**What is the actual behavior?**
What did you see instead?
```py
Configuration of configurator failed
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py", line 105, in _load_configurators
    entry_point.load()().configure(auto_instrumentation_version=__version__)  # type: ignore
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 273, in configure
    self._configure(**kwargs)
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 289, in _configure
    _initialize_components(kwargs.get("auto_instrumentation_version"))
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 233, in _initialize_components
    trace_exporters, metric_exporters, log_exporters = _import_exporters(
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 201, in _import_exporters
    for (exporter_name, exporter_impl,) in _import_config_components(
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 170, in _import_config_components
    raise RuntimeError(
RuntimeError: Requested component 'prometheus' not found in entry points for 'opentelemetry_metrics_exporter'
Failed to auto initialize opentelemetry
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py", line 121, in initialize
    _load_configurators()
  File "/usr/local/lib/python3.9/site-packages/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py", line 109, in _load_configurators
    raise exc
  File "/usr/local/lib/python3.9/site-packages/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py", line 105, in _load_configurators
    entry_point.load()().configure(auto_instrumentation_version=__version__)  # type: ignore
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 273, in configure
    self._configure(**kwargs)
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 289, in _configure
    _initialize_components(kwargs.get("auto_instrumentation_version"))
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 233, in _initialize_components
    trace_exporters, metric_exporters, log_exporters = _import_exporters(
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 201, in _import_exporters
    for (exporter_name, exporter_impl,) in _import_config_components(
  File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/opentelemetry/sdk/_configuration/__init__.py", line 170, in _import_config_components
    raise RuntimeError(
RuntimeError: Requested component 'prometheus' not found in entry points for 'opentelemetry_metrics_exporter'
```
 * Serving Flask app 'automatic' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)


**Additional context**
Add any other context about the problem here.



Discussion:
https://github.com/open-telemetry/opentelemetry-python/blob/0b4f247e9d401e94ec77f72ddfcf917298ea27e8/exporter/opentelemetry-exporter-prometheus/setup.cfg#L53-L55

I think it was discussed to name it `opentelemetry_metric_reader ` for Prometheus since it is an implementation of `MetricReader`. We will probably have to update the configuration to code handle this case separately.
> https://github.com/open-telemetry/opentelemetry-python/blob/0b4f247e9d401e94ec77f72ddfcf917298ea27e8/exporter/opentelemetry-exporter-prometheus/setup.cfg#L53-L55
> 
> I think it was discussed to name it `opentelemetry_metric_reader ` for Prometheus since it is an implementation of `MetricReader`. We will probably have to update the configuration to code handle this case separately.

What's flag to use for running it correctly
There is none for the auto instrumentation right now.
Discussion during 11th Aug call.

We are probably going to change the way we load the (metric) exporters for auto instrumentation which works regardless of the implementation type (push exporter/ pull metric reader) of exporter. The original proposal https://github.com/open-telemetry/opentelemetry-python/pull/2843#issuecomment-1198417591
I ran across the same issue when evaluating the possibility of instrumenting Dask with OTel. Is there any update on the proposal from #2843? I'd be happy to help with the implementation!
@hendrikmakait what proposal are you referring to? 
@srikanthccv: I'm referring to your comment above:

> We are probably going to change the way we load the (metric) exporters for auto instrumentation which works regardless of the implementation type (push exporter/ pull metric reader) of exporter. The original proposal [#2843 (comment)](https://github.com/open-telemetry/opentelemetry-python/pull/2843#issuecomment-1198417591)

Contributions are always welcome. I think @aabmass comment https://github.com/open-telemetry/opentelemetry-python/pull/2843#issuecomment-1198417591 is good way to approach this problem and it will support both push and pull exporter types.  I would like to get the consensus on this from @open-telemetry/python-approvers before any work is actually started or if someone has any alternative methods?
@srikanthccv 

+1 to @aabmass 's approach.
@hendrikmakait Please let us know if you would like to work on the implementation, and I will assign this to you. Feel free to ask any questions.
I split the entrypoint work into a separate issue https://github.com/open-telemetry/opentelemetry-python/issues/3411 and I will work on it.

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py
--- 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/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables.py
--- 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.
+"""


Test patch:
diff --git a/exporter/opentelemetry-exporter-prometheus/tests/test_entrypoints.py b/exporter/opentelemetry-exporter-prometheus/tests/test_entrypoints.py
new file mode 100644
--- /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)