Skip to content

Commit 59205e3

Browse files
committed
Merge branch 'open-telemetry:main' into context-key-contrib
2 parents 99e17bd + 5b125b1 commit 59205e3

File tree

6 files changed

+119
-17
lines changed

6 files changed

+119
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ 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+
- `opentelemetry-instrumentation` Fixed cases where trying to use an instrumentation package without the
10+
target library was crashing auto instrumentation agent.
11+
([#530](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/530))
12+
913
## [0.22b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.3.0-0.22b0) - 2021-06-01
1014

1115
### Changed

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

Lines changed: 25 additions & 7 deletions
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

Lines changed: 25 additions & 2 deletions
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+
)

scripts/generate_instrumentation_bootstrap.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import logging
1919
import os
2020
import subprocess
21+
import sys
2122

2223
import astor
2324
import pkg_resources
@@ -91,7 +92,18 @@ def main():
9192
with open(gen_path, "w") as gen_file:
9293
gen_file.write(source)
9394

94-
subprocess.run(["black", "-q", gen_path], check=True)
95+
subprocess.run(
96+
[
97+
sys.executable,
98+
"scripts/eachdist.py",
99+
"format",
100+
"--path",
101+
"opentelemetry-instrumentation",
102+
],
103+
check=True,
104+
)
105+
106+
logger.info("generated %s", gen_path)
95107

96108

97109
if __name__ == "__main__":

scripts/otel_packaging.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ def get_instrumentation_packages():
2727
if not os.path.isdir(pkg_path):
2828
continue
2929

30-
out = str(
31-
subprocess.check_output(
32-
"python setup.py meta", shell=True, cwd=pkg_path
33-
)
30+
out = subprocess.check_output(
31+
"python setup.py meta",
32+
shell=True,
33+
cwd=pkg_path,
34+
universal_newlines=True,
3435
)
35-
instrumentation = json.loads(out.split("\\n")[1])
36+
instrumentation = json.loads(out.splitlines()[1])
3637
instrumentation["requirement"] = "==".join(
3738
(instrumentation["name"], instrumentation["version"],)
3839
)

tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
# limitations under the License.
1414

1515
import contextlib
16+
import logging
17+
import threading
1618

17-
from sqlalchemy import Column, Integer, String, create_engine
19+
from sqlalchemy import Column, Integer, String, create_engine, insert
1820
from sqlalchemy.ext.declarative import declarative_base
19-
from sqlalchemy.orm import sessionmaker
21+
from sqlalchemy.orm import close_all_sessions, scoped_session, sessionmaker
2022

2123
from opentelemetry import trace
2224
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
@@ -199,3 +201,45 @@ def test_parent(self):
199201
self.assertEqual(parent_span.instrumentation_info.name, "sqlalch_svc")
200202

201203
self.assertEqual(child_span.name, "SELECT " + self.SQL_DB)
204+
205+
def test_multithreading(self):
206+
"""Ensure spans are captured correctly in a multithreading scenario
207+
208+
We also expect no logged warnings about calling end() on an ended span.
209+
"""
210+
211+
if self.VENDOR == "sqlite":
212+
return
213+
214+
def insert_player(session):
215+
_session = session()
216+
player = Player(name="Player")
217+
_session.add(player)
218+
_session.commit()
219+
_session.query(Player).all()
220+
221+
def insert_players(session):
222+
_session = session()
223+
players = []
224+
for player_number in range(3):
225+
players.append(Player(name=f"Player {player_number}"))
226+
_session.add_all(players)
227+
_session.commit()
228+
229+
session_factory = sessionmaker(bind=self.engine)
230+
# pylint: disable=invalid-name
231+
Session = scoped_session(session_factory)
232+
thread_one = threading.Thread(target=insert_player, args=(Session,))
233+
thread_two = threading.Thread(target=insert_players, args=(Session,))
234+
235+
logger = logging.getLogger("opentelemetry.sdk.trace")
236+
with self.assertRaises(AssertionError):
237+
with self.assertLogs(logger, level="WARNING"):
238+
thread_one.start()
239+
thread_two.start()
240+
thread_one.join()
241+
thread_two.join()
242+
close_all_sessions()
243+
244+
spans = self.memory_exporter.get_finished_spans()
245+
self.assertEqual(len(spans), 5)

0 commit comments

Comments
 (0)