Skip to content

Commit 94aca0e

Browse files
Merge branch 'main' into sqlalchemy-db-statement-with-sqlcomment
2 parents b1fbeb7 + 07c3324 commit 94aca0e

24 files changed

+3129
-351
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
([#2922](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2922))
3737
- `opentelemetry-instrumentation-celery` Don't detach context without a None token
3838
([#2927](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2927))
39+
- `opentelemetry-exporter-prometheus-remote-write`: sort labels before exporting
40+
([#2940](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2940))
41+
- `opentelemetry-instrumentation-dbapi` sqlcommenter key values created from PostgreSQL, MySQL systems
42+
([#2897](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2897))
3943

4044
### Breaking changes
4145

exporter/opentelemetry-exporter-prometheus-remote-write/src/opentelemetry/exporter/prometheus_remote_write/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import re
1717
from collections import defaultdict
1818
from itertools import chain
19-
from typing import Dict, Sequence
19+
from typing import Dict, Mapping, Sequence
2020

2121
import requests
2222
import snappy
@@ -253,12 +253,14 @@ def _parse_metric(
253253
return self._convert_to_timeseries(sample_sets, resource_labels)
254254

255255
def _convert_to_timeseries(
256-
self, sample_sets: Sequence[tuple], resource_labels: Sequence
256+
self, sample_sets: Mapping[tuple, Sequence], resource_labels: Sequence
257257
) -> Sequence[TimeSeries]:
258258
timeseries = []
259259
for labels, samples in sample_sets.items():
260260
ts = TimeSeries()
261-
for label_name, label_value in chain(resource_labels, labels):
261+
for label_name, label_value in sorted(
262+
chain(resource_labels, labels)
263+
):
262264
# Previous implementation did not str() the names...
263265
ts.labels.append(self._label(label_name, str(label_value)))
264266
for value, timestamp in samples:

exporter/opentelemetry-exporter-prometheus-remote-write/tests/test_prometheus_remote_write_exporter.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
PrometheusRemoteWriteMetricsExporter,
2323
)
2424
from opentelemetry.exporter.prometheus_remote_write.gen.types_pb2 import ( # pylint: disable=E0611
25+
Label,
26+
Sample,
2527
TimeSeries,
2628
)
2729
from opentelemetry.sdk.metrics.export import (
@@ -155,6 +157,38 @@ def test_parse_metric(metric, prom_rw):
155157
assert sample.value in values
156158

157159

160+
def test_convert_to_timeseries(prom_rw):
161+
resource_labels = (("service_name", "foo"), ("bool_value", True))
162+
sample_sets = {
163+
(("foo", "bar"), ("baz", 42), ("__name__", "test_histogram_tu")): [
164+
(1, 1641946016139)
165+
],
166+
(("baz", "42"), ("foo", "bar")): [(4, 1641946016139)],
167+
}
168+
timeseries = prom_rw._convert_to_timeseries(sample_sets, resource_labels)
169+
assert timeseries == [
170+
TimeSeries(
171+
labels=[
172+
Label(name="__name__", value="test_histogram_tu"),
173+
Label(name="baz", value="42"),
174+
Label(name="bool_value", value="True"),
175+
Label(name="foo", value="bar"),
176+
Label(name="service_name", value="foo"),
177+
],
178+
samples=[Sample(value=1, timestamp=1641946016139)],
179+
),
180+
TimeSeries(
181+
labels=[
182+
Label(name="baz", value="42"),
183+
Label(name="bool_value", value="True"),
184+
Label(name="foo", value="bar"),
185+
Label(name="service_name", value="foo"),
186+
],
187+
samples=[Sample(value=4, timestamp=1641946016139)],
188+
),
189+
]
190+
191+
158192
class TestValidation(unittest.TestCase):
159193
# Test cases to ensure exporter parameter validation works as intended
160194
def test_valid_standard_param(self):

instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@
5353
)
5454
from opentelemetry.semconv.trace import SpanAttributes
5555
from opentelemetry.trace import SpanKind, TracerProvider, get_tracer
56+
from opentelemetry.util._importlib_metadata import version as util_version
57+
58+
_DB_DRIVER_ALIASES = {
59+
"MySQLdb": "mysqlclient",
60+
}
5661

5762
_logger = logging.getLogger(__name__)
5863

@@ -275,6 +280,70 @@ def __init__(
275280
self.name = ""
276281
self.database = ""
277282
self.connect_module = connect_module
283+
self.commenter_data = self.calculate_commenter_data()
284+
285+
def _get_db_version(
286+
self,
287+
db_driver,
288+
):
289+
if db_driver in _DB_DRIVER_ALIASES:
290+
return util_version(_DB_DRIVER_ALIASES[db_driver])
291+
db_version = ""
292+
try:
293+
db_version = self.connect_module.__version__
294+
except AttributeError:
295+
db_version = "unknown"
296+
return db_version
297+
298+
def calculate_commenter_data(
299+
self,
300+
):
301+
commenter_data = {}
302+
if not self.enable_commenter:
303+
return commenter_data
304+
305+
db_driver = getattr(self.connect_module, "__name__", "unknown")
306+
db_version = self._get_db_version(db_driver)
307+
308+
commenter_data = {
309+
"db_driver": f"{db_driver}:{db_version.split(' ')[0]}",
310+
# PEP 249-compliant drivers should have the following attributes.
311+
# We can assume apilevel "1.0" if not given.
312+
# We use "unknown" for others to prevent uncaught AttributeError.
313+
# https://peps.python.org/pep-0249/#globals
314+
"dbapi_threadsafety": getattr(
315+
self.connect_module, "threadsafety", "unknown"
316+
),
317+
"dbapi_level": getattr(self.connect_module, "apilevel", "1.0"),
318+
"driver_paramstyle": getattr(
319+
self.connect_module, "paramstyle", "unknown"
320+
),
321+
}
322+
323+
if self.database_system == "postgresql":
324+
if hasattr(self.connect_module, "__libpq_version__"):
325+
libpq_version = self.connect_module.__libpq_version__
326+
else:
327+
libpq_version = self.connect_module.pq.__build_version__
328+
commenter_data.update(
329+
{
330+
"libpq_version": libpq_version,
331+
}
332+
)
333+
elif self.database_system == "mysql":
334+
mysqlc_version = ""
335+
if db_driver == "MySQLdb":
336+
mysqlc_version = self.connect_module._mysql.get_client_info()
337+
elif db_driver == "pymysql":
338+
mysqlc_version = self.connect_module.get_client_info()
339+
340+
commenter_data.update(
341+
{
342+
"mysql_client_version": mysqlc_version,
343+
}
344+
)
345+
346+
return commenter_data
278347

279348
def wrapped_connection(
280349
self,
@@ -427,21 +496,23 @@ def traced_execution(
427496
if args and self._commenter_enabled:
428497
try:
429498
args_list = list(args)
430-
if hasattr(self._connect_module, "__libpq_version__"):
431-
libpq_version = self._connect_module.__libpq_version__
432-
else:
433-
libpq_version = (
434-
self._connect_module.pq.__build_version__
435-
)
436499

437-
commenter_data = {
438-
# Psycopg2/framework information
439-
"db_driver": f"psycopg2:{self._connect_module.__version__.split(' ')[0]}",
440-
"dbapi_threadsafety": self._connect_module.threadsafety,
441-
"dbapi_level": self._connect_module.apilevel,
442-
"libpq_version": libpq_version,
443-
"driver_paramstyle": self._connect_module.paramstyle,
444-
}
500+
# lazy capture of mysql-connector client version using cursor
501+
if (
502+
self._db_api_integration.database_system == "mysql"
503+
and self._db_api_integration.connect_module.__name__
504+
== "mysql.connector"
505+
and not self._db_api_integration.commenter_data[
506+
"mysql_client_version"
507+
]
508+
):
509+
self._db_api_integration.commenter_data[
510+
"mysql_client_version"
511+
] = cursor._cnx._cmysql.get_client_info()
512+
513+
commenter_data = dict(
514+
self._db_api_integration.commenter_data
515+
)
445516
if self._commenter_options.get(
446517
"opentelemetry_values", True
447518
):

0 commit comments

Comments
 (0)