Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 635599e

Browse files
wkiserliyanhui1228
authored andcommitted
Update ContextTracer to flush leftover spans on finish (#150)
1 parent c0ea828 commit 635599e

3 files changed

Lines changed: 46 additions & 31 deletions

File tree

opencensus/trace/tracers/context_tracer.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import logging
16+
import threading
1617

1718
from opencensus.trace import execution_context
1819
from opencensus.trace.span_context import SpanContext
@@ -42,6 +43,7 @@ def __init__(self, exporter=None, span_context=None):
4243
self.trace_id = span_context.trace_id
4344
self.root_span_id = span_context.span_id
4445

46+
self._spans_list_condition = threading.Condition()
4547
# List of spans to report
4648
self._spans_list = []
4749

@@ -51,7 +53,8 @@ def finish(self):
5153
:rtype: dict
5254
:returns: JSON format trace.
5355
"""
54-
self._spans_list = []
56+
while self._spans_list:
57+
self.end_span()
5558

5659
def span(self, name='span'):
5760
"""Create a new span with the trace using the context information.
@@ -86,7 +89,8 @@ def start_span(self, name='span'):
8689
name,
8790
parent_span=parent_span,
8891
context_tracer=self)
89-
self._spans_list.append(span)
92+
with self._spans_list_condition:
93+
self._spans_list.append(span)
9094
self.span_context.span_id = span.span_id
9195
execution_context.set_current_span(span)
9296
span.start()
@@ -97,21 +101,27 @@ def end_span(self, *args, **kwargs):
97101
parent span id; Update the current span.
98102
"""
99103
cur_span = self.current_span()
104+
if cur_span is None and self._spans_list:
105+
cur_span = self._spans_list[-1]
100106

101107
if cur_span is None:
102108
logging.warning('No active span, cannot do end_span.')
103109
return
104110

105111
cur_span.finish()
106-
self.span_context.span_id = cur_span.parent_span.span_id
112+
self.span_context.span_id = cur_span.parent_span.span_id if \
113+
cur_span.parent_span else None
107114

108115
if isinstance(cur_span.parent_span, trace_span.Span):
109116
execution_context.set_current_span(cur_span.parent_span)
110117
else:
111118
execution_context.set_current_span(None)
112119

113-
span_datas = self.get_span_datas(cur_span)
114-
self.exporter.export(span_datas)
120+
with self._spans_list_condition:
121+
if cur_span in self._spans_list:
122+
span_datas = self.get_span_datas(cur_span)
123+
self.exporter.export(span_datas)
124+
self._spans_list.remove(cur_span)
115125

116126
return cur_span
117127

@@ -148,7 +158,8 @@ def get_span_datas(self, span):
148158
name=span.name,
149159
context=self.span_context,
150160
span_id=span.span_id,
151-
parent_span_id=span.parent_span.span_id,
161+
parent_span_id=span.parent_span.span_id if
162+
span.parent_span else None,
152163
attributes=span.attributes,
153164
start_time=span.start_time,
154165
end_time=span.end_time,

tests/unit/trace/test_tracer.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import mock
1818

1919
from opencensus.trace import tracer as tracer_module
20+
from opencensus.trace import span_data
2021

2122

2223
class TestTracer(unittest.TestCase):
@@ -246,7 +247,8 @@ def test_add_attribute_to_current_span_not_sampled(self):
246247
assert isinstance(span, base.NullContextManager)
247248

248249
def test_trace_decorator(self):
249-
tracer = tracer_module.Tracer()
250+
mock_exporter = mock.MagicMock()
251+
tracer = tracer_module.Tracer(exporter=mock_exporter)
250252

251253
return_value = "test"
252254

@@ -256,6 +258,8 @@ def test_decorator():
256258

257259
returned = test_decorator()
258260

259-
self.assertEqual(len(tracer.tracer._spans_list), 1)
260-
self.assertEqual(tracer.tracer._spans_list[0].name, 'test_decorator')
261261
self.assertEqual(returned, return_value)
262+
self.assertEqual(mock_exporter.export.call_count, 1)
263+
exported_spandata = mock_exporter.export.call_args[0][0][0]
264+
self.assertIsInstance(exported_spandata, span_data.SpanData)
265+
self.assertEqual(exported_spandata.name, 'test_decorator')

tests/unit/trace/tracers/test_context_tracer.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717
import mock
1818

1919
from opencensus.trace.tracers import context_tracer
20+
from opencensus.trace import span
21+
from opencensus.trace import execution_context
2022

2123

2224
class TestContextTracer(unittest.TestCase):
25+
26+
def tearDown(self):
27+
execution_context.clear()
28+
2329
def test_constructor_defaults(self):
2430
from opencensus.trace import span_context
2531
from opencensus.trace.exporters import print_exporter
@@ -58,7 +64,14 @@ def test_finish_without_spans(self):
5864

5965
def test_finish_with_spans(self):
6066
tracer = context_tracer.ContextTracer()
61-
tracer._spans_list = [mock.Mock()]
67+
tracer.start_span('span')
68+
tracer.finish()
69+
70+
self.assertEqual(tracer._spans_list, [])
71+
72+
def test_end_leftover_spans(self):
73+
tracer = context_tracer.ContextTracer()
74+
tracer._spans_list = [span.Span(name='span')]
6275
tracer.finish()
6376

6477
self.assertEqual(tracer._spans_list, [])
@@ -132,7 +145,7 @@ def test_end_span_active(self, mock_current_span):
132145

133146
self.assertTrue(mock_span.finish.called)
134147
self.assertEqual(tracer.span_context.span_id, parent_span_id)
135-
self.assertTrue(tracer.exporter.export.called)
148+
self.assertFalse(tracer.exporter.export.called)
136149

137150
@mock.patch.object(context_tracer.ContextTracer, 'current_span')
138151
def test_end_span_without_parent(self, mock_current_span):
@@ -155,30 +168,16 @@ def test_end_span_without_parent(self, mock_current_span):
155168
cur_span = get_current_span()
156169
self.assertIsNone(cur_span)
157170

158-
@mock.patch.object(context_tracer.ContextTracer, 'current_span')
159-
def test_end_span_batch_export(self, mock_current_span):
160-
from opencensus.trace import span
161-
162-
span = span.Span(name='test')
171+
def test_end_span_batch_export(self):
163172
exporter = mock.Mock()
164173
tracer = context_tracer.ContextTracer(exporter=exporter)
165-
tracer._spans_list = [span]
166-
mock_span = mock.Mock()
167-
mock_span.name = 'span'
168-
mock_span.children = []
169-
mock_span.status = None
170-
mock_span.links = None
171-
mock_span.stack_trace = None
172-
mock_span.time_events = None
173-
mock_span.attributes = {}
174-
mock_span.__iter__ = mock.Mock(
175-
return_value=iter([mock_span]))
174+
span = tracer.start_span('test')
176175
parent_span_id = '6e0c63257de34c92'
177-
mock_span.parent_span.span_id = parent_span_id
178-
mock_current_span.return_value = mock_span
176+
span.parent_span.span_id = parent_span_id
177+
span.finish = mock.Mock()
179178
tracer.end_span()
180179

181-
self.assertTrue(mock_span.finish.called)
180+
self.assertTrue(span.finish.called)
182181
self.assertEqual(tracer.span_context.span_id, parent_span_id)
183182
self.assertTrue(tracer.exporter.export.called)
184183

@@ -208,4 +207,5 @@ def test_add_attribute_to_current_span(self):
208207

209208
tracer.add_attribute_to_current_span(attribute_key, attribute_value)
210209

211-
span1.add_attribute.assert_called_once_with(attribute_key, attribute_value)
210+
span1.add_attribute.assert_called_once_with(attribute_key,
211+
attribute_value)

0 commit comments

Comments
 (0)