File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff 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+
354387if __name__ == "__main__" :
355388 unittest .main ()
Original file line number Diff line number Diff 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+
348381if __name__ == "__main__" :
349382 unittest .main ()
You can’t perform that action at this time.
0 commit comments