Skip to content

Commit 8edce48

Browse files
authored
Merge branch 'main' into add-type-hints-to-httpx
2 parents 082fecc + 0ad779a commit 8edce48

File tree

19 files changed

+1190
-115
lines changed

19 files changed

+1190
-115
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-asyncpg/tests/test_asyncpg_wrapper.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from asyncpg import Connection, Record, cursor
66
from wrapt import ObjectProxy
77

8+
from opentelemetry import trace as trace_api
89
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
910
from opentelemetry.test.test_base import TestBase
1011

@@ -105,3 +106,36 @@ async def exec_mock(*args, **kwargs):
105106
spans = self.memory_exporter.get_finished_spans()
106107
self.assertEqual(len(spans), 2)
107108
self.assertEqual([span.status.is_ok for span in spans], [True, True])
109+
110+
def test_no_op_tracer_provider(self):
111+
AsyncPGInstrumentor().uninstrument()
112+
AsyncPGInstrumentor().instrument(
113+
tracer_provider=trace_api.NoOpTracerProvider()
114+
)
115+
116+
# Mock out all interaction with postgres
117+
async def bind_mock(*args, **kwargs):
118+
return []
119+
120+
async def exec_mock(*args, **kwargs):
121+
return [], None, True
122+
123+
conn = mock.Mock()
124+
conn.is_closed = lambda: False
125+
126+
conn._protocol = mock.Mock()
127+
conn._protocol.bind = bind_mock
128+
conn._protocol.execute = exec_mock
129+
conn._protocol.bind_execute = exec_mock
130+
conn._protocol.close_portal = bind_mock
131+
132+
state = mock.Mock()
133+
state.closed = False
134+
135+
# init the cursor and fetch a single record
136+
crs = cursor.Cursor(conn, "SELECT * FROM test", state, [], Record)
137+
asyncio.run(crs._init(1))
138+
asyncio.run(crs.fetch(1))
139+
140+
spans = self.memory_exporter.get_finished_spans()
141+
self.assertEqual(len(spans), 0)

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

0 commit comments

Comments
 (0)