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

Commit a155f47

Browse files
authored
Remove span_stack and store parent_span in span object (#52)
1 parent 3e62e59 commit a155f47

9 files changed

Lines changed: 97 additions & 62 deletions

File tree

trace/opencensus/trace/execution_context.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def get_opencensus_attr(attr_key):
4747
return None
4848

4949

50+
def get_current_span():
51+
return getattr(_thread_local, 'current_span', None)
52+
53+
54+
def set_current_span(current_span):
55+
setattr(_thread_local, 'current_span', current_span)
56+
57+
5058
def clear():
5159
"""Clear the thread local, used in test."""
5260
_thread_local.__dict__.clear()

trace/opencensus/trace/span.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from opencensus.trace.enums import Enum
1919
from opencensus.trace.span_context import generate_span_id
20+
from opencensus.trace.tracer import base
2021

2122

2223
class Span(object):
@@ -37,8 +38,8 @@ class Span(object):
3738
distinguished using RPC_CLIENT and RPC_SERVER to identify
3839
queueing latency associated with the span.
3940
40-
:type parent_span_id: int
41-
:param parent_span_id: (Optional) ID of the parent span.
41+
:type parent_span: :class:`~opencensus.trace.span.Span`
42+
:param parent_span: (Optional) Parent span.
4243
4344
:type labels: dict
4445
:param labels: Collection of labels associated with the span.
@@ -70,15 +71,15 @@ def __init__(
7071
self,
7172
name,
7273
kind=Enum.SpanKind.SPAN_KIND_UNSPECIFIED,
73-
parent_span_id=None,
74+
parent_span=None,
7475
labels=None,
7576
start_time=None,
7677
end_time=None,
7778
span_id=None,
7879
context_tracer=None):
7980
self.name = name
8081
self.kind = kind
81-
self.parent_span_id = parent_span_id
82+
self.parent_span = parent_span
8283
self.start_time = start_time
8384
self.end_time = end_time
8485

@@ -88,6 +89,11 @@ def __init__(
8889
if labels is None:
8990
labels = {}
9091

92+
# Do not manipulate spans directly using the methods in Span Class,
93+
# make sure to use the RequestTracer.
94+
if parent_span is None:
95+
parent_span = base.NullContextManager()
96+
9197
self.labels = labels
9298
self.span_id = span_id
9399
self._child_spans = []
@@ -108,7 +114,7 @@ def span(self, name='child_span'):
108114
:rtype: :class: `~opencensus.trace.span.Span`
109115
:returns: A child Span to be added to the current span.
110116
"""
111-
child_span = Span(name, parent_span_id=self.span_id)
117+
child_span = Span(name, parent_span=self)
112118
self._child_spans.append(child_span)
113119
return child_span
114120

@@ -168,8 +174,8 @@ def format_span_json(span):
168174
'endTime': span.end_time,
169175
}
170176

171-
if span.parent_span_id is not None:
172-
span_json['parentSpanId'] = span.parent_span_id
177+
if span.parent_span is not None:
178+
span_json['parentSpanId'] = span.parent_span.span_id
173179

174180
if span.labels is not None:
175181
span_json['labels'] = span.labels

trace/opencensus/trace/tracer/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ class NullContextManager(object):
7878
"""Empty object as a helper for faking Trace and Span when tracing is
7979
disabled.
8080
"""
81-
def __init__(self):
81+
def __init__(self, span_id=None):
8282
self.name = None
83+
self.span_id = span_id
8384

8485
def __enter__(self):
8586
pass # pragma: NO COVER

trace/opencensus/trace/tracer/context_tracer.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import logging
1616

17+
from opencensus.trace import execution_context
1718
from opencensus.trace.span_context import SpanContext
1819
from opencensus.trace import labels_helper
1920
from opencensus.trace import span as trace_span
@@ -35,9 +36,6 @@ def __init__(self, span_context=None):
3536
self.trace_id = span_context.trace_id
3637
self.root_span_id = span_context.span_id
3738

38-
# Stack which maintains nested spans
39-
self._span_stack = []
40-
4139
# List of spans to report
4240
self._spans_list = []
4341

@@ -76,14 +74,21 @@ def start_span(self, name='span'):
7674
:rtype: :class:`~opencensus.trace.span.Span`
7775
:returns: The Span object.
7876
"""
79-
parent_span_id = self.span_context.span_id
77+
parent_span = self.current_span()
78+
79+
# If a span has remote parent span, then the parent_span.span_id
80+
# should be the span_id from the request header.
81+
if parent_span is None:
82+
parent_span = base.NullContextManager(
83+
span_id=self.span_context.span_id)
84+
8085
span = trace_span.Span(
8186
name,
82-
parent_span_id=parent_span_id,
87+
parent_span=parent_span,
8388
context_tracer=self)
8489
self._spans_list.append(span)
85-
self._span_stack.append(span)
8690
self.span_context.span_id = span.span_id
91+
execution_context.set_current_span(span)
8792
span.start()
8893
return span
8994

@@ -94,23 +99,23 @@ def end_span(self):
9499
"""
95100
cur_span = self.current_span()
96101

97-
self._span_stack.pop()
102+
if cur_span is None:
103+
logging.warning('No active span, cannot do end_span.')
104+
return
105+
98106
cur_span.finish()
107+
self.span_context.span_id = cur_span.parent_span.span_id
99108

100-
if not self._span_stack:
101-
self.span_context.span_id = self.root_span_id
109+
if isinstance(cur_span.parent_span, trace_span.Span):
110+
execution_context.set_current_span(cur_span.parent_span)
102111
else:
103-
self.span_context.span_id = self._span_stack[-1].span_id
112+
execution_context.set_current_span(None)
104113

105114
def current_span(self):
106115
"""Return the current span."""
107-
try:
108-
cur_span = self._span_stack[-1]
109-
except IndexError:
110-
logging.error('No span in the span stack.')
111-
cur_span = base.NullContextManager()
116+
current_span = execution_context.get_current_span()
112117

113-
return cur_span
118+
return current_span
114119

115120
def list_collected_spans(self):
116121
return self._spans_list

trace/tests/unit/ext/django/test_middleware.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def test_process_request(self):
187187
'/http/method': 'GET',
188188
}
189189
self.assertEqual(span.labels, expected_labels)
190-
self.assertEqual(span.parent_span_id, span_id)
190+
self.assertEqual(span.parent_span.span_id, span_id)
191191

192192
span_context = tracer.span_context
193193
self.assertEqual(span_context.trace_id, trace_id)

trace/tests/unit/ext/flask/test_flask_middleware.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def test__before_request(self):
104104
}
105105

106106
self.assertEqual(span.labels, expected_labels)
107-
self.assertEqual(span.parent_span_id, span_id)
107+
self.assertEqual(span.parent_span.span_id, span_id)
108108

109109
span_context = tracer.span_context
110110
self.assertEqual(span_context.trace_id, trace_id)
@@ -116,6 +116,7 @@ def test_header_encoding(self):
116116
# in SpanContext because it cannot match the pattern for trace_id,
117117
# And a new trace_id will generate for the context.
118118
from opencensus.trace import execution_context
119+
from opencensus.trace.tracer import base
119120

120121
flask_trace_header = 'X_CLOUD_TRACE_CONTEXT'
121122
trace_id = "你好"
@@ -141,13 +142,14 @@ def test_header_encoding(self):
141142
}
142143

143144
self.assertEqual(span.labels, expected_labels)
144-
self.assertIsNone(span.parent_span_id)
145+
assert isinstance(span.parent_span, base.NullContextManager)
145146

146147
span_context = tracer.span_context
147148
self.assertNotEqual(span_context.trace_id, trace_id)
148149

149150
def test_header_is_none(self):
150151
from opencensus.trace import execution_context
152+
from opencensus.trace.tracer import base
151153

152154
app = self.create_app()
153155
flask_middleware.FlaskMiddleware(app=app)
@@ -167,7 +169,7 @@ def test_header_is_none(self):
167169
}
168170

169171
self.assertEqual(span.labels, expected_labels)
170-
self.assertIsNone(span.parent_span_id)
172+
assert isinstance(span.parent_span, base.NullContextManager)
171173

172174
def test__after_request_not_sampled(self):
173175
from opencensus.trace.samplers import always_off

trace/tests/unit/test_request_tracer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,13 @@ def test_end_span_not_sampled(self):
188188
self.assertFalse(span_context.span_id.called)
189189

190190
def test_end_span_sampled(self):
191+
from opencensus.trace import execution_context
192+
191193
sampler = mock.Mock()
192194
sampler.should_sample.return_value = True
193195
tracer = request_tracer.RequestTracer(sampler=sampler)
194196
span = mock.Mock()
195-
tracer.tracer._span_stack.append(span)
197+
execution_context.set_current_span(span)
196198
tracer.end_span()
197199

198200
self.assertTrue(span.finish.called)
@@ -209,11 +211,13 @@ def test_current_span_not_sampled(self):
209211
assert isinstance(span, base.NullContextManager)
210212

211213
def test_current_span_sampled(self):
214+
from opencensus.trace import execution_context
215+
212216
sampler = mock.Mock()
213217
sampler.should_sample.return_value = True
214218
tracer = request_tracer.RequestTracer(sampler=sampler)
215219
span = mock.Mock()
216-
tracer.tracer._span_stack.append(span)
220+
execution_context.set_current_span(span)
217221

218222
result = tracer.current_span()
219223

trace/tests/unit/test_span.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_constructor_defaults(self):
4646
self.assertEqual(span.name, span_name)
4747
self.assertEqual(span.span_id, span_id)
4848
self.assertEqual(span.kind, Enum.SpanKind.SPAN_KIND_UNSPECIFIED)
49-
self.assertIsNone(span.parent_span_id)
49+
self.assertIsNone(span.parent_span)
5050
self.assertEqual(span.labels, {})
5151
self.assertIsNone(span.start_time)
5252
self.assertIsNone(span.end_time)
@@ -61,7 +61,7 @@ def test_constructor_explicit(self):
6161
span_id = 'test_span_id'
6262
span_name = 'test_span_name'
6363
kind = Enum.SpanKind.RPC_CLIENT
64-
parent_span_id = 1234
64+
parent_span = mock.Mock()
6565
start_time = datetime.utcnow().isoformat() + 'Z'
6666
end_time = datetime.utcnow().isoformat() + 'Z'
6767
labels = {
@@ -73,7 +73,7 @@ def test_constructor_explicit(self):
7373
span = self._make_one(
7474
name=span_name,
7575
kind=kind,
76-
parent_span_id=parent_span_id,
76+
parent_span=parent_span,
7777
labels=labels,
7878
start_time=start_time,
7979
end_time=end_time,
@@ -83,7 +83,7 @@ def test_constructor_explicit(self):
8383
self.assertEqual(span.name, span_name)
8484
self.assertEqual(span.span_id, span_id)
8585
self.assertEqual(span.kind, kind)
86-
self.assertEqual(span.parent_span_id, parent_span_id)
86+
self.assertEqual(span.parent_span, parent_span)
8787
self.assertEqual(span.labels, labels)
8888
self.assertEqual(span.start_time, start_time)
8989
self.assertEqual(span.end_time, end_time)
@@ -115,7 +115,7 @@ def test_span(self):
115115
self.assertEqual(result_child_span.name, child_span_name)
116116
self.assertEqual(result_child_span.span_id, span_id)
117117
self.assertEqual(result_child_span.kind, kind)
118-
self.assertEqual(result_child_span.parent_span_id, root_span.span_id)
118+
self.assertEqual(result_child_span.parent_span, root_span)
119119
self.assertEqual(result_child_span.labels, {})
120120
self.assertIsNone(result_child_span.start_time)
121121
self.assertIsNone(result_child_span.end_time)

0 commit comments

Comments
 (0)