|
1 | 1 | # Copyright The OpenTelemetry Authors |
2 | 2 | # SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
| 4 | +import contextlib |
4 | 5 | from unittest import mock |
5 | 6 |
|
6 | 7 | from opentelemetry import trace as trace_api |
| 8 | +from opentelemetry.instrumentation._semconv import ( |
| 9 | + OTEL_SEMCONV_STABILITY_OPT_IN, |
| 10 | + _OpenTelemetrySemanticConventionStability, |
| 11 | + _StabilityMode, |
| 12 | +) |
7 | 13 | from opentelemetry.instrumentation.pymongo import ( |
8 | 14 | CommandTracer, |
9 | 15 | PymongoInstrumentor, |
|
19 | 25 | NET_PEER_NAME, |
20 | 26 | NET_PEER_PORT, |
21 | 27 | ) |
| 28 | +from opentelemetry.semconv.attributes.db_attributes import ( |
| 29 | + DB_COLLECTION_NAME, |
| 30 | + DB_NAMESPACE, |
| 31 | + DB_QUERY_TEXT, |
| 32 | + DB_SYSTEM_NAME, |
| 33 | +) |
| 34 | +from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE |
| 35 | +from opentelemetry.semconv.attributes.server_attributes import ( |
| 36 | + SERVER_ADDRESS, |
| 37 | + SERVER_PORT, |
| 38 | +) |
22 | 39 | from opentelemetry.test.test_base import TestBase |
23 | 40 |
|
24 | 41 |
|
| 42 | +@contextlib.contextmanager |
| 43 | +def use_semconv_opt_in(sem_conv_mode): |
| 44 | + env_patch = mock.patch.dict( |
| 45 | + "os.environ", |
| 46 | + {OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode}, |
| 47 | + ) |
| 48 | + _OpenTelemetrySemanticConventionStability._initialized = False |
| 49 | + env_patch.start() |
| 50 | + try: |
| 51 | + yield |
| 52 | + finally: |
| 53 | + env_patch.stop() |
| 54 | + _OpenTelemetrySemanticConventionStability._initialized = False |
| 55 | + |
| 56 | + |
| 57 | +# pylint: disable=too-many-public-methods |
25 | 58 | class TestPymongo(TestBase): |
26 | 59 | def setUp(self): |
27 | 60 | super().setUp() |
28 | 61 | self.tracer = self.tracer_provider.get_tracer(__name__) |
29 | 62 | self.start_callback = mock.MagicMock() |
30 | 63 | self.success_callback = mock.MagicMock() |
31 | 64 | self.failed_callback = mock.MagicMock() |
| 65 | + PymongoInstrumentor._instance = None |
| 66 | + PymongoInstrumentor._commandtracer_instance = None |
| 67 | + _OpenTelemetrySemanticConventionStability._initialized = False |
| 68 | + |
| 69 | + def tearDown(self): |
| 70 | + super().tearDown() |
| 71 | + PymongoInstrumentor._instance = None |
| 72 | + PymongoInstrumentor._commandtracer_instance = None |
| 73 | + _OpenTelemetrySemanticConventionStability._initialized = False |
32 | 74 |
|
33 | 75 | def test_pymongo_instrumentor(self): |
34 | 76 | mock_register = mock.Mock() |
@@ -60,7 +102,7 @@ def test_started(self): |
60 | 102 | self.assertEqual(span.attributes[DB_NAME], "database_name") |
61 | 103 | self.assertEqual(span.attributes[DB_STATEMENT], "find") |
62 | 104 | self.assertEqual(span.attributes[NET_PEER_NAME], "test.com") |
63 | | - self.assertEqual(span.attributes[NET_PEER_PORT], "1234") |
| 105 | + self.assertEqual(span.attributes[NET_PEER_PORT], 1234) |
64 | 106 | self.start_callback.assert_called_once_with(span, mock_event) |
65 | 107 |
|
66 | 108 | def test_succeeded(self): |
@@ -301,6 +343,139 @@ def test_collection_name_attribute(self): |
301 | 343 | ) |
302 | 344 | self.memory_exporter.clear() |
303 | 345 |
|
| 346 | + def test_schema_url_default(self): |
| 347 | + with mock.patch("pymongo.monitoring.register"): |
| 348 | + instrumentor = PymongoInstrumentor() |
| 349 | + instrumentor.instrument(tracer_provider=self.tracer_provider) |
| 350 | + self.assertEqual( |
| 351 | + instrumentor._commandtracer_instance._tracer._instrumentation_scope.schema_url, |
| 352 | + "https://opentelemetry.io/schemas/1.11.0", |
| 353 | + ) |
| 354 | + |
| 355 | + def test_schema_url_new_semconv(self): |
| 356 | + with use_semconv_opt_in("database"): |
| 357 | + with mock.patch("pymongo.monitoring.register"): |
| 358 | + instrumentor = PymongoInstrumentor() |
| 359 | + instrumentor.instrument(tracer_provider=self.tracer_provider) |
| 360 | + self.assertEqual( |
| 361 | + instrumentor._commandtracer_instance._tracer._instrumentation_scope.schema_url, |
| 362 | + "https://opentelemetry.io/schemas/1.25.0", |
| 363 | + ) |
| 364 | + |
| 365 | + def test_started_new_semconv(self): |
| 366 | + command_tracer = CommandTracer( |
| 367 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE |
| 368 | + ) |
| 369 | + mock_event = MockEvent({"command_name": "find"}, ("testhost", 1234)) |
| 370 | + command_tracer.started(event=mock_event) |
| 371 | + span = command_tracer._pop_span(mock_event) # pylint: disable=protected-access |
| 372 | + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "mongodb") |
| 373 | + self.assertEqual(span.attributes[DB_NAMESPACE], "database_name") |
| 374 | + self.assertEqual(span.attributes[DB_QUERY_TEXT], "find") |
| 375 | + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") |
| 376 | + self.assertEqual(span.attributes[SERVER_PORT], 1234) |
| 377 | + |
| 378 | + def test_started_both_semconv(self): |
| 379 | + command_tracer = CommandTracer( |
| 380 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE_DUP |
| 381 | + ) |
| 382 | + mock_event = MockEvent({"command_name": "find"}, ("testhost", 1234)) |
| 383 | + command_tracer.started(event=mock_event) |
| 384 | + span = command_tracer._pop_span(mock_event) # pylint: disable=protected-access |
| 385 | + self.assertEqual(span.attributes[DB_SYSTEM], "mongodb") |
| 386 | + self.assertEqual(span.attributes[DB_NAME], "database_name") |
| 387 | + self.assertEqual(span.attributes[DB_STATEMENT], "find") |
| 388 | + self.assertEqual(span.attributes[NET_PEER_NAME], "testhost") |
| 389 | + self.assertEqual(span.attributes[NET_PEER_PORT], 1234) |
| 390 | + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "mongodb") |
| 391 | + self.assertEqual(span.attributes[DB_NAMESPACE], "database_name") |
| 392 | + self.assertEqual(span.attributes[DB_QUERY_TEXT], "find") |
| 393 | + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") |
| 394 | + self.assertEqual(span.attributes[SERVER_PORT], 1234) |
| 395 | + |
| 396 | + def test_collection_name_default_semconv(self): |
| 397 | + command_tracer = CommandTracer(self.tracer) |
| 398 | + mock_event = MockEvent({"command_name": "find", "find": "test_coll"}) |
| 399 | + command_tracer.started(event=mock_event) |
| 400 | + command_tracer.succeeded(event=mock_event) |
| 401 | + spans = self.memory_exporter.get_finished_spans() |
| 402 | + self.assertEqual(len(spans), 1) |
| 403 | + span = spans[0] |
| 404 | + self.assertEqual(span.attributes[DB_MONGODB_COLLECTION], "test_coll") |
| 405 | + self.assertNotIn(DB_COLLECTION_NAME, span.attributes) |
| 406 | + |
| 407 | + def test_collection_name_new_semconv(self): |
| 408 | + command_tracer = CommandTracer( |
| 409 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE |
| 410 | + ) |
| 411 | + mock_event = MockEvent({"command_name": "find", "find": "test_coll"}) |
| 412 | + command_tracer.started(event=mock_event) |
| 413 | + command_tracer.succeeded(event=mock_event) |
| 414 | + spans = self.memory_exporter.get_finished_spans() |
| 415 | + self.assertEqual(len(spans), 1) |
| 416 | + span = spans[0] |
| 417 | + self.assertEqual(span.attributes[DB_COLLECTION_NAME], "test_coll") |
| 418 | + self.assertNotIn(DB_MONGODB_COLLECTION, span.attributes) |
| 419 | + |
| 420 | + def test_collection_name_both_semconv(self): |
| 421 | + command_tracer = CommandTracer( |
| 422 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE_DUP |
| 423 | + ) |
| 424 | + mock_event = MockEvent({"command_name": "find", "find": "test_coll"}) |
| 425 | + command_tracer.started(event=mock_event) |
| 426 | + command_tracer.succeeded(event=mock_event) |
| 427 | + spans = self.memory_exporter.get_finished_spans() |
| 428 | + self.assertEqual(len(spans), 1) |
| 429 | + span = spans[0] |
| 430 | + self.assertEqual(span.attributes[DB_MONGODB_COLLECTION], "test_coll") |
| 431 | + self.assertEqual(span.attributes[DB_COLLECTION_NAME], "test_coll") |
| 432 | + |
| 433 | + def test_failed_error_type_not_set_on_default(self): |
| 434 | + command_tracer = CommandTracer(self.tracer) |
| 435 | + mock_event = MockEvent({}) |
| 436 | + command_tracer.started(event=mock_event) |
| 437 | + mock_event.mark_as_failed() |
| 438 | + command_tracer.failed(event=mock_event) |
| 439 | + spans = self.memory_exporter.get_finished_spans() |
| 440 | + self.assertEqual(len(spans), 1) |
| 441 | + span = spans[0] |
| 442 | + self.assertFalse(span.status.is_ok) |
| 443 | + self.assertNotIn(ERROR_TYPE, span.attributes) |
| 444 | + |
| 445 | + def test_failed_error_type_set_on_new_semconv(self): |
| 446 | + command_tracer = CommandTracer( |
| 447 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE |
| 448 | + ) |
| 449 | + mock_event = MockEvent({}) |
| 450 | + command_tracer.started(event=mock_event) |
| 451 | + mock_event.failure = { |
| 452 | + "errmsg": "operation failed", |
| 453 | + "codeName": "OperationFailed", |
| 454 | + } |
| 455 | + command_tracer.failed(event=mock_event) |
| 456 | + spans = self.memory_exporter.get_finished_spans() |
| 457 | + self.assertEqual(len(spans), 1) |
| 458 | + span = spans[0] |
| 459 | + self.assertFalse(span.status.is_ok) |
| 460 | + self.assertEqual(span.attributes[ERROR_TYPE], "OperationFailed") |
| 461 | + |
| 462 | + def test_failed_error_type_set_on_both_semconv(self): |
| 463 | + command_tracer = CommandTracer( |
| 464 | + self.tracer, semconv_opt_in_mode=_StabilityMode.DATABASE_DUP |
| 465 | + ) |
| 466 | + mock_event = MockEvent({}) |
| 467 | + command_tracer.started(event=mock_event) |
| 468 | + mock_event.failure = { |
| 469 | + "errmsg": "operation failed", |
| 470 | + "codeName": "OperationFailed", |
| 471 | + } |
| 472 | + command_tracer.failed(event=mock_event) |
| 473 | + spans = self.memory_exporter.get_finished_spans() |
| 474 | + self.assertEqual(len(spans), 1) |
| 475 | + span = spans[0] |
| 476 | + self.assertFalse(span.status.is_ok) |
| 477 | + self.assertEqual(span.attributes[ERROR_TYPE], "OperationFailed") |
| 478 | + |
304 | 479 |
|
305 | 480 | class MockCommand: |
306 | 481 | def __init__(self, command_attrs): |
|
0 commit comments