Compare commits

...

22 Commits

Author SHA1 Message Date
OpenTelemetry Bot 109173fec7
Update version to 1.36.0.dev/0.57b0.dev (#3633) 2025-07-11 14:35:47 +02:00
Emídio Neto b63ca133be
Update CHANGELOG.md (#3632) 2025-07-11 13:33:10 +02:00
OpenTelemetry Bot 1f78c8acff
Sort contributor listings and remove affiliation from emeriti (#3627)
Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-07-11 09:20:33 +00:00
Mike Dearman 49fa53131d
Add tornado WebSocketHandler instrumentation support (#3498)
* Add tornado WebSocketHandler instrumentation support. (https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2761)

* Linting

* Update CHANGELOG.md

* Apply refactor changes from #3582

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-07-10 17:41:36 +02:00
OpenTelemetry Bot 333fc5dcb4
Update opentelemetry-instrumentation-google-genai version to v0.4b0 (#3619) 2025-07-08 14:46:08 -04:00
warmagedon007 80c357bb16
pika: added instrumentation for pika.connection.Connection and pika.c… (#3584)
* pika: added instrumentation for pika.connection.Connection and pika.channel.Channel, thus added instrumentation support to all SelectConnection adapters.

* updated changelog.

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-07-07 08:17:31 +00:00
Prajna Paramita Biswal 3c4d18cc13
ref(mysql): remove SpanAttributes (#3616)
* ref(mysql): remove SpanAttributes

* fix imports
2025-07-07 09:43:47 +02:00
Radoslav Kirilov b74633a552
feat(pymongo): aggregate and getMore capture statements support (#3601)
* feat(pymongo): aggregate and getMore capture statements

* chore: update changelog

* fix: tests

* fix: proper MockCommand and expectations

* chore: ruff-format
2025-07-02 07:23:51 +00:00
Carlos RC b1c2c7941b
fix excluded_urls in instrumentation-asgi (#3567)
* fix excluded_urls in instrumentation-asgi

* fix ExcludeList or str

* chhangelog

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-07-01 12:33:01 +00:00
rads-1996 b69ebb7224
Redact specific url query string values and url credentials in instrumentations (#3508)
* Updated the instrumentation with aiohttp-server tests for url redaction

* Updated the aiohttp-server implementation and the query redaction logic

* Updated changelog and moved change to unreleased. Updated test files with license header

* Improved formatting

* Fixed failing tests

* Fixed ruff

* Update util/opentelemetry-util-http/src/opentelemetry/util/http/__init__.py

Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>

---------

Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
2025-07-01 12:26:47 +00:00
Andre Murbach Maidl 6977da3893
Improve asgi instrumentation example (#3576)
* Improve asgi instrumentation example

* Fix indentation of asgi instrumentation example

* Improve type hints of asgi instrumentation example

* Ignore pylint too-many-lines

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-06-30 11:03:50 +02:00
Ezzio Moreira ca079cbc56
feat: change semcov util/opentelemetry-util-http (#3592) 2025-06-30 08:21:28 +00:00
OpenTelemetry Bot 40d8942bf5
Update community member listings (#3595)
* Update community member listings

* Fix outdated community membership link

---------

Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com>
2025-06-30 08:14:08 +00:00
Emídio Neto 78300e9642
Update CHANGELOG.md (#3593) 2025-06-26 12:01:51 +02:00
shwejan raj c4347e027c
adds NoOpTracerProvider test case for falcon instrumentation (#3382)
Signed-off-by: Shwejan Raj, Bhupathi <Bhupathi.ShwejanRaj@fmr.com>
Signed-off-by: Bhupathi Shwejan Raj <Bhupathi.ShwejanRaj@fmr.com>
2025-06-17 16:16:26 +00:00
f-kanari-safie 0a03c9abf2
refactor(tornado): replaces SpanAttributes by semconv attributes (#3582)
* refactor: fix import paths

* fix imports

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-06-16 12:09:38 +00:00
Andre Murbach Maidl 85dbfe520a
Improve grpc instrumentation examples (#3575) 2025-06-16 09:31:12 +00:00
Andre Murbach Maidl b27225273b
Improve wsgi instrumenation example (#3577) 2025-06-16 09:06:34 +00:00
Andre Murbach Maidl 591051f8bb
Improve pyramid instrumentation example (#3578)
Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
2025-06-16 08:42:59 +00:00
Riccardo Magliocchetti 50cdeeee12
resource-detector-containerid: demote failure to read cgroup files to debug (#3579)
* resource-detector-containerid: demote failure to read cgroup files to debug

Make the detection more quiet so we can load it on application not
running on linux machines without spamming logs.

* Add changelog
2025-06-16 10:33:16 +02:00
Riccardo Magliocchetti 04f8899252
docs: update redis-py documentation link (#3581) 2025-06-13 10:01:16 -03:00
OpenTelemetry Bot 59cc34e9f3
Update version to 1.35.0.dev/0.56b0.dev (#3556) 2025-06-04 16:53:03 +02:00
156 changed files with 1280 additions and 559 deletions

View File

@ -11,10 +11,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
## Version 1.35.0/0.56b0 (2025-07-11)
### Added
- `opentelemetry-instrumentation-aiokafka` Add instrumentation of `consumer.getmany` (batch)
([#3257](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3257))
- `opentelemetry-instrumentation-pika` Added instrumentation for All `SelectConnection` adapters
([#3584](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3584))
- `opentelemetry-instrumentation-tornado` Add support for `WebSocketHandler` instrumentation
([#3498](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3498))
- `opentelemetry-util-http` Added support for redacting specific url query string values and url credentials in instrumentations
([#3508](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3508))
- `opentelemetry-instrumentation-pymongo` `aggregate` and `getMore` capture statements support
([#3601](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3601))
### Fixed
- `opentelemetry-instrumentation-asgi`: fix excluded_urls in instrumentation-asgi
([#3567](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3567))
- `opentelemetry-resource-detector-containerid`: make it more quiet on platforms without cgroups
([#3579](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3579))
## Version 1.34.0/0.55b0 (2025-06-04)
### Fixed
@ -71,7 +88,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3513](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3513))
- `opentelemetry-instrumentation` Allow re-raising exception when instrumentation fails
([#3545](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3545))
- `opentelemetry-instrumentation-aiokafka` Add instrumentation of `consumer.getmany` (batch)
([#3257](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3257))
### Deprecated
- Drop support for Python 3.8, bump baseline to Python 3.9.

View File

@ -111,7 +111,16 @@ We meet weekly on Thursday at 9AM PT. The meeting is subject to change depending
Meeting notes are available as a public [Google doc](https://docs.google.com/document/d/1CIMGoIOZ-c3-igzbd6_Pnxx1SjAkjwqoYSUWxPY8XIs/edit). For edit access, get in touch on [GitHub Discussions](https://github.com/open-telemetry/opentelemetry-python/discussions).
Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telemetry/teams/python-approvers)):
### Maintainers
- [Aaron Abbott](https://github.com/aabmass), Google
- [Leighton Chen](https://github.com/lzchen), Microsoft
- [Riccardo Magliocchetti](https://github.com/xrmx), Elastic
- [Shalev Roda](https://github.com/shalevr), Cisco
For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer).
### Approvers
- [Emídio Neto](https://github.com/emdneto), PicPay
- [Jeremy Voss](https://github.com/jeremydvoss), Microsoft
@ -121,34 +130,29 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem
- [Srikanth Chekuri](https://github.com/srikanthccv), signoz.io
- [Tammy Baylis](https://github.com/tammy-baylis-swi), SolarWinds
Emeritus Approvers:
For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver).
- [Ashutosh Goel](https://github.com/ashu658), Cisco
- [Héctor Hernández](https://github.com/hectorhdzg), Microsoft
- [Nikolay Sokolik](https://github.com/oxeye-nikolay), Oxeye
- [Nikolay Sokolik](https://github.com/nikosokolik), Oxeye
- [Nathaniel Ruiz Nowell](https://github.com/NathanielRN), AWS
### Emeritus Maintainers
*Find more about the approver role in [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver).*
- [Alex Boten](https://github.com/codeboten)
- [Diego Hurtado](https://github.com/ocelotl)
- [Owais Lone](https://github.com/owais)
- [Yusuke Tsutsumi](https://github.com/toumorokoshi)
Maintainers ([@open-telemetry/python-maintainers](https://github.com/orgs/open-telemetry/teams/python-maintainers)):
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
- [Aaron Abbott](https://github.com/aabmass), Google
- [Leighton Chen](https://github.com/lzchen), Microsoft
- [Riccardo Magliocchetti](https://github.com/xrmx), Elastic
- [Shalev Roda](https://github.com/shalevr), Cisco
### Emeritus Approvers
Emeritus Maintainers:
- [Ashutosh Goel](https://github.com/ashu658)
- [Héctor Hernández](https://github.com/hectorhdzg)
- [Nathaniel Ruiz Nowell](https://github.com/NathanielRN)
- [Nikolay Sokolik](https://github.com/nikosokolik)
- [Nikolay Sokolik](https://github.com/oxeye-nikolay)
- [Alex Boten](https://github.com/codeboten), Lightstep
- [Diego Hurtado](https://github.com/ocelotl), Lightstep
- [Owais Lone](https://github.com/owais), Splunk
- [Yusuke Tsutsumi](https://github.com/toumorokoshi), Google
For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager).
*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer).*
### Thanks to all the people who already contributed
### Thanks to all of our contributors!
<a href="https://github.com/open-telemetry/opentelemetry-python-contrib/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=open-telemetry/opentelemetry-python-contrib" />
<img alt="Repo contributors" src="https://contrib.rocks/image?repo=open-telemetry/opentelemetry-python-contrib" />
</a>

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -122,7 +122,7 @@ intersphinx_mapping = {
"https://opentelemetry-python.readthedocs.io/en/latest/",
None,
),
"redis": ("https://redis-py.readthedocs.io/en/latest/", None),
"redis": ("https://redis.readthedocs.io/en/latest/", None),
}
# http://www.sphinx-doc.org/en/master/config.html#confval-nitpicky

View File

@ -16,7 +16,7 @@ sortfirst=
ext/*
[stable]
version=1.34.0.dev
version=1.36.0.dev
packages=
opentelemetry-sdk
@ -34,7 +34,7 @@ packages=
opentelemetry-api
[prerelease]
version=0.55b0.dev
version=0.57b0.dev
packages=
all

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -27,7 +27,7 @@ classifiers = [
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-sdk ~= 1.12",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"rich>=10.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
## Version 0.3b0 (2025-07-08)
- Add automatic instrumentation to tool call functions ([#3446](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3446))
## Version 0.2b0 (2025-04-28)

View File

@ -17,4 +17,4 @@
# This version should stay below "1.0" until the fundamentals
# in "TODOS.md" have been addressed. Please revisit the TODOs
# listed there before bumping to a stable version.
__version__ = "0.3b0.dev"
__version__ = "0.4b0.dev"

View File

@ -26,7 +26,7 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.5",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,9 +26,9 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -135,7 +135,7 @@ from opentelemetry.semconv.metrics.http_metrics import (
)
from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import remove_url_credentials, sanitize_method
from opentelemetry.util.http import redact_url, sanitize_method
_UrlFilterT = typing.Optional[typing.Callable[[yarl.URL], str]]
_RequestHookT = typing.Optional[
@ -311,9 +311,9 @@ def create_trace_config(
method = params.method
request_span_name = _get_span_name(method)
request_url = (
remove_url_credentials(trace_config_ctx.url_filter(params.url))
redact_url(trace_config_ctx.url_filter(params.url))
if callable(trace_config_ctx.url_filter)
else remove_url_credentials(str(params.url))
else redact_url(str(params.url))
)
span_attributes = {}

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -762,16 +762,16 @@ class TestAioHttpIntegration(TestBase):
)
self.memory_exporter.clear()
def test_credential_removal(self):
def test_remove_sensitive_params(self):
trace_configs = [aiohttp_client.create_trace_config()]
app = HttpServerMock("test_credential_removal")
app = HttpServerMock("test_remove_sensitive_params")
@app.route("/status/200")
def index():
return "hello"
url = "http://username:password@localhost:5000/status/200"
url = "http://username:password@localhost:5000/status/200?Signature=secret"
with app.run("localhost", 5000):
with self.subTest(url=url):
@ -793,7 +793,9 @@ class TestAioHttpIntegration(TestBase):
(StatusCode.UNSET, None),
{
HTTP_METHOD: "GET",
HTTP_URL: ("http://localhost:5000/status/200"),
HTTP_URL: (
"http://REDACTED:REDACTED@localhost:5000/status/200?Signature=REDACTED"
),
HTTP_STATUS_CODE: int(HTTPStatus.OK),
},
)

View File

@ -26,9 +26,9 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -72,7 +72,7 @@ from opentelemetry.semconv._incubating.attributes.net_attributes import (
)
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import get_excluded_urls, remove_url_credentials
from opentelemetry.util.http import get_excluded_urls, redact_url
_duration_attrs = [
HTTP_METHOD,
@ -148,6 +148,7 @@ def collect_request_attributes(request: web.Request) -> Dict:
request.url.port,
str(request.url),
)
query_string = request.query_string
if query_string and http_url:
if isinstance(query_string, bytes):
@ -161,7 +162,7 @@ def collect_request_attributes(request: web.Request) -> Dict:
HTTP_ROUTE: _get_view_func(request),
HTTP_FLAVOR: f"{request.version.major}.{request.version.minor}",
HTTP_TARGET: request.path,
HTTP_URL: remove_url_credentials(http_url),
HTTP_URL: redact_url(http_url),
}
http_method = request.method

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -152,3 +152,46 @@ async def test_suppress_instrumentation(
await client.get("/test-path")
assert len(memory_exporter.get_finished_spans()) == 0
@pytest.mark.asyncio
async def test_remove_sensitive_params(tracer, aiohttp_server):
"""Test that sensitive information in URLs is properly redacted."""
_, memory_exporter = tracer
# Set up instrumentation
AioHttpServerInstrumentor().instrument()
# Create app with test route
app = aiohttp.web.Application()
async def handler(request):
return aiohttp.web.Response(text="hello")
app.router.add_get("/status/200", handler)
# Start the server
server = await aiohttp_server(app)
# Make request with sensitive data in URL
url = f"http://username:password@{server.host}:{server.port}/status/200?Signature=secret"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
assert response.status == 200
assert await response.text() == "hello"
# Verify redaction in span attributes
spans = memory_exporter.get_finished_spans()
assert len(spans) == 1
span = spans[0]
assert span.attributes[HTTP_METHOD] == "GET"
assert span.attributes[HTTP_STATUS_CODE] == 200
assert (
span.attributes[HTTP_URL]
== f"http://{server.host}:{server.port}/status/200?Signature=REDACTED"
)
# Clean up
AioHttpServerInstrumentor().uninstrument()
memory_exporter.clear()

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.27",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"typing_extensions ~= 4.1",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -27,9 +27,9 @@ classifiers = [
dependencies = [
"asgiref ~= 3.0",
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=too-many-locals
# pylint: disable=too-many-locals,too-many-lines
"""
The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used
@ -81,19 +81,38 @@ For example,
.. code-block:: python
def server_request_hook(span: Span, scope: dict[str, Any]):
from opentelemetry.trace import Span
from typing import Any
from asgiref.typing import Scope, ASGIReceiveEvent, ASGISendEvent
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
async def application(scope: Scope, receive: ASGIReceiveEvent, send: ASGISendEvent):
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
await send({
'type': 'http.response.body',
'body': b'Hello, world!',
})
def server_request_hook(span: Span, scope: Scope):
if span and span.is_recording():
span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
def client_request_hook(span: Span, scope: dict[str, Any], message: dict[str, Any]):
def client_request_hook(span: Span, scope: Scope, message: dict[str, Any]):
if span and span.is_recording():
span.set_attribute("custom_user_attribute_from_client_request_hook", "some-value")
def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, Any]):
def client_response_hook(span: Span, scope: Scope, message: dict[str, Any]):
if span and span.is_recording():
span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
OpenTelemetryMiddleware().(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)
OpenTelemetryMiddleware(application, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook)
Capture HTTP request and response headers
*****************************************
@ -254,12 +273,14 @@ from opentelemetry.util.http import (
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE,
ExcludeList,
SanitizeValue,
_parse_url_query,
get_custom_headers,
normalise_request_header_name,
normalise_response_header_name,
remove_url_credentials,
parse_excluded_urls,
redact_url,
sanitize_method,
)
@ -356,7 +377,7 @@ def collect_request_attributes(
if _report_old(sem_conv_opt_in_mode):
_set_http_url(
result,
remove_url_credentials(http_url),
redact_url(http_url),
_StabilityMode.DEFAULT,
)
http_method = scope.get("method", "")
@ -537,7 +558,7 @@ class OpenTelemetryMiddleware:
def __init__(
self,
app,
excluded_urls=None,
excluded_urls: ExcludeList | str | None = None,
default_span_details=None,
server_request_hook: ServerRequestHook = None,
client_request_hook: ClientRequestHook = None,
@ -619,6 +640,8 @@ class OpenTelemetryMiddleware:
self.active_requests_counter = create_http_server_active_requests(
self.meter
)
if isinstance(excluded_urls, str):
excluded_urls = parse_excluded_urls(excluded_urls)
self.excluded_urls = excluded_urls
self.default_span_details = (
default_span_details or get_default_span_details

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -1633,6 +1633,29 @@ class TestAsgiApplication(AsyncAsgiTestBase):
await self.get_all_output()
self.assertIsNone(self.memory_metrics_reader.get_metrics_data())
async def test_excluded_urls(self):
self.scope["path"] = "/test_excluded_urls"
app = otel_asgi.OpenTelemetryMiddleware(
simple_asgi, excluded_urls="test_excluded_urls"
)
self.seed_app(app)
await self.send_default_request()
await self.get_all_output()
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)
async def test_no_excluded_urls(self):
self.scope["path"] = "/test_excluded_urls"
app = otel_asgi.OpenTelemetryMiddleware(
simple_asgi, excluded_urls="test_excluded_urls"
)
self.seed_app(app)
self.scope["path"] = "/test_no_excluded_urls"
await self.send_default_request()
await self.get_all_output()
spans = self.memory_exporter.get_finished_spans()
self.assertGreater(len(spans), 0)
class TestAsgiAttributes(unittest.TestCase):
def setUp(self):
@ -1809,12 +1832,14 @@ class TestAsgiAttributes(unittest.TestCase):
otel_asgi.set_status_code(self.span, "Invalid Status Code")
self.assertEqual(self.span.set_status.call_count, 1)
def test_credential_removal(self):
def test_remove_sensitive_params(self):
self.scope["server"] = ("username:password@mock", 80)
self.scope["path"] = "/status/200"
self.scope["query_string"] = b"X-Goog-Signature=1234567890"
attrs = otel_asgi.collect_request_attributes(self.scope)
self.assertEqual(
attrs[SpanAttributes.HTTP_URL], "http://mock/status/200"
attrs[SpanAttributes.HTTP_URL],
"http://REDACTED:REDACTED@mock/status/200?X-Goog-Signature=REDACTED",
)
def test_collect_target_attribute_missing(self):

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt ~= 1.0",
"typing_extensions ~= 4.12",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.14",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -25,8 +25,8 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-propagator-aws-xray ~= 1.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -25,8 +25,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.30",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-propagator-aws-xray ~= 1.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -25,7 +25,7 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-api ~= 1.12",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,6 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"
_instruments = tuple()

View File

@ -26,15 +26,15 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-wsgi == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-wsgi == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
]
[project.optional-dependencies]
asgi = [
"opentelemetry-instrumentation-asgi == 0.55b0.dev",
"opentelemetry-instrumentation-asgi == 0.57b0.dev",
]
instruments = [
"django >= 1.10",

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,10 +26,10 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-wsgi == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-wsgi == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"packaging >= 20.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -427,6 +427,20 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)
def test_no_op_tracer_provider(self):
FalconInstrumentor().uninstrument()
FalconInstrumentor().instrument(
tracer_provider=trace.NoOpTracerProvider()
)
self.memory_exporter.clear()
self.client().simulate_get(path="/hello")
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)
def test_exclude_lists(self):
self.client().simulate_get(path="/ping")
span_list = self.memory_exporter.get_finished_spans()

View File

@ -26,10 +26,10 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-asgi == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-asgi == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,10 +26,10 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-wsgi == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-wsgi == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"packaging >= 21.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -149,7 +149,7 @@ Usage Aio Client
grpc_client_instrumentor.instrument()
async def run():
with grpc.aio.insecure_channel("localhost:50051") as channel:
async with grpc.aio.insecure_channel("localhost:50051") as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = await stub.SayHello(helloworld_pb2.HelloRequest(name="YOU"))
@ -168,7 +168,7 @@ You can also add the interceptor manually, rather than using
from opentelemetry.instrumentation.grpc import aio_client_interceptors
channel = grpc.aio.insecure_channel("localhost:12345", interceptors=aio_client_interceptors())
async with grpc.aio.insecure_channel("localhost:50051", interceptors=aio_client_interceptors()) as channel:
Usage Aio Server
@ -253,6 +253,11 @@ You can also use the filters directly on the provided interceptors:
.. code-block::
import grpc
from concurrent import futures
from opentelemetry.instrumentation.grpc import filters
from opentelemetry.instrumentation.grpc import server_interceptor
my_interceptor = server_interceptor(
filter_ = filters.negate(filters.method_name("TestMethod"))
)

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,9 +26,9 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -259,7 +259,7 @@ from opentelemetry.semconv.metrics.http_metrics import (
from opentelemetry.trace import SpanKind, Tracer, TracerProvider, get_tracer
from opentelemetry.trace.span import Span
from opentelemetry.trace.status import StatusCode
from opentelemetry.util.http import remove_url_credentials, sanitize_method
from opentelemetry.util.http import redact_url, sanitize_method
_logger = logging.getLogger(__name__)
@ -313,7 +313,7 @@ def _extract_parameters(
# In httpx >= 0.20.0, handle_request receives a Request object
request: httpx.Request = args[0]
method = request.method.encode()
url = httpx.URL(remove_url_credentials(str(request.url)))
url = httpx.URL(str(request.url))
headers = request.headers
stream = request.stream
extensions = request.extensions
@ -382,7 +382,7 @@ def _apply_request_client_attributes_to_span(
)
# http semconv transition: http.url -> url.full
_set_http_url(span_attributes, str(url), semconv)
_set_http_url(span_attributes, redact_url(str(url)), semconv)
# Set HTTP method in metric labels
_set_http_method(

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -1301,12 +1301,26 @@ class TestSyncIntegration(BaseTestCases.BaseManualTest):
self.assert_span(num_spans=1)
self.assert_metrics(num_metrics=1)
def test_credential_removal(self):
new_url = "http://username:password@mock/status/200"
def test_remove_sensitive_params(self):
new_url = "http://username:password@mock/status/200?sig=secret"
self.perform_request(new_url)
span = self.assert_span()
self.assertEqual(span.attributes[SpanAttributes.HTTP_URL], self.URL)
actual_url = span.attributes[SpanAttributes.HTTP_URL]
if "@" in actual_url:
# If credentials are present, they must be redacted
self.assertEqual(
span.attributes[SpanAttributes.HTTP_URL],
"http://REDACTED:REDACTED@mock/status/200?sig=REDACTED",
)
else:
# If credentials are removed completely, the query string should still be redacted
self.assertIn(
"http://mock/status/200?sig=REDACTED",
actual_url,
f"Basic URL structure is incorrect: {actual_url}",
)
class TestAsyncIntegration(BaseTestCases.BaseManualTest):
@ -1373,12 +1387,24 @@ class TestAsyncIntegration(BaseTestCases.BaseManualTest):
self.assert_span(num_spans=2)
self.assert_metrics(num_metrics=1)
def test_credential_removal(self):
new_url = "http://username:password@mock/status/200"
def test_remove_sensitive_params(self):
new_url = "http://username:password@mock/status/200?Signature=secret"
self.perform_request(new_url)
span = self.assert_span()
self.assertEqual(span.attributes[SpanAttributes.HTTP_URL], self.URL)
actual_url = span.attributes[SpanAttributes.HTTP_URL]
if "@" in actual_url:
self.assertEqual(
span.attributes[SpanAttributes.HTTP_URL],
"http://REDACTED:REDACTED@mock/status/200?Signature=REDACTED",
)
else:
self.assertIn(
"http://mock/status/200?Signature=REDACTED",
actual_url,
f"If credentials are removed, the query string still should be redacted {actual_url}",
)
class TestSyncInstrumentationIntegration(BaseTestCases.BaseInstrumentorTest):

View File

@ -26,7 +26,7 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.5",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,7 +26,7 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,6 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"
_instruments = tuple()

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -20,7 +20,9 @@ import opentelemetry.instrumentation.mysql
from opentelemetry import trace as trace_api
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
from opentelemetry.sdk import resources
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.semconv._incubating.attributes.db_attributes import (
DB_STATEMENT,
)
from opentelemetry.test.test_base import TestBase
@ -157,7 +159,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)
@ -196,7 +198,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
@ -239,7 +241,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)
@ -274,7 +276,7 @@ class TestMysqlIntegration(TestBase):
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)
@ -332,7 +334,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)
@ -373,7 +375,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
@ -418,7 +420,7 @@ class TestMysqlIntegration(TestBase):
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)
@ -453,7 +455,7 @@ class TestMysqlIntegration(TestBase):
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
span.attributes[DB_STATEMENT],
"Select 1;",
)

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -25,7 +25,7 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-api ~= 1.5",
"packaging >= 20.0",
"wrapt >= 1.0.0, < 2.0.0",

View File

@ -14,7 +14,7 @@
# pylint: disable=unnecessary-dunder-call
from logging import getLogger
from typing import Any, Collection, Dict, Optional
from typing import Any, Collection, Dict, Optional, Union
import pika
import wrapt
@ -24,6 +24,8 @@ from pika.adapters.blocking_connection import (
BlockingChannel,
_QueueConsumerGeneratorInfo,
)
from pika.channel import Channel
from pika.connection import Connection
from opentelemetry import trace
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@ -53,12 +55,16 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
# pylint: disable=attribute-defined-outside-init
@staticmethod
def _instrument_blocking_channel_consumers(
channel: BlockingChannel,
def _instrument_channel_consumers(
channel: Union[BlockingChannel, Channel],
tracer: Tracer,
consume_hook: utils.HookT = utils.dummy_callback,
) -> Any:
for consumer_tag, consumer_info in channel._consumer_infos.items():
if isinstance(channel, BlockingChannel):
consumer_infos = channel._consumer_infos
elif isinstance(channel, Channel):
consumer_infos = channel._consumers
for consumer_tag, consumer_info in consumer_infos.items():
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
consumer_callback = getattr(consumer_info, callback_attr, None)
if consumer_callback is None:
@ -79,7 +85,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
@staticmethod
def _instrument_basic_publish(
channel: BlockingChannel,
channel: Union[BlockingChannel, Channel],
tracer: Tracer,
publish_hook: utils.HookT = utils.dummy_callback,
) -> None:
@ -93,7 +99,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
@staticmethod
def _instrument_channel_functions(
channel: BlockingChannel,
channel: Union[BlockingChannel, Channel],
tracer: Tracer,
publish_hook: utils.HookT = utils.dummy_callback,
) -> None:
@ -103,7 +109,9 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
)
@staticmethod
def _uninstrument_channel_functions(channel: BlockingChannel) -> None:
def _uninstrument_channel_functions(
channel: Union[BlockingChannel, Channel],
) -> None:
for function_name in _FUNCTIONS_TO_UNINSTRUMENT:
if not hasattr(channel, function_name):
continue
@ -115,7 +123,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
@staticmethod
# Make sure that the spans are created inside hash them set as parent and not as brothers
def instrument_channel(
channel: BlockingChannel,
channel: Union[BlockingChannel, Channel],
tracer_provider: Optional[TracerProvider] = None,
publish_hook: utils.HookT = utils.dummy_callback,
consume_hook: utils.HookT = utils.dummy_callback,
@ -133,7 +141,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
tracer_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
)
PikaInstrumentor._instrument_blocking_channel_consumers(
PikaInstrumentor._instrument_channel_consumers(
channel, tracer, consume_hook
)
PikaInstrumentor._decorate_basic_consume(channel, tracer, consume_hook)
@ -178,16 +186,17 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
return channel
wrapt.wrap_function_wrapper(BlockingConnection, "channel", wrapper)
wrapt.wrap_function_wrapper(Connection, "channel", wrapper)
@staticmethod
def _decorate_basic_consume(
channel: BlockingChannel,
channel: Union[BlockingChannel, Channel],
tracer: Optional[Tracer],
consume_hook: utils.HookT = utils.dummy_callback,
) -> None:
def wrapper(wrapped, instance, args, kwargs):
return_value = wrapped(*args, **kwargs)
PikaInstrumentor._instrument_blocking_channel_consumers(
PikaInstrumentor._instrument_channel_consumers(
channel, tracer, consume_hook
)
return return_value
@ -236,6 +245,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
if hasattr(self, "__opentelemetry_tracer_provider"):
delattr(self, "__opentelemetry_tracer_provider")
unwrap(BlockingConnection, "channel")
unwrap(Connection, "channel")
unwrap(_QueueConsumerGeneratorInfo, "__init__")
def instrumentation_dependencies(self) -> Collection[str]:

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -13,9 +13,13 @@
# limitations under the License.
from unittest import TestCase, mock
from pika.adapters import BlockingConnection
from pika.adapters.blocking_connection import _QueueConsumerGeneratorInfo
from pika.adapters import BaseConnection, BlockingConnection
from pika.adapters.blocking_connection import (
BlockingChannel,
_QueueConsumerGeneratorInfo,
)
from pika.channel import Channel
from pika.connection import Connection
from wrapt import BoundFunctionWrapper
from opentelemetry.instrumentation.pika import PikaInstrumentor
@ -31,11 +35,13 @@ from opentelemetry.trace import Tracer
class TestPika(TestCase):
def setUp(self) -> None:
self.blocking_channel = mock.MagicMock(spec=BlockingChannel)
self.channel = mock.MagicMock(spec=Channel)
consumer_info = mock.MagicMock()
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
setattr(consumer_info, callback_attr, mock.MagicMock())
self.channel._consumer_infos = {"consumer-tag": consumer_info}
self.blocking_channel._consumer_infos = {"consumer-tag": consumer_info}
self.channel._consumers = {"consumer-tag": consumer_info}
self.mock_callback = mock.MagicMock()
def test_instrument_api(self) -> None:
@ -44,6 +50,10 @@ class TestPika(TestCase):
self.assertTrue(
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
)
self.assertTrue(isinstance(Connection.channel, BoundFunctionWrapper))
self.assertTrue(
isinstance(BaseConnection.channel, BoundFunctionWrapper)
)
self.assertTrue(
isinstance(
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
@ -56,6 +66,10 @@ class TestPika(TestCase):
self.assertFalse(
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
)
self.assertFalse(isinstance(Connection.channel, BoundFunctionWrapper))
self.assertFalse(
isinstance(BaseConnection.channel, BoundFunctionWrapper)
)
self.assertFalse(
isinstance(
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
@ -69,11 +83,34 @@ class TestPika(TestCase):
"opentelemetry.instrumentation.pika.PikaInstrumentor._decorate_basic_consume"
)
@mock.patch(
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_blocking_channel_consumers"
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_consumers"
)
def test_instrument_blocking_channel(
self,
instrument_channel_consumers: mock.MagicMock,
instrument_basic_consume: mock.MagicMock,
instrument_channel_functions: mock.MagicMock,
):
PikaInstrumentor.instrument_channel(channel=self.blocking_channel)
assert hasattr(
self.blocking_channel, "_is_instrumented_by_opentelemetry"
), "channel is not marked as instrumented!"
instrument_channel_consumers.assert_called_once()
instrument_basic_consume.assert_called_once()
instrument_channel_functions.assert_called_once()
@mock.patch(
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_functions"
)
@mock.patch(
"opentelemetry.instrumentation.pika.PikaInstrumentor._decorate_basic_consume"
)
@mock.patch(
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_consumers"
)
def test_instrument_channel(
self,
instrument_blocking_channel_consumers: mock.MagicMock,
instrument_channel_consumers: mock.MagicMock,
instrument_basic_consume: mock.MagicMock,
instrument_channel_functions: mock.MagicMock,
):
@ -81,12 +118,12 @@ class TestPika(TestCase):
assert hasattr(
self.channel, "_is_instrumented_by_opentelemetry"
), "channel is not marked as instrumented!"
instrument_blocking_channel_consumers.assert_called_once()
instrument_channel_consumers.assert_called_once()
instrument_basic_consume.assert_called_once()
instrument_channel_functions.assert_called_once()
@mock.patch("opentelemetry.instrumentation.pika.utils._decorate_callback")
def test_instrument_consumers(
def test_instrument_consumers_on_blocking_channel(
self, decorate_callback: mock.MagicMock
) -> None:
tracer = mock.MagicMock(spec=Tracer)
@ -95,23 +132,63 @@ class TestPika(TestCase):
mock.call(
getattr(value, callback_attr), tracer, key, dummy_callback
)
for key, value in self.channel._consumer_infos.items()
for key, value in self.blocking_channel._consumer_infos.items()
]
PikaInstrumentor._instrument_blocking_channel_consumers(
self.channel, tracer
PikaInstrumentor._instrument_channel_consumers(
self.blocking_channel, tracer
)
decorate_callback.assert_has_calls(
calls=expected_decoration_calls, any_order=True
)
assert all(
hasattr(callback, "_original_callback")
for callback in self.channel._consumer_infos.values()
for callback in self.blocking_channel._consumer_infos.values()
)
@mock.patch("opentelemetry.instrumentation.pika.utils._decorate_callback")
def test_instrument_consumers_on_channel(
self, decorate_callback: mock.MagicMock
) -> None:
tracer = mock.MagicMock(spec=Tracer)
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
expected_decoration_calls = [
mock.call(
getattr(value, callback_attr), tracer, key, dummy_callback
)
for key, value in self.channel._consumers.items()
]
PikaInstrumentor._instrument_channel_consumers(self.channel, tracer)
decorate_callback.assert_has_calls(
calls=expected_decoration_calls, any_order=True
)
assert all(
hasattr(callback, "_original_callback")
for callback in self.channel._consumers.values()
)
@mock.patch(
"opentelemetry.instrumentation.pika.utils._decorate_basic_publish"
)
def test_instrument_basic_publish(
def test_instrument_basic_publish_on_blocking_channel(
self, decorate_basic_publish: mock.MagicMock
) -> None:
tracer = mock.MagicMock(spec=Tracer)
original_function = self.blocking_channel.basic_publish
PikaInstrumentor._instrument_basic_publish(
self.blocking_channel, tracer
)
decorate_basic_publish.assert_called_once_with(
original_function, self.blocking_channel, tracer, dummy_callback
)
self.assertEqual(
self.blocking_channel.basic_publish,
decorate_basic_publish.return_value,
)
@mock.patch(
"opentelemetry.instrumentation.pika.utils._decorate_basic_publish"
)
def test_instrument_basic_publish_on_channel(
self, decorate_basic_publish: mock.MagicMock
) -> None:
tracer = mock.MagicMock(spec=Tracer)
@ -141,6 +218,17 @@ class TestPika(TestCase):
isinstance(generator_info.pending_events, ReadyMessagesDequeProxy)
)
def test_uninstrument_blocking_channel_functions(self) -> None:
original_function = self.blocking_channel.basic_publish
self.blocking_channel.basic_publish = mock.MagicMock()
self.blocking_channel.basic_publish._original_function = (
original_function
)
PikaInstrumentor._uninstrument_channel_functions(self.blocking_channel)
self.assertEqual(
self.blocking_channel.basic_publish, original_function
)
def test_uninstrument_channel_functions(self) -> None:
original_function = self.channel.basic_publish
self.channel.basic_publish = mock.MagicMock()

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -17,4 +17,6 @@ COMMAND_TO_ATTRIBUTE_MAPPING = {
"delete": "deletes",
"update": "updates",
"find": "filter",
"getMore": "collection",
"aggregate": "pipeline",
}

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -57,14 +57,12 @@ class TestPymongo(TestBase):
# pylint: disable=protected-access
span = command_tracer._pop_span(mock_event)
self.assertIs(span.kind, trace_api.SpanKind.CLIENT)
self.assertEqual(span.name, "database_name.command_name")
self.assertEqual(span.name, "database_name.find")
self.assertEqual(span.attributes[SpanAttributes.DB_SYSTEM], "mongodb")
self.assertEqual(
span.attributes[SpanAttributes.DB_NAME], "database_name"
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT], "command_name"
)
self.assertEqual(span.attributes[SpanAttributes.DB_STATEMENT], "find")
self.assertEqual(
span.attributes[SpanAttributes.NET_PEER_NAME], "test.com"
)
@ -181,7 +179,7 @@ class TestPymongo(TestBase):
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
self.assertEqual(span.name, "database_name.command_name")
self.assertEqual(span.name, "database_name.123")
def test_no_op_tracer(self):
mock_event = MockEvent({})
@ -194,6 +192,90 @@ class TestPymongo(TestBase):
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 0)
def test_capture_statement_getmore(self):
command_attrs = {
"command_name": "getMore",
"collection": "test_collection",
}
mock_event = MockEvent(command_attrs)
command_tracer = CommandTracer(self.tracer, capture_statement=True)
command_tracer.started(event=mock_event)
command_tracer.succeeded(event=mock_event)
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
"getMore test_collection",
)
def test_capture_statement_aggregate(self):
pipeline = [
{"$match": {"status": "active"}},
{"$group": {"_id": "$category", "count": {"$sum": 1}}},
]
command_attrs = {
"command_name": "aggregate",
"pipeline": pipeline,
}
command_tracer = CommandTracer(self.tracer, capture_statement=True)
mock_event = MockEvent(command_attrs)
command_tracer.started(event=mock_event)
command_tracer.succeeded(event=mock_event)
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
expected_statement = f"aggregate {pipeline}"
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT], expected_statement
)
def test_capture_statement_disabled_getmore(self):
command_attrs = {
"command_name": "getMore",
"collection": "test_collection",
}
command_tracer = CommandTracer(self.tracer, capture_statement=False)
mock_event = MockEvent(command_attrs)
command_tracer.started(event=mock_event)
command_tracer.succeeded(event=mock_event)
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT], "getMore"
)
def test_capture_statement_disabled_aggregate(self):
pipeline = [{"$match": {"status": "active"}}]
command_attrs = {
"command_name": "aggregate",
"pipeline": pipeline,
}
command_tracer = CommandTracer(self.tracer, capture_statement=False)
mock_event = MockEvent(command_attrs)
command_tracer.started(event=mock_event)
command_tracer.succeeded(event=mock_event)
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT], "aggregate"
)
class MockCommand:
def __init__(self, command_attrs):
@ -206,6 +288,7 @@ class MockCommand:
class MockEvent:
def __init__(self, command_attrs, connection_id=None, request_id=""):
self.command = MockCommand(command_attrs)
self.command_name = self.command.get("command_name")
self.connection_id = connection_id
self.request_id = request_id

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,8 +26,8 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-dbapi == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-dbapi == 0.57b0.dev",
]
[project.optional-dependencies]

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.55b0.dev"
__version__ = "0.57b0.dev"

View File

@ -26,10 +26,10 @@ classifiers = [
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-instrumentation == 0.55b0.dev",
"opentelemetry-instrumentation-wsgi == 0.55b0.dev",
"opentelemetry-semantic-conventions == 0.55b0.dev",
"opentelemetry-util-http == 0.55b0.dev",
"opentelemetry-instrumentation == 0.57b0.dev",
"opentelemetry-instrumentation-wsgi == 0.57b0.dev",
"opentelemetry-semantic-conventions == 0.57b0.dev",
"opentelemetry-util-http == 0.57b0.dev",
"wrapt >= 1.0.0, < 2.0.0",
]

Some files were not shown because too many files have changed in this diff Show More