Skip to content

Commit cae53f8

Browse files
authored
Merge branch 'main' into audit-asyncpg
2 parents c6fbeac + 41e670a commit cae53f8

File tree

16 files changed

+1109
-105
lines changed

16 files changed

+1109
-105
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
2828
- add support to Python 3.13
2929
([#3134](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3134))
30+
- `opentelemetry-util-http` Add `py.typed` file to enable PEP 561
31+
([#3127](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3127))
3032

3133
### Fixed
3234

3335
- `opentelemetry-instrumentation-httpx` Fix `RequestInfo`/`ResponseInfo` type hints
3436
([#3105](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3105))
37+
- `opentelemetry-instrumentation-click` Disable tracing of well-known server click commands
38+
([#3174](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3174))
39+
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
40+
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
41+
42+
### Breaking changes
43+
44+
- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
45+
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))
46+
47+
### Breaking changes
48+
49+
- `opentelemetry-instrumentation-dbapi` including sqlcomment in `db.statement` span attribute value is now opt-in
50+
([#3115](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3115))
3551

3652

3753
## Version 1.29.0/0.50b0 (2024-12-11)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ The Python auto-instrumentation libraries for [OpenTelemetry](https://openteleme
4848
* [Installation](#installation)
4949
* [Releasing](#releasing)
5050
* [Releasing a package as `1.0` stable](#releasing-a-package-as-10-stable)
51+
* [Semantic Convention status of instrumentations](#semantic-convention-status-of-instrumentations)
5152
* [Contributing](#contributing)
5253
* [Thanks to all the people who already contributed](#thanks-to-all-the-people-who-already-contributed)
5354

@@ -100,7 +101,7 @@ To release a package as `1.0` stable, the package:
100101

101102
## Semantic Convention status of instrumentations
102103

103-
In our efforts to maintain optimal user experience and prevent breaking changes for transitioning into stable semantic conventions, OpenTelemetry Python is adopting the [semantic convention migration plan](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/migration-guide.md) for several instrumentations. Currently this plan is only being adopted for HTTP-related instrumentations, but will eventually cover all types. Please refer to the `semconv status` column of the [instrumentation README](instrumentation/README.md) of the current status of instrumentations' semantic conventions. The possible values are `experimental`, `stable` and `migration` referring to [status](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/document-status.md#lifecycle-status) of that particular semantic convention. `Migration` refers to an instrumentation that currently supports the migration plan.
104+
In our efforts to maintain optimal user experience and prevent breaking changes for transitioning into stable semantic conventions, OpenTelemetry Python is adopting the semantic convention migration plan for several instrumentations. Currently this plan is only being adopted for [HTTP-related instrumentations](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md), but will eventually cover all types. Please refer to the `semconv status` column of the [instrumentation README](instrumentation/README.md) of the current status of instrumentations' semantic conventions. The possible values are `experimental`, `stable` and `migration` referring to [status](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.31.0/specification/document-status.md#lifecycle-status) of that particular semantic convention. `Migration` refers to an instrumentation that currently supports the migration plan.
104105

105106
## Contributing
106107

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
# limitations under the License.
1414

1515
"""
16-
Instrument `click`_ CLI applications.
16+
Instrument `click`_ CLI applications. The instrumentor will avoid instrumenting
17+
well-known servers (e.g. *flask run* and *uvicorn*) to avoid unexpected effects
18+
like every request having the same Trace ID.
1719
1820
.. _click: https://pypi.org/project/click/
1921
@@ -47,6 +49,12 @@ def hello():
4749
import click
4850
from wrapt import wrap_function_wrapper
4951

52+
try:
53+
from flask.cli import ScriptInfo as FlaskScriptInfo
54+
except ImportError:
55+
FlaskScriptInfo = None
56+
57+
5058
from opentelemetry import trace
5159
from opentelemetry.instrumentation.click.package import _instruments
5260
from opentelemetry.instrumentation.click.version import __version__
@@ -66,6 +74,20 @@ def hello():
6674
_logger = getLogger(__name__)
6775

6876

77+
def _skip_servers(ctx: click.Context):
78+
# flask run
79+
if (
80+
ctx.info_name == "run"
81+
and FlaskScriptInfo
82+
and isinstance(ctx.obj, FlaskScriptInfo)
83+
):
84+
return True
85+
# uvicorn
86+
if ctx.info_name == "uvicorn":
87+
return True
88+
return False
89+
90+
6991
def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
7092
# Subclasses of Command include groups and CLI runners, but
7193
# we only want to instrument the actual commands which are
@@ -74,6 +96,12 @@ def _command_invoke_wrapper(wrapped, instance, args, kwargs, tracer):
7496
return wrapped(*args, **kwargs)
7597

7698
ctx = args[0]
99+
100+
# we don't want to create a root span for long running processes like servers
101+
# otherwise all requests would have the same trace id
102+
if _skip_servers(ctx):
103+
return wrapped(*args, **kwargs)
104+
77105
span_name = ctx.info_name
78106
span_attributes = {
79107
PROCESS_COMMAND_ARGS: sys.argv,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
asgiref==3.8.1
2+
blinker==1.7.0
23
click==8.1.7
34
Deprecated==1.2.14
5+
Flask==3.0.2
46
iniconfig==2.0.0
7+
itsdangerous==2.1.2
8+
Jinja2==3.1.4
9+
MarkupSafe==2.1.2
510
packaging==24.0
611
pluggy==1.5.0
712
py-cpuinfo==9.0.0
813
pytest==7.4.4
914
pytest-asyncio==0.23.5
1015
tomli==2.0.1
1116
typing_extensions==4.12.2
17+
Werkzeug==3.0.6
1218
wrapt==1.16.0
1319
zipp==3.19.2
1420
-e opentelemetry-instrumentation
1521
-e instrumentation/opentelemetry-instrumentation-click
22+
-e instrumentation/opentelemetry-instrumentation-flask
23+
-e instrumentation/opentelemetry-instrumentation-wsgi
24+
-e util/opentelemetry-util-http

instrumentation/opentelemetry-instrumentation-click/tests/test_click.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
from unittest import mock
1717

1818
import click
19+
import pytest
1920
from click.testing import CliRunner
2021

22+
try:
23+
from flask import cli as flask_cli
24+
except ImportError:
25+
flask_cli = None
26+
2127
from opentelemetry.instrumentation.click import ClickInstrumentor
2228
from opentelemetry.test.test_base import TestBase
2329
from opentelemetry.trace import SpanKind
@@ -60,7 +66,7 @@ def command():
6066
)
6167

6268
@mock.patch("sys.argv", ["flask", "command"])
63-
def test_flask_run_command_wrapping(self):
69+
def test_flask_command_wrapping(self):
6470
@click.command()
6571
def command():
6672
pass
@@ -162,6 +168,27 @@ def command_raises():
162168
},
163169
)
164170

171+
def test_uvicorn_cli_command_ignored(self):
172+
@click.command("uvicorn")
173+
def command_uvicorn():
174+
pass
175+
176+
runner = CliRunner()
177+
result = runner.invoke(command_uvicorn)
178+
self.assertEqual(result.exit_code, 0)
179+
180+
self.assertFalse(self.memory_exporter.get_finished_spans())
181+
182+
@pytest.mark.skipif(flask_cli is None, reason="requires flask")
183+
def test_flask_run_command_ignored(self):
184+
runner = CliRunner()
185+
result = runner.invoke(
186+
flask_cli.run_command, obj=flask_cli.ScriptInfo()
187+
)
188+
self.assertEqual(result.exit_code, 2)
189+
190+
self.assertFalse(self.memory_exporter.get_finished_spans())
191+
165192
def test_uninstrument(self):
166193
ClickInstrumentor().uninstrument()
167194

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

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def trace_integration(
7171
capture_parameters: bool = False,
7272
enable_commenter: bool = False,
7373
db_api_integration_factory=None,
74+
enable_attribute_commenter: bool = False,
7475
):
7576
"""Integrate with DB API library.
7677
https://www.python.org/dev/peps/pep-0249/
@@ -88,6 +89,7 @@ def trace_integration(
8889
enable_commenter: Flag to enable/disable sqlcommenter.
8990
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
9091
default one is used.
92+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
9193
"""
9294
wrap_connect(
9395
__name__,
@@ -100,6 +102,7 @@ def trace_integration(
100102
capture_parameters=capture_parameters,
101103
enable_commenter=enable_commenter,
102104
db_api_integration_factory=db_api_integration_factory,
105+
enable_attribute_commenter=enable_attribute_commenter,
103106
)
104107

105108

@@ -115,6 +118,7 @@ def wrap_connect(
115118
enable_commenter: bool = False,
116119
db_api_integration_factory=None,
117120
commenter_options: dict = None,
121+
enable_attribute_commenter: bool = False,
118122
):
119123
"""Integrate with DB API library.
120124
https://www.python.org/dev/peps/pep-0249/
@@ -133,6 +137,7 @@ def wrap_connect(
133137
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
134138
default one is used.
135139
commenter_options: Configurations for tags to be appended at the sql query.
140+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
136141
137142
"""
138143
db_api_integration_factory = (
@@ -156,6 +161,7 @@ def wrap_connect_(
156161
enable_commenter=enable_commenter,
157162
commenter_options=commenter_options,
158163
connect_module=connect_module,
164+
enable_attribute_commenter=enable_attribute_commenter,
159165
)
160166
return db_integration.wrapped_connection(wrapped, args, kwargs)
161167

@@ -191,6 +197,7 @@ def instrument_connection(
191197
enable_commenter: bool = False,
192198
commenter_options: dict = None,
193199
connect_module: typing.Callable[..., typing.Any] = None,
200+
enable_attribute_commenter: bool = False,
194201
):
195202
"""Enable instrumentation in a database connection.
196203
@@ -206,6 +213,7 @@ def instrument_connection(
206213
enable_commenter: Flag to enable/disable sqlcommenter.
207214
commenter_options: Configurations for tags to be appended at the sql query.
208215
connect_module: Module name where connect method is available.
216+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
209217
210218
Returns:
211219
An instrumented connection.
@@ -224,6 +232,7 @@ def instrument_connection(
224232
enable_commenter=enable_commenter,
225233
commenter_options=commenter_options,
226234
connect_module=connect_module,
235+
enable_attribute_commenter=enable_attribute_commenter,
227236
)
228237
db_integration.get_connection_attributes(connection)
229238
return get_traced_connection_proxy(connection, db_integration)
@@ -257,6 +266,7 @@ def __init__(
257266
enable_commenter: bool = False,
258267
commenter_options: dict = None,
259268
connect_module: typing.Callable[..., typing.Any] = None,
269+
enable_attribute_commenter: bool = False,
260270
):
261271
self.connection_attributes = connection_attributes
262272
if self.connection_attributes is None:
@@ -277,6 +287,7 @@ def __init__(
277287
self.capture_parameters = capture_parameters
278288
self.enable_commenter = enable_commenter
279289
self.commenter_options = commenter_options
290+
self.enable_attribute_commenter = enable_attribute_commenter
280291
self.database_system = database_system
281292
self.connection_props = {}
282293
self.span_attributes = {}
@@ -434,9 +445,52 @@ def __init__(self, db_api_integration: DatabaseApiIntegration) -> None:
434445
if self._db_api_integration.commenter_options
435446
else {}
436447
)
448+
self._enable_attribute_commenter = (
449+
self._db_api_integration.enable_attribute_commenter
450+
)
437451
self._connect_module = self._db_api_integration.connect_module
438452
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
439453

454+
def _capture_mysql_version(self, cursor) -> None:
455+
"""Lazy capture of mysql-connector client version using cursor, if applicable"""
456+
if (
457+
self._db_api_integration.database_system == "mysql"
458+
and self._db_api_integration.connect_module.__name__
459+
== "mysql.connector"
460+
and not self._db_api_integration.commenter_data[
461+
"mysql_client_version"
462+
]
463+
):
464+
self._db_api_integration.commenter_data["mysql_client_version"] = (
465+
cursor._cnx._cmysql.get_client_info()
466+
)
467+
468+
def _get_commenter_data(self) -> dict:
469+
"""Uses DB-API integration to return commenter data for sqlcomment"""
470+
commenter_data = dict(self._db_api_integration.commenter_data)
471+
if self._commenter_options.get("opentelemetry_values", True):
472+
commenter_data.update(**_get_opentelemetry_values())
473+
return {
474+
k: v
475+
for k, v in commenter_data.items()
476+
if self._commenter_options.get(k, True)
477+
}
478+
479+
def _update_args_with_added_sql_comment(self, args, cursor) -> tuple:
480+
"""Updates args with cursor info and adds sqlcomment to query statement"""
481+
try:
482+
args_list = list(args)
483+
self._capture_mysql_version(cursor)
484+
commenter_data = self._get_commenter_data()
485+
statement = _add_sql_comment(args_list[0], **commenter_data)
486+
args_list[0] = statement
487+
args = tuple(args_list)
488+
except Exception as exc: # pylint: disable=broad-except
489+
_logger.exception(
490+
"Exception while generating sql comment: %s", exc
491+
)
492+
return args
493+
440494
def _populate_span(
441495
self,
442496
span: trace_api.Span,
@@ -497,52 +551,22 @@ def traced_execution(
497551
) as span:
498552
if span.is_recording():
499553
if args and self._commenter_enabled:
500-
try:
501-
args_list = list(args)
502-
503-
# lazy capture of mysql-connector client version using cursor
504-
if (
505-
self._db_api_integration.database_system == "mysql"
506-
and self._db_api_integration.connect_module.__name__
507-
== "mysql.connector"
508-
and not self._db_api_integration.commenter_data[
509-
"mysql_client_version"
510-
]
511-
):
512-
self._db_api_integration.commenter_data[
513-
"mysql_client_version"
514-
] = cursor._cnx._cmysql.get_client_info()
515-
516-
commenter_data = dict(
517-
self._db_api_integration.commenter_data
518-
)
519-
if self._commenter_options.get(
520-
"opentelemetry_values", True
521-
):
522-
commenter_data.update(
523-
**_get_opentelemetry_values()
524-
)
525-
526-
# Filter down to just the requested attributes.
527-
commenter_data = {
528-
k: v
529-
for k, v in commenter_data.items()
530-
if self._commenter_options.get(k, True)
531-
}
532-
statement = _add_sql_comment(
533-
args_list[0], **commenter_data
554+
if self._enable_attribute_commenter:
555+
# sqlcomment is added to executed query and db.statement span attribute
556+
args = self._update_args_with_added_sql_comment(
557+
args, cursor
534558
)
535-
536-
args_list[0] = statement
537-
args = tuple(args_list)
538-
539-
except Exception as exc: # pylint: disable=broad-except
540-
_logger.exception(
541-
"Exception while generating sql comment: %s", exc
559+
self._populate_span(span, cursor, *args)
560+
else:
561+
# sqlcomment is only added to executed query
562+
# so db.statement is set before add_sql_comment
563+
self._populate_span(span, cursor, *args)
564+
args = self._update_args_with_added_sql_comment(
565+
args, cursor
542566
)
543-
544-
self._populate_span(span, cursor, *args)
545-
567+
else:
568+
# no sqlcomment anywhere
569+
self._populate_span(span, cursor, *args)
546570
return query_method(*args, **kwargs)
547571

548572

0 commit comments

Comments
 (0)