Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tornado): ensure reading future.result() won't throw an exception in client.py _finish_tracing_callback #2563

Merged
merged 29 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
38ebaf6
fix(tornado): ensure reading future.result() won't throw an exception…
jgibo May 28, 2024
6220697
add test cases
jgibo May 29, 2024
245c557
improve test 'test_http_client_failed_response'
jgibo May 29, 2024
a13049a
uncomment dev only change
jgibo May 29, 2024
516de12
fix test
jgibo May 29, 2024
cbb3e4d
lint fix
jgibo May 29, 2024
9aebb65
update changelog
jgibo May 29, 2024
ca6cfc9
remove debugging prints
jgibo May 29, 2024
4280382
Revert "lint fix"
jgibo May 29, 2024
a55a22e
lint fix tornado dir only
jgibo May 29, 2024
dfd4198
remove unused import
jgibo May 29, 2024
18fbf80
Remove 'general exception' catch clause
jgibo May 29, 2024
b60cfc8
Merge branch 'main' into issue_2555
jgibo May 31, 2024
4d9613e
Merge branch 'open-telemetry:main' into issue_2555
jgibo Jun 10, 2024
8256c46
Merge branch 'main' into issue_2555
jgibo Jun 10, 2024
632452a
extract response from exception and record its info into span
jgibo Jun 26, 2024
d9ed843
improvement, make sure we record a non HTTPError as well
jgibo Jun 26, 2024
a1a8caa
update changelog
jgibo Jun 26, 2024
24c75ad
Merge branch 'main' into issue_2555
jgibo Jun 26, 2024
8f1a52f
get status_code from exception instead of the optional exception resp…
jgibo Jul 9, 2024
d3e0d25
Merge branch 'main' into issue_2555, fix changelog conflict
jgibo Jul 10, 2024
90eb089
Merge branch 'main' into issue_2555, fix changelog conflict
jgibo Aug 2, 2024
d96e92c
Update CHANGELOG.md
jgibo Aug 5, 2024
4937ee7
Merge branch 'main' into issue_2555, fix changelog conflict
jgibo Aug 5, 2024
e1963f0
Merge branch 'main' into issue_2555
jgibo Aug 7, 2024
20b2846
Merge branch 'main' into issue_2555, fix changelog conflict
jgibo Aug 13, 2024
252feba
record exception when exc is not a HTTPError
jgibo Aug 13, 2024
5a841a7
code review suggestion - use __qualname__
jgibo Aug 14, 2024
9b37b88
Merge branch 'main' into issue_2555
jgibo Aug 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#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-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
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,52 @@ 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).__name__}: {exc}"
jgibo marked this conversation as resolved.
Show resolved Hide resolved
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,
)
lzchen marked this conversation as resolved.
Show resolved Hide resolved
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
jgibo marked this conversation as resolved.
Show resolved Hide resolved
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