Repository: open-telemetry/opentelemetry-python

Issue:
Convert Values to JSON for prometheus collector
* Fixes #2502

# Convert values to native JSON 

Convert values to native JSON instead of string in the Prometheus Collector
* Changes in the list for `_translate_to_prometheus`

Fixes #2502

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# Checklist:

- [x] Followed the style guidelines of this project
- [x] Changelogs have been updated
- [ ] Unit tests have been added
- [ ] Documentation has been updated



Discussion:

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
@@ -62,18 +62,19 @@
 ---
 """
 
-import collections
-import logging
-import re
+from collections import deque
 from itertools import chain
-from typing import Iterable, Optional, Sequence, Tuple
+from json import dumps
+from logging import getLogger
+from re import IGNORECASE, UNICODE, compile
+from typing import Iterable, Optional, Sequence, Tuple, Union
 
 from prometheus_client import core
 
 from opentelemetry.sdk._metrics.export import MetricReader
 from opentelemetry.sdk._metrics.point import Gauge, Histogram, Metric, Sum
 
-_logger = logging.getLogger(__name__)
+_logger = getLogger(__name__)
 
 
 def _convert_buckets(metric: Metric) -> Sequence[Tuple[str, int]]:
@@ -123,9 +124,9 @@ class _CustomCollector:
     def __init__(self, prefix: str = ""):
         self._prefix = prefix
         self._callback = None
-        self._metrics_to_export = collections.deque()
-        self._non_letters_digits_underscore_re = re.compile(
-            r"[^\w]", re.UNICODE | re.IGNORECASE
+        self._metrics_to_export = deque()
+        self._non_letters_digits_underscore_re = compile(
+            r"[^\w]", UNICODE | IGNORECASE
         )
 
     def add_metrics_data(self, export_records: Sequence[Metric]) -> None:
@@ -157,7 +158,7 @@ def _translate_to_prometheus(
         label_keys = []
         for key, value in metric.attributes.items():
             label_keys.append(self._sanitize(key))
-            label_values.append(str(value))
+            label_values.append(self._check_value(value))
 
         metric_name = ""
         if self._prefix != "":
@@ -206,3 +207,10 @@ def _sanitize(self, key: str) -> str:
         Replace all characters other than [A-Za-z0-9_] with '_'.
         """
         return self._non_letters_digits_underscore_re.sub("_", key)
+
+    # pylint: disable=no-self-use
+    def _check_value(self, value: Union[int, float, str, Sequence]) -> str:
+        """Check the label value and return is appropriate representation"""
+        if not isinstance(value, str) and isinstance(value, Sequence):
+            return dumps(value, default=str)
+        return str(value)


Test patch:
diff --git a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py
--- a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py
+++ b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py
@@ -167,3 +167,41 @@ def test_sanitize(self):
         self.assertEqual(collector._sanitize(",./?;:[]{}"), "__________")
         self.assertEqual(collector._sanitize("TestString"), "TestString")
         self.assertEqual(collector._sanitize("aAbBcC_12_oi"), "aAbBcC_12_oi")
+
+    def test_list_labels(self):
+        labels = {"environment@": ["1", "2", "3"], "os": "Unix"}
+        record = _generate_gauge(
+            "test@gauge",
+            123,
+            attributes=labels,
+            description="testdesc",
+            unit="testunit",
+        )
+        collector = _CustomCollector("testprefix")
+        collector.add_metrics_data([record])
+
+        for prometheus_metric in collector.collect():
+            self.assertEqual(type(prometheus_metric), GaugeMetricFamily)
+            self.assertEqual(
+                prometheus_metric.name, "testprefix_test_gauge_testunit"
+            )
+            self.assertEqual(prometheus_metric.documentation, "testdesc")
+            self.assertTrue(len(prometheus_metric.samples) == 1)
+            self.assertEqual(prometheus_metric.samples[0].value, 123)
+            self.assertTrue(len(prometheus_metric.samples[0].labels) == 2)
+            self.assertEqual(
+                prometheus_metric.samples[0].labels["environment_"],
+                '["1", "2", "3"]',
+            )
+            self.assertEqual(prometheus_metric.samples[0].labels["os"], "Unix")
+
+    def test_check_value(self):
+
+        collector = _CustomCollector("")
+
+        self.assertEqual(collector._check_value(1), "1")
+        self.assertEqual(collector._check_value(1.0), "1.0")
+        self.assertEqual(collector._check_value("a"), "a")
+        self.assertEqual(collector._check_value([1, 2]), "[1, 2]")
+        self.assertEqual(collector._check_value((1, 2)), "[1, 2]")
+        self.assertEqual(collector._check_value(["a", 2]), '["a", 2]')