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

Commit cac4305

Browse files
authored
Add BaseSpan interface to make Span and BlankSpan compatible (#313)
* Add base span interface to make Span and BlankSpan compatible * Add test for context_manager and minor import style fix
1 parent 6c88916 commit cac4305

5 files changed

Lines changed: 320 additions & 5 deletions

File tree

opencensus/trace/base_span.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2018, 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+
"""Module containing base class for Span."""
16+
17+
18+
class BaseSpan(object):
19+
"""Base class for Opencensus spans.
20+
Subclasses of :class:`BaseSpan` must implement the below methods.
21+
"""
22+
23+
@staticmethod
24+
def on_create(callback):
25+
raise NotImplementedError
26+
27+
@property
28+
def children(self):
29+
"""The child spans of the current span."""
30+
raise NotImplementedError
31+
32+
def span(self, name='child_span'):
33+
"""Create a child span for the current span and append it to the child
34+
spans list.
35+
36+
:type name: str
37+
:param name: (Optional) The name of the child span.
38+
39+
:rtype: :class: `~opencensus.trace.span.Span`
40+
:returns: A child Span to be added to the current span.
41+
"""
42+
raise NotImplementedError
43+
44+
def add_attribute(self, attribute_key, attribute_value):
45+
"""Add attribute to span.
46+
47+
:type attribute_key: str
48+
:param attribute_key: Attribute key.
49+
50+
:type attribute_value:str
51+
:param attribute_value: Attribute value.
52+
"""
53+
raise NotImplementedError
54+
55+
def add_annotation(self, description, **attrs):
56+
"""Add an annotation to span.
57+
58+
:type description: str
59+
:param description: A user-supplied message describing the event.
60+
The maximum length for the description is 256 bytes.
61+
62+
:type attrs: kwargs
63+
:param attrs: keyworded arguments e.g. failed=True, name='Caching'
64+
"""
65+
raise NotImplementedError
66+
67+
def add_time_event(self, time_event):
68+
"""Add a TimeEvent.
69+
70+
:type time_event: :class: `~opencensus.trace.time_event.TimeEvent`
71+
:param time_event: A TimeEvent object.
72+
"""
73+
raise NotImplementedError
74+
75+
def add_link(self, link):
76+
"""Add a Link.
77+
78+
:type link: :class: `~opencensus.trace.link.Link`
79+
:param link: A Link object.
80+
"""
81+
raise NotImplementedError
82+
83+
def start(self):
84+
"""Set the start time for a span."""
85+
raise NotImplementedError
86+
87+
def finish(self):
88+
"""Set the end time for a span."""
89+
raise NotImplementedError
90+
91+
def __iter__(self):
92+
"""Iterate through the span tree."""
93+
raise NotImplementedError
94+
95+
def __enter__(self):
96+
"""Start a span."""
97+
raise NotImplementedError
98+
99+
def __exit__(self, exception_type, exception_value, traceback):
100+
"""Finish a span."""
101+
raise NotImplementedError

opencensus/trace/blank_span.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from opencensus.trace import base_span
1516
from opencensus.trace import time_event as time_event_module
1617
from opencensus.trace.span_context import generate_span_id
1718
from opencensus.trace.tracers import base
1819

1920

20-
class BlankSpan(object):
21+
class BlankSpan(base_span.BaseSpan):
2122
"""A BlankSpan is an individual timed event which forms a node of the trace
2223
tree. All operations are no-op.
2324
@@ -72,6 +73,10 @@ def __init__(
7273
self.context_tracer = context_tracer
7374
self.span_kind = span_kind
7475

76+
@staticmethod
77+
def on_create(callback):
78+
pass
79+
7580
@property
7681
def children(self):
7782
"""The child spans of the current BlankSpan."""
@@ -143,3 +148,11 @@ def finish(self):
143148
def __iter__(self):
144149
"""Iterate through the span tree."""
145150
yield self
151+
152+
def __enter__(self):
153+
"""Start a span."""
154+
return self
155+
156+
def __exit__(self, exception_type, exception_value, traceback):
157+
"""Finish a span."""
158+
pass

opencensus/trace/span.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from itertools import chain
1717

1818
from opencensus.trace import attributes
19+
from opencensus.trace import base_span
1920
from opencensus.trace import link as link_module
2021
from opencensus.trace import stack_trace
2122
from opencensus.trace import status
@@ -31,7 +32,7 @@ class SpanKind(object):
3132
CLIENT = 2
3233

3334

34-
class Span(object):
35+
class Span(base_span.BaseSpan):
3536
"""A span is an individual timed event which forms a node of the trace
3637
tree. Each span has its name, span id and parent id. The parent id
3738
indicates the causal relationships between the individual spans in a

tests/unit/trace/test_base_span.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2018, 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+
import unittest
16+
import mock
17+
from opencensus.trace.base_span import BaseSpan
18+
19+
20+
class TestBaseSpan(unittest.TestCase):
21+
22+
def test_span_abstract(self):
23+
span = BaseSpan()
24+
25+
with self.assertRaises(NotImplementedError):
26+
span.span('root_span')
27+
28+
def test_children_abstract(self):
29+
span = BaseSpan()
30+
31+
with self.assertRaises(NotImplementedError):
32+
span.children
33+
34+
def test_create_abstract(self):
35+
span = BaseSpan()
36+
37+
with self.assertRaises(NotImplementedError):
38+
@BaseSpan.on_create
39+
def callback(span):
40+
pass
41+
42+
def test_start_abstract(self):
43+
span = BaseSpan()
44+
45+
with self.assertRaises(NotImplementedError):
46+
span.start()
47+
48+
def test_finish_abstract(self):
49+
span = BaseSpan()
50+
51+
with self.assertRaises(NotImplementedError):
52+
span.finish()
53+
54+
def test_add_attribute_abstract(self):
55+
span = BaseSpan()
56+
57+
with self.assertRaises(NotImplementedError):
58+
span.add_attribute("key", "value")
59+
60+
def test_add_annotation_abstract(self):
61+
span = BaseSpan()
62+
63+
with self.assertRaises(NotImplementedError):
64+
span.add_annotation("desc")
65+
66+
def test_add_time_event_abstract(self):
67+
span = BaseSpan()
68+
69+
with self.assertRaises(NotImplementedError):
70+
span.add_time_event(None)
71+
72+
def test_add_link_abstract(self):
73+
span = BaseSpan()
74+
75+
with self.assertRaises(NotImplementedError):
76+
span.add_link(None)
77+
78+
def test_iter_abstract(self):
79+
span = BaseSpan()
80+
81+
with self.assertRaises(NotImplementedError):
82+
list(iter(span))
83+
84+
@mock.patch.object(BaseSpan, '__exit__')
85+
@mock.patch.object(BaseSpan, '__enter__')
86+
def test_context_manager_called(self, mock_enter, mock_exit):
87+
span = BaseSpan()
88+
with span:
89+
pass
90+
self.assertTrue(mock_enter.called)
91+
self.assertTrue(mock_exit.called)
92+
93+
def test_context_manager_methods(self):
94+
span = BaseSpan()
95+
with self.assertRaises(NotImplementedError):
96+
span.__enter__()
97+
98+
with self.assertRaises(NotImplementedError):
99+
span.__exit__(None, None, None)
100+
101+

tests/unit/trace/test_blank_span.py

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import unittest
2-
3-
import mock
1+
# Copyright 2018, 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.
414

515
import datetime
16+
import mock
17+
import unittest
618
from opencensus.trace.link import Link
719
from opencensus.trace.span import format_span_json
820
from opencensus.trace.time_event import TimeEvent
@@ -71,3 +83,90 @@ def test_do_not_crash(self):
7183

7284
span.start()
7385
span.finish()
86+
87+
def test_constructor_explicit(self):
88+
from datetime import datetime
89+
90+
span_id = 'test_span_id'
91+
span_name = 'test_span_name'
92+
parent_span = mock.Mock()
93+
start_time = datetime.utcnow().isoformat() + 'Z'
94+
end_time = datetime.utcnow().isoformat() + 'Z'
95+
attributes = {
96+
'http.status_code': '200',
97+
'component': 'HTTP load balancer',
98+
}
99+
time_events = mock.Mock()
100+
links = mock.Mock()
101+
stack_trace = mock.Mock()
102+
status = mock.Mock()
103+
context_tracer = mock.Mock()
104+
105+
span = self._make_one(
106+
name=span_name,
107+
parent_span=parent_span,
108+
attributes=attributes,
109+
start_time=start_time,
110+
end_time=end_time,
111+
span_id=span_id,
112+
stack_trace=stack_trace,
113+
time_events=time_events,
114+
links=links,
115+
status=status,
116+
context_tracer=context_tracer)
117+
118+
self.assertEqual(span.name, span_name)
119+
self.assertIsNotNone(span.span_id)
120+
self.assertEqual(span.attributes, {})
121+
self.assertEqual(span.start_time, start_time)
122+
self.assertEqual(span.end_time, end_time)
123+
self.assertEqual(span.time_events, time_events)
124+
self.assertEqual(span.stack_trace, stack_trace)
125+
self.assertEqual(span.links, [])
126+
self.assertEqual(span.status, status)
127+
self.assertEqual(span.children, [])
128+
self.assertEqual(span.context_tracer, context_tracer)
129+
130+
def test_start(self):
131+
span_name = 'root_span'
132+
span = self._make_one(span_name)
133+
self.assertIsNone(span.start_time)
134+
135+
span.start()
136+
self.assertIsNone(span.start_time)
137+
138+
def test_finish_without_context_tracer(self):
139+
span_name = 'root_span'
140+
span = self._make_one(span_name)
141+
self.assertIsNone(span.end_time)
142+
143+
span.finish()
144+
self.assertIsNone(span.end_time)
145+
146+
def test_finish(self):
147+
span_name = 'root_span'
148+
span = self._make_one(span_name)
149+
self.assertIsNone(span.end_time)
150+
151+
span.finish()
152+
self.assertIsNone(span.end_time)
153+
154+
def test_on_create(self):
155+
from opencensus.trace.blank_span import BlankSpan
156+
self.on_create_called = False
157+
span = self._make_one('span1')
158+
self.assertFalse(self.on_create_called)
159+
try:
160+
@BlankSpan.on_create
161+
def callback(span):
162+
self.on_create_called = True
163+
span = self._make_one('span2')
164+
finally:
165+
BlankSpan._on_create_callbacks = []
166+
self.assertFalse(self.on_create_called)
167+
168+
def test_context_manager(self):
169+
span_name = 'root_span'
170+
with self._make_one(span_name) as s:
171+
self.assertIsNotNone(s)
172+
self.assertEquals(s.name, span_name)

0 commit comments

Comments
 (0)