Skip to content

Commit

Permalink
Merge branch 'main' into 2410-fix-ut-with-pypy
Browse files Browse the repository at this point in the history
  • Loading branch information
gongbenhua authored Aug 21, 2024
2 parents 8aa38d0 + dda369b commit f962aab
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ jobs:
!contains(github.event.pull_request.labels.*.name, 'Skip generate-workflows')
&& github.actor != 'opentelemetrybot'
{%- endif %}
{%- if job_data == "public-symbols-check" %}
if: |
!contains(github.event.pull_request.labels.*.name, 'Approve Public API check')
&& github.actor != 'opentelemetrybot'
{%- endif %}
steps:
- name: Checkout repo @ SHA - ${% raw %}{{ github.sha }}{% endraw %}
uses: actions/checkout@v4
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `opentelemetry-instrumentation-kafka-python` Instrument temporary fork, kafka-python-ng
inside kafka-python's instrumentation
([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537)))
([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537))

## Breaking changes

- `opentelemetry-bootstrap` Remove `opentelemetry-instrumentation-aws-lambda` from the defaults instrumentations
([#2786](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2786))

## Fixed

- `opentelemetry-instrumentation-fastapi` fix `fastapi` auto-instrumentation by removing `fastapi-slim` support, `fastapi-slim` itself is discontinued from maintainers
Expand All @@ -23,12 +26,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2750](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2750))
- `opentelemetry-instrumentation-django` Fix regression - `http.target` re-added back to old semconv duration metrics
([#2746](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2746))
- `opentelemetry-instrumentation-asgi` do not set `url.full` attribute for server spans
([#2735](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2735))
- `opentelemetry-instrumentation-grpc` Fixes the issue with the gRPC instrumentation not working with the 1.63.0 and higher version of gRPC
([#2483](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2484))
- `opentelemetry-instrumentation-aws-lambda` Fixing w3c baggage support
([#2589](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2589))
- `opentelemetry-instrumentation-celery` propagates baggage
([#2385](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2385))
- `opentelemetry-instrumentation-asyncio` Fixes async generator coroutines not being awaited
([#2792](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2792))
- `opentelemetry-instrumentation-tornado` Handle http client exception and record exception info into span
([#2563](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2563))

## Version 1.26.0/0.47b0 (2024-07-23)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.9.4
aiohttp==3.10.2
aiosignal==1.3.1
asgiref==3.7.2
async-timeout==4.0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ async def request_handler(request):
[
(
"GET",
(StatusCode.ERROR, "ServerTimeoutError"),
(StatusCode.ERROR, "SocketTimeoutError"),
{
SpanAttributes.HTTP_METHOD: "GET",
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test_timeout",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.9.4
aiohttp==3.10.2
aiosignal==1.3.1
asgiref==3.7.2
async-timeout==4.0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,12 @@ def collect_request_attributes(
result, path, path, query_string, sem_conv_opt_in_mode
)
if http_url:
_set_http_url(
result, remove_url_credentials(http_url), sem_conv_opt_in_mode
)

if _report_old(sem_conv_opt_in_mode):
_set_http_url(
result,
remove_url_credentials(http_url),
_HTTPStabilityMode.DEFAULT,
)
http_method = scope.get("method", "")
if http_method:
_set_http_method(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
SERVER_PORT,
)
from opentelemetry.semconv.attributes.url_attributes import (
URL_FULL,
URL_PATH,
URL_QUERY,
URL_SCHEME,
Expand Down Expand Up @@ -410,7 +409,6 @@ def validate_outputs(
SERVER_ADDRESS: "127.0.0.1",
NETWORK_PROTOCOL_VERSION: "1.0",
URL_PATH: "/",
URL_FULL: "http://127.0.0.1/",
CLIENT_ADDRESS: "127.0.0.1",
CLIENT_PORT: 32767,
HTTP_RESPONSE_STATUS_CODE: 200,
Expand Down Expand Up @@ -447,7 +445,6 @@ def validate_outputs(
SERVER_ADDRESS: "127.0.0.1",
NETWORK_PROTOCOL_VERSION: "1.0",
URL_PATH: "/",
URL_FULL: "http://127.0.0.1/",
CLIENT_ADDRESS: "127.0.0.1",
CLIENT_PORT: 32767,
HTTP_RESPONSE_STATUS_CODE: 200,
Expand Down Expand Up @@ -693,7 +690,6 @@ def update_expected_server(expected):
{
SERVER_ADDRESS: "0.0.0.0",
SERVER_PORT: 80,
URL_FULL: "http://0.0.0.0/",
}
)
return expected
Expand Down Expand Up @@ -721,7 +717,6 @@ def update_expected_server(expected):
SpanAttributes.HTTP_URL: "http://0.0.0.0/",
SERVER_ADDRESS: "0.0.0.0",
SERVER_PORT: 80,
URL_FULL: "http://0.0.0.0/",
}
)
return expected
Expand Down Expand Up @@ -1009,7 +1004,6 @@ def test_websocket_new_semconv(self):
SERVER_ADDRESS: self.scope["server"][0],
NETWORK_PROTOCOL_VERSION: self.scope["http_version"],
URL_PATH: self.scope["path"],
URL_FULL: f'{self.scope["scheme"]}://{self.scope["server"][0]}{self.scope["path"]}',
CLIENT_ADDRESS: self.scope["client"][0],
CLIENT_PORT: self.scope["client"][1],
HTTP_RESPONSE_STATUS_CODE: 200,
Expand Down Expand Up @@ -1095,7 +1089,6 @@ def test_websocket_both_semconv(self):
SERVER_ADDRESS: self.scope["server"][0],
NETWORK_PROTOCOL_VERSION: self.scope["http_version"],
URL_PATH: self.scope["path"],
URL_FULL: f'{self.scope["scheme"]}://{self.scope["server"][0]}{self.scope["path"]}',
CLIENT_ADDRESS: self.scope["client"][0],
CLIENT_PORT: self.scope["client"][1],
HTTP_RESPONSE_STATUS_CODE: 200,
Expand Down Expand Up @@ -1639,7 +1632,6 @@ def test_request_attributes_new_semconv(self):
SERVER_ADDRESS: "127.0.0.1",
URL_PATH: "/",
URL_QUERY: "foo=bar",
URL_FULL: "http://127.0.0.1/?foo=bar",
SERVER_PORT: 80,
URL_SCHEME: "http",
NETWORK_PROTOCOL_VERSION: "1.0",
Expand Down Expand Up @@ -1676,7 +1668,6 @@ def test_request_attributes_both_semconv(self):
SERVER_ADDRESS: "127.0.0.1",
URL_PATH: "/",
URL_QUERY: "foo=bar",
URL_FULL: "http://127.0.0.1/?foo=bar",
SERVER_PORT: 80,
URL_SCHEME: "http",
NETWORK_PROTOCOL_VERSION: "1.0",
Expand All @@ -1698,18 +1689,24 @@ def test_query_string_new_semconv(self):
self.scope,
_HTTPStabilityMode.HTTP,
)
self.assertEqual(attrs[URL_FULL], "http://127.0.0.1/?foo=bar")
self.assertEqual(attrs[URL_SCHEME], "http")
self.assertEqual(attrs[SERVER_ADDRESS], "127.0.0.1")
self.assertEqual(attrs[URL_PATH], "/")
self.assertEqual(attrs[URL_QUERY], "foo=bar")

def test_query_string_both_semconv(self):
self.scope["query_string"] = b"foo=bar"
attrs = otel_asgi.collect_request_attributes(
self.scope,
_HTTPStabilityMode.HTTP_DUP,
)
self.assertEqual(attrs[URL_FULL], "http://127.0.0.1/?foo=bar")
self.assertEqual(
attrs[SpanAttributes.HTTP_URL], "http://127.0.0.1/?foo=bar"
)
self.assertEqual(attrs[URL_SCHEME], "http")
self.assertEqual(attrs[SERVER_ADDRESS], "127.0.0.1")
self.assertEqual(attrs[URL_PATH], "/")
self.assertEqual(attrs[URL_QUERY], "foo=bar")

def test_query_string_percent_bytes(self):
self.scope["query_string"] = b"foo%3Dbar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def trace_item(self, coro_or_future):

async def trace_coroutine(self, coro):
if not hasattr(coro, "__name__"):
return coro
return await coro
start = default_timer()
attr = {
"type": "coroutine",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,23 @@ def tearDown(self):

# Asyncio anext() does not have __name__ attribute, which is used to determine if the coroutine should be traced.
# This test is to ensure that the instrumentation does not break when the coroutine does not have __name__ attribute.
# Additionally, ensure the coroutine is actually awaited.
@skipIf(
sys.version_info < (3, 10), "anext is only available in Python 3.10+"
)
def test_asyncio_anext(self):
async def main():
async def async_gen():
for it in range(2):
# nothing special about this range other than to avoid returning a zero
# from a function named 'main' (which might cause confusion about intent)
for it in range(2, 4):
yield it

async_gen_instance = async_gen()
agen = anext(async_gen_instance)
await asyncio.create_task(agen)
return await asyncio.create_task(agen)

asyncio.run(main())
ret = asyncio.run(main())
self.assertEqual(ret, 2) # first iteration from range()
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,14 @@ def test_uninstrumented(self):
self.assertEqual(len(spans), 0)

Jinja2Instrumentor().instrument()

def test_no_op_tracer_provider(self):
self.memory_exporter.clear()
Jinja2Instrumentor().uninstrument()
Jinja2Instrumentor().instrument(
tracer_provider=trace_api.NoOpTracerProvider()
)
template = jinja2.environment.Template("Hello {{name}}!")
self.assertEqual(template.render(name="Jinja"), "Hello Jinja!")
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ tomli==2.0.1
translationstring==1.4
typing_extensions==4.9.0
venusian==3.1.0
WebOb==1.8.7
WebOb==1.8.8
Werkzeug==3.0.3
wrapt==1.16.0
zipp==3.19.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from opentelemetry.instrumentation.utils import http_status_to_status_code
from opentelemetry.propagate import inject
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.status import Status
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.util.http import remove_url_credentials


Expand Down Expand Up @@ -105,37 +105,53 @@ def _finish_tracing_callback(
request_size_histogram,
response_size_histogram,
):
response = None
status_code = None
status = None
description = None
exc = future.exception()

response = future.result()

if span.is_recording() and exc:
exc = future.exception()
if exc:
description = f"{type(exc).__qualname__}: {exc}"
if isinstance(exc, HTTPError):
response = exc.response
status_code = exc.code
description = f"{type(exc).__name__}: {exc}"
status = Status(
status_code=http_status_to_status_code(status_code),
description=description,
)
else:
status = Status(
status_code=StatusCode.ERROR,
description=description,
)
span.record_exception(exc)
else:
response = future.result()
status_code = response.code
status = Status(
status_code=http_status_to_status_code(status_code),
description=description,
)

if status_code is not None:
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
span.set_status(
Status(
status_code=http_status_to_status_code(status_code),
description=description,
)
)
span.set_status(status)

metric_attributes = _create_metric_attributes(response)
request_size = int(response.request.headers.get("Content-Length", 0))
response_size = int(response.headers.get("Content-Length", 0))
if response is not None:
metric_attributes = _create_metric_attributes(response)
request_size = int(response.request.headers.get("Content-Length", 0))
response_size = int(response.headers.get("Content-Length", 0))

duration_histogram.record(
response.request_time, attributes=metric_attributes
)
request_size_histogram.record(request_size, attributes=metric_attributes)
response_size_histogram.record(response_size, attributes=metric_attributes)
duration_histogram.record(
response.request_time, attributes=metric_attributes
)
request_size_histogram.record(
request_size, attributes=metric_attributes
)
response_size_histogram.record(
response_size, attributes=metric_attributes
)

if response_hook:
response_hook(span, future)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from unittest.mock import Mock, patch

from http_server_mock import HttpServerMock
from tornado.httpclient import HTTPClientError
from tornado.testing import AsyncHTTPTestCase

from opentelemetry import trace
Expand All @@ -32,7 +33,7 @@
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
from opentelemetry.test.wsgitestutil import WsgiTestBase
from opentelemetry.trace import SpanKind
from opentelemetry.trace import SpanKind, StatusCode
from opentelemetry.util.http import (
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE,
Expand Down Expand Up @@ -493,7 +494,6 @@ def test_response_headers(self):
self.assertEqual(len(spans), 3)
self.assertTraceResponseHeaderMatchesSpan(response.headers, spans[1])

self.memory_exporter.clear()
set_global_response_propagator(orig)

def test_credential_removal(self):
Expand Down Expand Up @@ -602,6 +602,49 @@ def client_response_hook(span, response):
self.memory_exporter.clear()


class TestTornadoHTTPClientInstrumentation(TornadoTest, WsgiTestBase):
def test_http_client_success_response(self):
response = self.fetch("/")
self.assertEqual(response.code, 201)

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 3)
manual, server, client = self.sorted_spans(spans)
self.assertEqual(manual.name, "manual")
self.assertEqual(server.name, "GET /")
self.assertEqual(client.name, "GET")
self.assertEqual(client.status.status_code, StatusCode.UNSET)
self.memory_exporter.clear()

def test_http_client_failed_response(self):
# when an exception isn't thrown
response = self.fetch("/some-404")
self.assertEqual(response.code, 404)

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
server, client = self.sorted_spans(spans)
self.assertEqual(server.name, "GET /some-404")
self.assertEqual(client.name, "GET")
self.assertEqual(client.status.status_code, StatusCode.ERROR)
self.memory_exporter.clear()

# when an exception is thrown
try:
response = self.fetch("/some-404", raise_error=True)
self.assertEqual(response.code, 404)
except HTTPClientError:
pass # expected exception - continue

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)
server, client = self.sorted_spans(spans)
self.assertEqual(server.name, "GET /some-404")
self.assertEqual(client.name, "GET")
self.assertEqual(client.status.status_code, StatusCode.ERROR)
self.memory_exporter.clear()


class TestTornadoUninstrument(TornadoTest):
def test_uninstrument(self):
response = self.fetch("/")
Expand Down
Loading

0 comments on commit f962aab

Please sign in to comment.