Repository: open-telemetry/opentelemetry-python

Issue:
`ExponentialHistogram` is missing the `to_json` method
The [`ExponentialHistorgram`](https://github.com/open-telemetry/opentelemetry-python/blob/3dfe2249cc4a203bf24578483b192fec7266596b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py#L93-L102) data point type was [somewhat recently added](https://github.com/open-telemetry/opentelemetry-python/pull/2964), and does not work with the `ConsoleMetricExporter` because it is missing a `to_json` method (which seems to be defined on the other data point types).

This is the stack trace I get locally:

```
Exception while exporting metrics 'ExponentialHistogram' object has no attribute 'to_json'
Traceback (most recent call last):
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/export/__init__.py", line 523, in _receive_metrics
    self._exporter.export(
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/export/__init__.py", line 163, in export
    self.out.write(self.formatter(metrics_data))
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/export/__init__.py", line 143, in <lambda>
    ] = lambda metrics_data: metrics_data.to_json()
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 252, in to_json
    "resource_metrics": [
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 253, in <listcomp>
    loads(resource_metrics.to_json(indent=indent))
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 233, in to_json
    "scope_metrics": [
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 234, in <listcomp>
    loads(scope_metrics.to_json(indent=indent))
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 211, in to_json
    "metrics": [
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 212, in <listcomp>
    loads(metric.to_json(indent=indent))
  File "/home/closeio/venv/lib/python3.10/site-packages/opentelemetry/sdk/metrics/_internal/point.py", line 193, in to_json
    "data": loads(self.data.to_json(indent=indent)),
AttributeError: 'ExponentialHistogram' object has no attribute 'to_json'
```


Discussion:
@ocelotl a friendly ping

(cc: @euroelessar @SaveTheRbtz)

Resolving patch:
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/point.py
@@ -101,6 +101,18 @@ class ExponentialHistogram:
         "opentelemetry.sdk.metrics.export.AggregationTemporality"
     )
 
+    def to_json(self, indent=4) -> str:
+        return dumps(
+            {
+                "data_points": [
+                    loads(data_point.to_json(indent=indent))
+                    for data_point in self.data_points
+                ],
+                "aggregation_temporality": self.aggregation_temporality,
+            },
+            indent=indent,
+        )
+
 
 @dataclass(frozen=True)
 class Sum:


Test patch:
diff --git a/opentelemetry-sdk/tests/metrics/test_point.py b/opentelemetry-sdk/tests/metrics/test_point.py
--- a/opentelemetry-sdk/tests/metrics/test_point.py
+++ b/opentelemetry-sdk/tests/metrics/test_point.py
@@ -16,6 +16,9 @@
 
 from opentelemetry.sdk.metrics.export import (
     AggregationTemporality,
+    Buckets,
+    ExponentialHistogram,
+    ExponentialHistogramDataPoint,
     Gauge,
     Histogram,
     HistogramDataPoint,
@@ -100,6 +103,22 @@ def setUpClass(cls):
         )
         cls.histogram_data_point_1_str = f'{{"attributes": {cls.attributes_1_str}, "start_time_unix_nano": 2, "time_unix_nano": 3, "count": 4, "sum": 4.4, "bucket_counts": [2, 1, 1], "explicit_bounds": [1.2, 2.3, 3.4, 4.5], "min": 0.3, "max": 4.4}}'
 
+        cls.exp_histogram_data_point_0 = ExponentialHistogramDataPoint(
+            attributes=cls.attributes_0,
+            start_time_unix_nano=1,
+            time_unix_nano=2,
+            count=1,
+            sum=10,
+            scale=1,
+            zero_count=0,
+            positive=Buckets(offset=0, bucket_counts=[1]),
+            negative=Buckets(offset=0, bucket_counts=[0]),
+            flags=0,
+            min=10,
+            max=10,
+        )
+        cls.exp_histogram_data_point_0_str = f'{{"attributes": {cls.attributes_0_str}, "start_time_unix_nano": 1, "time_unix_nano": 2, "count": 1, "sum": 10, "scale": 1, "zero_count": 0, "positive": {{"offset": 0, "bucket_counts": [1]}}, "negative": {{"offset": 0, "bucket_counts": [0]}}, "flags": 0, "min": 10, "max": 10}}'
+
         cls.sum_0 = Sum(
             data_points=[cls.number_data_point_0, cls.number_data_point_1],
             aggregation_temporality=AggregationTemporality.DELTA,
@@ -121,6 +140,14 @@ def setUpClass(cls):
         )
         cls.histogram_0_str = f'{{"data_points": [{cls.histogram_data_point_0_str}, {cls.histogram_data_point_1_str}], "aggregation_temporality": 1}}'
 
+        cls.exp_histogram_0 = ExponentialHistogram(
+            data_points=[
+                cls.exp_histogram_data_point_0,
+            ],
+            aggregation_temporality=AggregationTemporality.CUMULATIVE,
+        )
+        cls.exp_histogram_0_str = f'{{"data_points": [{cls.exp_histogram_data_point_0_str}], "aggregation_temporality": 2}}'
+
         cls.metric_0 = Metric(
             name="metric_0",
             description="description_0",
@@ -209,6 +236,15 @@ def test_histogram_data_point(self):
             self.histogram_data_point_1_str,
         )
 
+    def test_exp_histogram_data_point(self):
+
+        self.maxDiff = None
+
+        self.assertEqual(
+            self.exp_histogram_data_point_0.to_json(indent=None),
+            self.exp_histogram_data_point_0_str,
+        )
+
     def test_sum(self):
 
         self.assertEqual(self.sum_0.to_json(indent=None), self.sum_0_str)
@@ -225,6 +261,14 @@ def test_histogram(self):
             self.histogram_0.to_json(indent=None), self.histogram_0_str
         )
 
+    def test_exp_histogram(self):
+
+        self.maxDiff = None
+
+        self.assertEqual(
+            self.exp_histogram_0.to_json(indent=None), self.exp_histogram_0_str
+        )
+
     def test_metric(self):
 
         self.assertEqual(self.metric_0.to_json(indent=None), self.metric_0_str)