test_opentracing.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # Copyright 2022 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from twisted.internet import defer
  15. from twisted.test.proto_helpers import MemoryReactorClock
  16. from synapse.logging.context import (
  17. LoggingContext,
  18. make_deferred_yieldable,
  19. run_in_background,
  20. )
  21. from synapse.logging.opentracing import (
  22. start_active_span,
  23. start_active_span_follows_from,
  24. )
  25. from synapse.util import Clock
  26. try:
  27. from synapse.logging.scopecontextmanager import LogContextScopeManager
  28. except ImportError:
  29. LogContextScopeManager = None # type: ignore
  30. try:
  31. import jaeger_client
  32. except ImportError:
  33. jaeger_client = None # type: ignore
  34. from tests.unittest import TestCase
  35. class LogContextScopeManagerTestCase(TestCase):
  36. if LogContextScopeManager is None:
  37. skip = "Requires opentracing" # type: ignore[unreachable]
  38. if jaeger_client is None:
  39. skip = "Requires jaeger_client" # type: ignore[unreachable]
  40. def setUp(self) -> None:
  41. # since this is a unit test, we don't really want to mess around with the
  42. # global variables that power opentracing. We create our own tracer instance
  43. # and test with it.
  44. scope_manager = LogContextScopeManager({})
  45. config = jaeger_client.config.Config(
  46. config={}, service_name="test", scope_manager=scope_manager
  47. )
  48. self._reporter = jaeger_client.reporter.InMemoryReporter()
  49. self._tracer = config.create_tracer(
  50. sampler=jaeger_client.ConstSampler(True),
  51. reporter=self._reporter,
  52. )
  53. def test_start_active_span(self) -> None:
  54. # the scope manager assumes a logging context of some sort.
  55. with LoggingContext("root context"):
  56. self.assertIsNone(self._tracer.active_span)
  57. # start_active_span should start and activate a span.
  58. scope = start_active_span("span", tracer=self._tracer)
  59. span = scope.span
  60. self.assertEqual(self._tracer.active_span, span)
  61. self.assertIsNotNone(span.start_time)
  62. # entering the context doesn't actually do a whole lot.
  63. with scope as ctx:
  64. self.assertIs(ctx, scope)
  65. self.assertEqual(self._tracer.active_span, span)
  66. # ... but leaving it unsets the active span, and finishes the span.
  67. self.assertIsNone(self._tracer.active_span)
  68. self.assertIsNotNone(span.end_time)
  69. # the span should have been reported
  70. self.assertEqual(self._reporter.get_spans(), [span])
  71. def test_nested_spans(self) -> None:
  72. """Starting two spans off inside each other should work"""
  73. with LoggingContext("root context"):
  74. with start_active_span("root span", tracer=self._tracer) as root_scope:
  75. self.assertEqual(self._tracer.active_span, root_scope.span)
  76. scope1 = start_active_span(
  77. "child1",
  78. tracer=self._tracer,
  79. )
  80. self.assertEqual(
  81. self._tracer.active_span, scope1.span, "child1 was not activated"
  82. )
  83. self.assertEqual(
  84. scope1.span.context.parent_id, root_scope.span.context.span_id
  85. )
  86. scope2 = start_active_span_follows_from(
  87. "child2",
  88. contexts=(scope1,),
  89. tracer=self._tracer,
  90. )
  91. self.assertEqual(self._tracer.active_span, scope2.span)
  92. self.assertEqual(
  93. scope2.span.context.parent_id, scope1.span.context.span_id
  94. )
  95. with scope1, scope2:
  96. pass
  97. # the root scope should be restored
  98. self.assertEqual(self._tracer.active_span, root_scope.span)
  99. self.assertIsNotNone(scope2.span.end_time)
  100. self.assertIsNotNone(scope1.span.end_time)
  101. self.assertIsNone(self._tracer.active_span)
  102. # the spans should be reported in order of their finishing.
  103. self.assertEqual(
  104. self._reporter.get_spans(), [scope2.span, scope1.span, root_scope.span]
  105. )
  106. def test_overlapping_spans(self) -> None:
  107. """Overlapping spans which are not neatly nested should work"""
  108. reactor = MemoryReactorClock()
  109. clock = Clock(reactor)
  110. scopes = []
  111. async def task(i: int):
  112. scope = start_active_span(
  113. f"task{i}",
  114. tracer=self._tracer,
  115. )
  116. scopes.append(scope)
  117. self.assertEqual(self._tracer.active_span, scope.span)
  118. await clock.sleep(4)
  119. self.assertEqual(self._tracer.active_span, scope.span)
  120. scope.close()
  121. async def root():
  122. with start_active_span("root span", tracer=self._tracer) as root_scope:
  123. self.assertEqual(self._tracer.active_span, root_scope.span)
  124. scopes.append(root_scope)
  125. d1 = run_in_background(task, 1)
  126. await clock.sleep(2)
  127. d2 = run_in_background(task, 2)
  128. # because we did run_in_background, the active span should still be the
  129. # root.
  130. self.assertEqual(self._tracer.active_span, root_scope.span)
  131. await make_deferred_yieldable(
  132. defer.gatherResults([d1, d2], consumeErrors=True)
  133. )
  134. self.assertEqual(self._tracer.active_span, root_scope.span)
  135. with LoggingContext("root context"):
  136. # start the test off
  137. d1 = defer.ensureDeferred(root())
  138. # let the tasks complete
  139. reactor.pump((2,) * 8)
  140. self.successResultOf(d1)
  141. self.assertIsNone(self._tracer.active_span)
  142. # the spans should be reported in order of their finishing: task 1, task 2,
  143. # root.
  144. self.assertEqual(
  145. self._reporter.get_spans(),
  146. [scopes[1].span, scopes[2].span, scopes[0].span],
  147. )