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

Commit 5524eb8

Browse files
authored
Allow user to instrument a library without enabling tracing (using the NoopTracer) (#285)
* Allow user to instrument a library without enabling tracing (i.e. using the NoopTracer) * NoopSpan -> BlankSpan and add unit test
1 parent 1262e7f commit 5524eb8

7 files changed

Lines changed: 251 additions & 21 deletions

File tree

opencensus/trace/blank_span.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Copyright 2017, OpenCensus Authors
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+
15+
from opencensus.trace import time_event as time_event_module
16+
from opencensus.trace.span_context import generate_span_id
17+
from opencensus.trace.tracers import base
18+
19+
20+
class BlankSpan(object):
21+
"""A BlankSpan is an individual timed event which forms a node of the trace
22+
tree. All operations are no-op.
23+
24+
:type name: str
25+
:param name: The name of the span.
26+
27+
:type parent_span: :class:`~opencensus.trace.blank_span.BlankSpan`
28+
:param parent_span: (Optional) Parent span.
29+
30+
:type status: :class: `~opencensus.trace.status.Status`
31+
:param status: (Optional) An optional final status for this span.
32+
33+
:type context_tracer: :class:`~opencensus.trace.tracers.noop_tracer.
34+
NoopTracer`
35+
:param context_tracer: The tracer that holds a stack of spans. If this is
36+
not None, then when exiting a span, use the end_span
37+
method in the tracer class to finish a span. If no
38+
tracer is passed in, then just finish the span using
39+
the finish method in the Span class.
40+
"""
41+
42+
def __init__(
43+
self,
44+
name=None,
45+
parent_span=None,
46+
attributes=None,
47+
start_time=None,
48+
end_time=None,
49+
span_id=None,
50+
stack_trace=None,
51+
time_events=None,
52+
links=None,
53+
status=None,
54+
same_process_as_parent_span=None,
55+
context_tracer=None,
56+
span_kind=None):
57+
self.name = name
58+
self.parent_span = parent_span
59+
self.start_time = start_time
60+
self.end_time = end_time
61+
62+
self.span_id = generate_span_id()
63+
self.parent_span = base.NullContextManager()
64+
65+
self.attributes = {}
66+
self.stack_trace = stack_trace
67+
self.time_events = time_events
68+
self.links = []
69+
self.status = status
70+
self.same_process_as_parent_span = same_process_as_parent_span
71+
self._child_spans = []
72+
self.context_tracer = context_tracer
73+
self.span_kind = span_kind
74+
75+
@property
76+
def children(self):
77+
"""The child spans of the current BlankSpan."""
78+
return list()
79+
80+
def span(self, name='child_span'):
81+
"""Create a child span for the current span and append it to the child
82+
spans list.
83+
84+
:type name: str
85+
:param name: (Optional) The name of the child span.
86+
87+
:rtype: :class: `~opencensus.trace.blankspan.BlankSpan`
88+
:returns: A child Span to be added to the current span.
89+
"""
90+
child_span = BlankSpan(name, parent_span=self)
91+
self._child_spans.append(child_span)
92+
return child_span
93+
94+
def add_attribute(self, attribute_key, attribute_value):
95+
"""No-op implementation of this method.
96+
97+
:type attribute_key: str
98+
:param attribute_key: Attribute key.
99+
100+
:type attribute_value:str
101+
:param attribute_value: Attribute value.
102+
"""
103+
pass
104+
105+
def add_annotation(self, description, **attrs):
106+
"""No-op implementation of this method.
107+
108+
:type description: str
109+
:param description: A user-supplied message describing the event.
110+
The maximum length for the description is 256 bytes.
111+
112+
:type attrs: kwargs
113+
:param attrs: keyworded arguments e.g. failed=True, name='Caching'
114+
"""
115+
pass
116+
117+
def add_time_event(self, time_event):
118+
"""No-op implementation of this method.
119+
120+
:type time_event: :class: `~opencensus.trace.time_event.TimeEvent`
121+
:param time_event: A TimeEvent object.
122+
"""
123+
if not isinstance(time_event, time_event_module.TimeEvent):
124+
raise TypeError("Type Error: received {}, but requires TimeEvent.".
125+
format(type(time_event).__name__))
126+
127+
def add_link(self, link):
128+
"""No-op implementation of this method.
129+
130+
:type link: :class: `~opencensus.trace.link.Link`
131+
:param link: A Link object.
132+
"""
133+
pass
134+
135+
def start(self):
136+
"""No-op implementation of this method."""
137+
pass
138+
139+
def finish(self):
140+
"""No-op implementation of this method."""
141+
pass
142+
143+
def __iter__(self):
144+
"""Iterate through the span tree."""
145+
yield self

opencensus/trace/tracers/noop_tracer.py

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

1515
from opencensus.trace.tracers import base
16+
from opencensus.trace import blank_span as trace_span
17+
from opencensus.trace.span_context import SpanContext
18+
from opencensus.trace import trace_options
1619

1720

1821
class NoopTracer(base.Tracer):
1922
"""No-op implementation of the :class:`Tracer` interface, all methods are
2023
no-ops. Should be used when tracing is not enabled or not sampled.
2124
"""
22-
span_context = None
25+
26+
def __init__(self):
27+
28+
self.span_context = SpanContext(
29+
trace_options=trace_options.TraceOptions(0)
30+
)
2331

2432
def finish(self):
2533
"""End spans and send to reporter."""
@@ -34,7 +42,9 @@ def span(self, name='span'):
3442
:rtype: :class:`~opencensus.trace.trace_span.Span`
3543
:returns: The Span object.
3644
"""
37-
return base.NullContextManager(context_tracer=self)
45+
46+
span = self.start_span(name=name)
47+
return span
3848

3949
def start_span(self, name='span'):
4050
"""Start a span.
@@ -45,18 +55,19 @@ def start_span(self, name='span'):
4555
:rtype: :class:`~opencensus.trace.trace_span.Span`
4656
:returns: The Span object.
4757
"""
48-
return base.NullContextManager(context_tracer=self)
58+
span = trace_span.BlankSpan(name, context_tracer=self)
59+
return span
4960

5061
def end_span(self):
5162
"""End a span. Remove the span from the span stack, and update the
5263
span_id in TraceContext as the current span_id which is the peek
5364
element in the span stack.
5465
"""
55-
return base.NullContextManager(context_tracer=self)
66+
pass
5667

5768
def current_span(self):
5869
"""Return the current span."""
59-
return base.NullContextManager(context_tracer=self)
70+
return trace_span.BlankSpan()
6071

6172
def add_attribute_to_current_span(self, attribute_key, attribute_value):
6273
"""Add attribute to current span.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from opencensus.trace.propagation import google_cloud_format
2828
from opencensus.trace.samplers import always_on
2929
from opencensus.trace.samplers import probability
30-
from opencensus.trace.tracers import base
30+
from opencensus.trace.blank_span import BlankSpan
3131

3232

3333
class TestOpencensusMiddleware(unittest.TestCase):
@@ -244,7 +244,7 @@ def test_blacklist_path(self):
244244
tracer = middleware._get_current_tracer()
245245
span = tracer.current_span()
246246

247-
assert isinstance(span, base.NullContextManager)
247+
assert isinstance(span, BlankSpan)
248248

249249
# process response
250250
django_response = mock.Mock()
@@ -254,7 +254,7 @@ def test_blacklist_path(self):
254254

255255
tracer = middleware._get_current_tracer()
256256
span = tracer.current_span()
257-
assert isinstance(span, base.NullContextManager)
257+
assert isinstance(span, BlankSpan)
258258

259259
def test_process_response(self):
260260
from opencensus.trace.ext.django import middleware

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from opencensus.trace.samplers import always_off, always_on, ProbabilitySampler
3434
from opencensus.trace.tracers import base
3535
from opencensus.trace.tracers import noop_tracer
36+
from opencensus.trace.blank_span import BlankSpan
3637

3738

3839
class TestFlaskMiddleware(unittest.TestCase):
@@ -209,7 +210,7 @@ def test__before_request_blacklist(self):
209210

210211
span = tracer.current_span()
211212

212-
assert isinstance(span, base.NullContextManager)
213+
assert isinstance(span, BlankSpan)
213214

214215
def test_header_encoding(self):
215216
# The test is for detecting the encoding compatibility issue in

tests/unit/trace/ext/pyramid/test_pyramid_middleware.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from opencensus.trace.ext.pyramid import pyramid_middleware
3131
from opencensus.trace.propagation import google_cloud_format
3232
from opencensus.trace.samplers import always_on
33-
from opencensus.trace.tracers import base
33+
from opencensus.trace.blank_span import BlankSpan
3434
from opencensus.trace.tracers import noop_tracer
3535

3636

@@ -191,7 +191,7 @@ def dummy_handler(request):
191191

192192
span = tracer.current_span()
193193

194-
assert isinstance(span, base.NullContextManager)
194+
assert isinstance(span, BlankSpan)
195195

196196
def test__after_request(self):
197197
pyramid_trace_header = 'X-Cloud-Trace-Context'
@@ -271,4 +271,4 @@ def dummy_handler(request):
271271

272272
middleware._after_request(request, response)
273273

274-
assert isinstance(span, base.NullContextManager)
274+
assert isinstance(span, BlankSpan)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import unittest
2+
3+
import mock
4+
5+
import datetime
6+
from opencensus.trace.link import Link
7+
from opencensus.trace.span import format_span_json
8+
from opencensus.trace.time_event import TimeEvent
9+
10+
11+
class TestBlankSpan(unittest.TestCase):
12+
13+
@staticmethod
14+
def _get_target_class():
15+
from opencensus.trace.blank_span import BlankSpan
16+
17+
return BlankSpan
18+
19+
def _make_one(self, *args, **kw):
20+
return self._get_target_class()(*args, **kw)
21+
22+
def test_do_not_crash(self):
23+
span_id = 'test_span_id'
24+
span_name = 'test_span_name'
25+
26+
patch = mock.patch(
27+
'opencensus.trace.blank_span.generate_span_id',
28+
return_value=span_id)
29+
30+
with patch:
31+
span = self._make_one(span_name)
32+
33+
self.assertEqual(span.name, span_name)
34+
self.assertEqual(span.span_id, span_id)
35+
self.assertIsNotNone(span.parent_span)
36+
self.assertIsNotNone(span.parent_span.span())
37+
self.assertEqual(span.attributes, {})
38+
self.assertIsNone(span.start_time)
39+
self.assertIsNone(span.end_time)
40+
self.assertEqual(span.children, [])
41+
self.assertIsNone(span.context_tracer)
42+
43+
span.add_attribute('attribute_key', 'attribute_value')
44+
span.add_annotation('This is a test', name='blank-span')
45+
46+
link = Link(span_id='1234', trace_id='4567')
47+
span.add_link(link)
48+
49+
time_event = mock.Mock()
50+
51+
with self.assertRaises(TypeError):
52+
span.add_time_event(time_event)
53+
54+
time_event = TimeEvent(datetime.datetime.now())
55+
span.add_time_event(time_event)
56+
57+
span_iter_list = list(iter(span))
58+
self.assertEqual(span_iter_list, [span])
59+
60+
expected_span_json = {
61+
'spanId': 'test_span_id',
62+
'startTime': None,
63+
'endTime': None,
64+
'displayName': {
65+
'truncated_byte_count': 0,
66+
'value': 'test_span_name'},
67+
'childSpanCount': 0,
68+
}
69+
span_json = format_span_json(span)
70+
self.assertEqual(span_json, expected_span_json)
71+
72+
span.start()
73+
span.finish()

0 commit comments

Comments
 (0)