Skip to content

Commit d3071db

Browse files
committed
PYTHON-5945 Add regression test for OpenTelemetry tracer caching
Locks in that pymongo._otel only calls opentelemetry's get_tracer() once, at import, rather than per command.
1 parent 936cf7e commit d3071db

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

test/asynchronous/test_otel.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,5 +351,38 @@ def test_rejects_negative_query_text_max_length(self):
351351
common.validate_tracing_or_none("tracing", {"query_text_max_length": -1})
352352

353353

354+
class TestOTelTracerCaching(unittest.TestCase):
355+
"""Regression test for the tracer-caching fix in ``pymongo/_otel.py``.
356+
357+
``opentelemetry.trace.get_tracer()`` must only be called once, at import
358+
time (cached as module-level ``_otel._TRACER``). Calling it per command
359+
allocates two objects, takes a process-wide lock, and mutates the global
360+
``warnings`` filter list on every call, even on a cache hit.
361+
"""
362+
363+
@unittest.skipUnless(_otel._HAS_OPENTELEMETRY, "opentelemetry is not installed")
364+
def test_start_command_span_does_not_call_get_tracer(self):
365+
class _FakeConn:
366+
id = 1
367+
server_connection_id: Optional[int] = None
368+
address: _Address = ("localhost", 27017)
369+
service_id = None
370+
371+
with patch.object(_otel, "trace") as mock_trace:
372+
for _ in range(3):
373+
span = _otel.start_command_span(
374+
{"enabled": True, "query_text_max_length": None},
375+
_FakeConn(),
376+
{"ping": 1},
377+
"admin",
378+
"ping",
379+
False,
380+
)
381+
self.assertIsNotNone(span)
382+
span.end()
383+
384+
mock_trace.get_tracer.assert_not_called()
385+
386+
354387
if __name__ == "__main__":
355388
unittest.main()

test/test_otel.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,5 +345,38 @@ def test_rejects_negative_query_text_max_length(self):
345345
common.validate_tracing_or_none("tracing", {"query_text_max_length": -1})
346346

347347

348+
class TestOTelTracerCaching(unittest.TestCase):
349+
"""Regression test for the tracer-caching fix in ``pymongo/_otel.py``.
350+
351+
``opentelemetry.trace.get_tracer()`` must only be called once, at import
352+
time (cached as module-level ``_otel._TRACER``). Calling it per command
353+
allocates two objects, takes a process-wide lock, and mutates the global
354+
``warnings`` filter list on every call, even on a cache hit.
355+
"""
356+
357+
@unittest.skipUnless(_otel._HAS_OPENTELEMETRY, "opentelemetry is not installed")
358+
def test_start_command_span_does_not_call_get_tracer(self):
359+
class _FakeConn:
360+
id = 1
361+
server_connection_id: Optional[int] = None
362+
address: _Address = ("localhost", 27017)
363+
service_id = None
364+
365+
with patch.object(_otel, "trace") as mock_trace:
366+
for _ in range(3):
367+
span = _otel.start_command_span(
368+
{"enabled": True, "query_text_max_length": None},
369+
_FakeConn(),
370+
{"ping": 1},
371+
"admin",
372+
"ping",
373+
False,
374+
)
375+
self.assertIsNotNone(span)
376+
span.end()
377+
378+
mock_trace.get_tracer.assert_not_called()
379+
380+
348381
if __name__ == "__main__":
349382
unittest.main()

0 commit comments

Comments
 (0)