Skip to content

Commit e8ce689

Browse files
Add flask functional test for exemplars
1 parent 37ac4fb commit e8ce689

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from flask import Flask
16+
17+
from opentelemetry import metrics as metrics_api
18+
from opentelemetry.instrumentation.flask import FlaskInstrumentor
19+
from opentelemetry.sdk.metrics import AlwaysOnExemplarFilter
20+
from opentelemetry.test.globals_test import (
21+
reset_metrics_globals,
22+
)
23+
from opentelemetry.test.test_base import TestBase
24+
from opentelemetry.trace import (
25+
INVALID_SPAN_ID,
26+
INVALID_TRACE_ID,
27+
)
28+
29+
class TestFunctionalFlask(TestBase):
30+
def setUp(self):
31+
super().setUp()
32+
self.memory_exporter.clear()
33+
# This is done because set_meter_provider cannot override the
34+
# current meter provider.
35+
reset_metrics_globals()
36+
(
37+
self.meter_provider,
38+
self.memory_metrics_reader,
39+
) = self.create_meter_provider(
40+
exemplar_filter=AlwaysOnExemplarFilter(),
41+
)
42+
metrics_api.set_meter_provider(self.meter_provider)
43+
44+
self._app = Flask(__name__)
45+
@self._app.route("/test/")
46+
def test_route():
47+
return "Test response"
48+
49+
self._client = self._app.test_client()
50+
51+
FlaskInstrumentor().instrument_app(
52+
self._app,
53+
meter_provider=self.meter_provider,
54+
)
55+
56+
def tearDown(self):
57+
FlaskInstrumentor().uninstrument()
58+
super().tearDown()
59+
60+
def test_duration_metrics_exemplars(self):
61+
"""Should generate exemplars with trace and span IDs for Flask HTTP requests."""
62+
self._client.get("/test/")
63+
self._client.get("/test/")
64+
self._client.get("/test/")
65+
66+
metrics_data = self.memory_metrics_reader.get_metrics_data()
67+
self.assertIsNotNone(metrics_data)
68+
self.assertTrue(len(metrics_data.resource_metrics) > 0)
69+
70+
duration_metric = None
71+
metric_names = []
72+
for resource_metric in metrics_data.resource_metrics:
73+
for scope_metric in resource_metric.scope_metrics:
74+
for metric in scope_metric.metrics:
75+
metric_names.append(metric.name)
76+
if metric.name in ["http.server.request.duration", "http.server.duration"]:
77+
duration_metric = metric
78+
break
79+
if duration_metric:
80+
break
81+
if duration_metric:
82+
break
83+
84+
self.assertIsNotNone(duration_metric)
85+
data_points = list(duration_metric.data.data_points)
86+
self.assertTrue(len(data_points) > 0)
87+
88+
exemplar_count = 0
89+
for data_point in data_points:
90+
if hasattr(data_point, 'exemplars') and data_point.exemplars:
91+
for exemplar in data_point.exemplars:
92+
exemplar_count += 1
93+
# Exemplar has required fields and valid span context
94+
self.assertIsNotNone(exemplar.value)
95+
self.assertIsNotNone(exemplar.time_unix_nano)
96+
self.assertIsNotNone(exemplar.span_id)
97+
self.assertNotEqual(exemplar.span_id, INVALID_SPAN_ID)
98+
self.assertIsNotNone(exemplar.trace_id)
99+
self.assertNotEqual(exemplar.trace_id, INVALID_TRACE_ID)
100+
101+
# Trace and span ID of exemplar are part of finished spans
102+
finished_spans = self.memory_exporter.get_finished_spans()
103+
finished_span_ids = [span.context.span_id for span in finished_spans]
104+
finished_trace_ids = [span.context.trace_id for span in finished_spans]
105+
self.assertIn(exemplar.span_id, finished_span_ids)
106+
self.assertIn(exemplar.trace_id, finished_trace_ids)
107+
108+
# At least one exemplar was generated
109+
self.assertGreater(exemplar_count, 0)

tests/opentelemetry-docker-tests/tests/test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dockerpty==0.4.1
2424
docopt==0.6.2
2525
exceptiongroup==1.2.0
2626
flaky==3.7.0
27+
flask==3.0.2
2728
greenlet==3.0.3
2829
grpcio==1.63.2
2930
idna==2.10

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ deps =
10081008
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-kafka-python
10091009
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-confluent-kafka
10101010
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi
1011+
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-flask
10111012
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-mysql
10121013
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-mysqlclient
10131014
-e {toxinidir}/instrumentation/opentelemetry-instrumentation-psycopg

0 commit comments

Comments
 (0)