Skip to content

Commit 3440e70

Browse files
authored
Merge branch 'main' into django-asgi
2 parents f5b57a0 + b2dd4b8 commit 3440e70

File tree

11 files changed

+263
-12
lines changed

11 files changed

+263
-12
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.3.0-0.22b0...HEAD)
88

9+
### Changed
10+
- `opentelemetry-instrumentation-tornado` properly instrument work done in tornado on_finish method.
11+
([#499](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/499))
12+
- `opentelemetry-instrumentation` Fixed cases where trying to use an instrumentation package without the
13+
target library was crashing auto instrumentation agent.
14+
([#530](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/530))
15+
- Fix weak reference error for pyodbc cursor in SQLAlchemy instrumentation.
16+
([#469](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/469))
17+
918
## [0.22b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.3.0-0.22b0) - 2021-06-01
1019

1120
### Changed

instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
from threading import local
16-
from weakref import WeakKeyDictionary
1716

1817
from sqlalchemy.event import listen # pylint: disable=no-name-in-module
1918

@@ -60,7 +59,7 @@ def __init__(self, tracer, engine):
6059
self.tracer = tracer
6160
self.engine = engine
6261
self.vendor = _normalize_vendor(engine.name)
63-
self.cursor_mapping = WeakKeyDictionary()
62+
self.cursor_mapping = {}
6463
self.local = local()
6564

6665
listen(engine, "before_cursor_execute", self._before_cur_exec)
@@ -116,6 +115,7 @@ def _after_cur_exec(self, conn, cursor, statement, *args):
116115
return
117116

118117
span.end()
118+
self._cleanup(cursor)
119119

120120
def _handle_error(self, context):
121121
span = self.current_thread_span
@@ -129,6 +129,13 @@ def _handle_error(self, context):
129129
)
130130
finally:
131131
span.end()
132+
self._cleanup(context.cursor)
133+
134+
def _cleanup(self, cursor):
135+
try:
136+
del self.cursor_mapping[cursor]
137+
except KeyError:
138+
pass
132139

133140

134141
def _get_attributes_from_url(url):

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ def _prepare(tracer, request_hook, func, handler, args, kwargs):
213213

214214

215215
def _on_finish(tracer, func, handler, args, kwargs):
216+
response = func(*args, **kwargs)
216217
_finish_span(tracer, handler)
217-
return func(*args, **kwargs)
218+
return response
218219

219220

220221
def _log_exception(tracer, func, handler, args, kwargs):

instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py

+48
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,54 @@ def test_dynamic_handler(self):
347347
},
348348
)
349349

350+
def test_handler_on_finish(self):
351+
352+
response = self.fetch("/on_finish")
353+
self.assertEqual(response.code, 200)
354+
355+
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
356+
self.assertEqual(len(spans), 3)
357+
auditor, server, client = spans
358+
359+
self.assertEqual(server.name, "FinishedHandler.get")
360+
self.assertTrue(server.parent.is_remote)
361+
self.assertNotEqual(server.parent, client.context)
362+
self.assertEqual(server.parent.span_id, client.context.span_id)
363+
self.assertEqual(server.context.trace_id, client.context.trace_id)
364+
self.assertEqual(server.kind, SpanKind.SERVER)
365+
self.assert_span_has_attributes(
366+
server,
367+
{
368+
SpanAttributes.HTTP_METHOD: "GET",
369+
SpanAttributes.HTTP_SCHEME: "http",
370+
SpanAttributes.HTTP_HOST: "127.0.0.1:"
371+
+ str(self.get_http_port()),
372+
SpanAttributes.HTTP_TARGET: "/on_finish",
373+
SpanAttributes.NET_PEER_IP: "127.0.0.1",
374+
SpanAttributes.HTTP_STATUS_CODE: 200,
375+
},
376+
)
377+
378+
self.assertEqual(client.name, "GET")
379+
self.assertFalse(client.context.is_remote)
380+
self.assertIsNone(client.parent)
381+
self.assertEqual(client.kind, SpanKind.CLIENT)
382+
self.assert_span_has_attributes(
383+
client,
384+
{
385+
SpanAttributes.HTTP_URL: self.get_url("/on_finish"),
386+
SpanAttributes.HTTP_METHOD: "GET",
387+
SpanAttributes.HTTP_STATUS_CODE: 200,
388+
},
389+
)
390+
391+
self.assertEqual(auditor.name, "audit_task")
392+
self.assertFalse(auditor.context.is_remote)
393+
self.assertEqual(auditor.parent.span_id, server.context.span_id)
394+
self.assertEqual(auditor.context.trace_id, client.context.trace_id)
395+
396+
self.assertEqual(auditor.kind, SpanKind.INTERNAL)
397+
350398
def test_exclude_lists(self):
351399
def test_excluded(path):
352400
self.fetch(path)

instrumentation/opentelemetry-instrumentation-tornado/tests/tornado_test_app.py

+12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# pylint: disable=W0223,R0201
2+
import time
23

34
import tornado.web
45
from tornado import gen
@@ -79,6 +80,16 @@ def get(self):
7980
self.set_status(202)
8081

8182

83+
class FinishedHandler(tornado.web.RequestHandler):
84+
def on_finish(self):
85+
with self.application.tracer.start_as_current_span("audit_task"):
86+
time.sleep(0.05)
87+
88+
def get(self):
89+
self.write("Test can finish")
90+
self.set_status(200)
91+
92+
8293
class HealthCheckHandler(tornado.web.RequestHandler):
8394
def get(self):
8495
self.set_status(200)
@@ -91,6 +102,7 @@ def make_app(tracer):
91102
(r"/error", BadHandler),
92103
(r"/cor", CoroutineHandler),
93104
(r"/async", AsyncHandler),
105+
(r"/on_finish", FinishedHandler),
94106
(r"/healthz", HealthCheckHandler),
95107
(r"/ping", HealthCheckHandler),
96108
]

opentelemetry-instrumentation/src/opentelemetry/instrumentation/dependencies.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
from logging import getLogger
12
from typing import Collection, Optional
23

34
from pkg_resources import (
45
Distribution,
56
DistributionNotFound,
7+
RequirementParseError,
68
VersionConflict,
79
get_distribution,
810
)
911

12+
logger = getLogger(__file__)
13+
1014

1115
class DependencyConflict:
1216
required: str = None
@@ -25,22 +29,36 @@ def __str__(self):
2529
def get_dist_dependency_conflicts(
2630
dist: Distribution,
2731
) -> Optional[DependencyConflict]:
28-
deps = [
29-
dep
30-
for dep in dist.requires(("instruments",))
31-
if dep not in dist.requires()
32-
]
33-
return get_dependency_conflicts(deps)
32+
main_deps = dist.requires()
33+
instrumentation_deps = []
34+
for dep in dist.requires(("instruments",)):
35+
if dep not in main_deps:
36+
# we set marker to none so string representation of the dependency looks like
37+
# requests ~= 1.0
38+
# instead of
39+
# requests ~= 1.0; extra = "instruments"
40+
# which does not work with `get_distribution()`
41+
dep.marker = None
42+
instrumentation_deps.append(str(dep))
43+
44+
return get_dependency_conflicts(instrumentation_deps)
3445

3546

3647
def get_dependency_conflicts(
3748
deps: Collection[str],
3849
) -> Optional[DependencyConflict]:
3950
for dep in deps:
4051
try:
41-
get_distribution(str(dep))
52+
get_distribution(dep)
4253
except VersionConflict as exc:
4354
return DependencyConflict(dep, exc.dist)
4455
except DistributionNotFound:
4556
return DependencyConflict(dep)
57+
except RequirementParseError as exc:
58+
logger.warning(
59+
'error parsing dependency, reporting as a conflict: "%s" - %s',
60+
dep,
61+
exc,
62+
)
63+
return DependencyConflict(dep)
4664
return None

opentelemetry-instrumentation/tests/test_dependencies.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
# pylint: disable=protected-access
1616

17+
import pkg_resources
1718
import pytest
1819

1920
from opentelemetry.instrumentation.dependencies import (
2021
DependencyConflict,
2122
get_dependency_conflicts,
23+
get_dist_dependency_conflicts,
2224
)
2325
from opentelemetry.test.test_base import TestBase
2426

@@ -37,7 +39,6 @@ def test_get_dependency_conflicts_not_installed(self):
3739
conflict = get_dependency_conflicts(["this-package-does-not-exist"])
3840
self.assertTrue(conflict is not None)
3941
self.assertTrue(isinstance(conflict, DependencyConflict))
40-
print(conflict)
4142
self.assertEqual(
4243
str(conflict),
4344
'DependencyConflict: requested: "this-package-does-not-exist" but found: "None"',
@@ -47,10 +48,32 @@ def test_get_dependency_conflicts_mismatched_version(self):
4748
conflict = get_dependency_conflicts(["pytest == 5000"])
4849
self.assertTrue(conflict is not None)
4950
self.assertTrue(isinstance(conflict, DependencyConflict))
50-
print(conflict)
5151
self.assertEqual(
5252
str(conflict),
5353
'DependencyConflict: requested: "pytest == 5000" but found: "pytest {0}"'.format(
5454
pytest.__version__
5555
),
5656
)
57+
58+
def test_get_dist_dependency_conflicts(self):
59+
def mock_requires(extras=()):
60+
if "instruments" in extras:
61+
return [
62+
pkg_resources.Requirement(
63+
'test-pkg ~= 1.0; extra == "instruments"'
64+
)
65+
]
66+
return []
67+
68+
dist = pkg_resources.Distribution(
69+
project_name="test-instrumentation", version="1.0"
70+
)
71+
dist.requires = mock_requires
72+
73+
conflict = get_dist_dependency_conflicts(dist)
74+
self.assertTrue(conflict is not None)
75+
self.assertTrue(isinstance(conflict, DependencyConflict))
76+
self.assertEqual(
77+
str(conflict),
78+
'DependencyConflict: requested: "test-pkg~=1.0" but found: "None"',
79+
)

tests/opentelemetry-docker-tests/tests/check_availability.py

+17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import mysql.connector
1919
import psycopg2
2020
import pymongo
21+
import pyodbc
2122
import redis
2223

2324
MONGODB_COLLECTION_NAME = "test"
@@ -36,6 +37,11 @@
3637
POSTGRES_USER = os.getenv("POSTGRESQL_USER", "testuser")
3738
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
3839
REDIS_PORT = int(os.getenv("REDIS_PORT ", "6379"))
40+
MSSQL_DB_NAME = os.getenv("MSSQL_DB_NAME", "opentelemetry-tests")
41+
MSSQL_HOST = os.getenv("MSSQL_HOST", "localhost")
42+
MSSQL_PORT = int(os.getenv("MSSQL_PORT", "1433"))
43+
MSSQL_USER = os.getenv("MSSQL_USER", "sa")
44+
MSSQL_PASSWORD = os.getenv("MSSQL_PASSWORD", "yourStrong(!)Password")
3945
RETRY_COUNT = 8
4046
RETRY_INTERVAL = 5 # Seconds
4147

@@ -104,12 +110,23 @@ def check_redis_connection():
104110
connection.hgetall("*")
105111

106112

113+
@retryable
114+
def check_mssql_connection():
115+
connection = pyodbc.connect(
116+
f"DRIVER={{ODBC Driver 17 for SQL Server}};SERVER={MSSQL_HOST},"
117+
f"{MSSQL_PORT};DATABASE={MSSQL_DB_NAME};UID={MSSQL_USER};"
118+
f"PWD={MSSQL_PASSWORD}"
119+
)
120+
connection.close()
121+
122+
107123
def check_docker_services_availability():
108124
# Check if Docker services accept connections
109125
check_pymongo_connection()
110126
check_mysql_connection()
111127
check_postgres_connection()
112128
check_redis_connection()
129+
check_mssql_connection()
113130

114131

115132
check_docker_services_availability()

tests/opentelemetry-docker-tests/tests/docker-compose.yml

+8
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,11 @@ services:
3939
- "16686:16686"
4040
- "14268:14268"
4141
- "9411:9411"
42+
otmssql:
43+
image: mcr.microsoft.com/mssql/server:2017-latest
44+
ports:
45+
- "1433:1433"
46+
environment:
47+
ACCEPT_EULA: "Y"
48+
SA_PASSWORD: "yourStrong(!)Password"
49+
command: /bin/sh -c "sleep 10s && /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P yourStrong\(!\)Password -d master -Q 'CREATE DATABASE [opentelemetry-tests]' & /opt/mssql/bin/sqlservr"

0 commit comments

Comments
 (0)