Skip to content

Commit 2d873e5

Browse files
authored
Make SpanProcessor.on_start accept parent Context (#1251)
* context from Tracer.start_span is passed through to the SpanProcessor * fix linting issue in falcon test app when linting with eachdist script * fix global error handler test as it read installed extensions * reset global Configuration object state after tests were run
1 parent 5fc08ad commit 2d873e5

File tree

11 files changed

+143
-23
lines changed

11 files changed

+143
-23
lines changed

exporter/opentelemetry-exporter-datadog/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Make `SpanProcessor.on_start` accept parent Context
6+
([#1251](https://github.com/open-telemetry/opentelemetry-python/pull/1251))
7+
58
## Version 0.14b0
69

710
Released 2020-10-13

exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/spanprocessor.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import threading
1818
import typing
1919

20-
from opentelemetry.context import attach, detach, set_value
20+
from opentelemetry.context import Context, attach, detach, set_value
2121
from opentelemetry.sdk.trace import Span, SpanProcessor
2222
from opentelemetry.sdk.trace.export import SpanExporter
2323
from opentelemetry.trace import INVALID_TRACE_ID
@@ -81,7 +81,9 @@ def __init__(
8181
self.done = False
8282
self.worker_thread.start()
8383

84-
def on_start(self, span: Span) -> None:
84+
def on_start(
85+
self, span: Span, parent_context: typing.Optional[Context] = None
86+
) -> None:
8587
ctx = span.get_span_context()
8688
trace_id = ctx.trace_id
8789

exporter/opentelemetry-exporter-datadog/tests/test_datadog_exporter.py

+16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ddtrace.internal.writer import AgentWriter
2222

2323
from opentelemetry import trace as trace_api
24+
from opentelemetry.context import Context
2425
from opentelemetry.exporter import datadog
2526
from opentelemetry.sdk import trace
2627
from opentelemetry.sdk.trace import Resource, sampling
@@ -482,6 +483,21 @@ def test_span_processor_scheduled_delay(self):
482483

483484
tracer_provider.shutdown()
484485

486+
def test_span_processor_accepts_parent_context(self):
487+
span_processor = mock.Mock(
488+
wraps=datadog.DatadogExportSpanProcessor(self.exporter)
489+
)
490+
tracer_provider = trace.TracerProvider()
491+
tracer_provider.add_span_processor(span_processor)
492+
tracer = tracer_provider.get_tracer(__name__)
493+
494+
context = Context()
495+
span = tracer.start_span("foo", context=context)
496+
497+
span_processor.on_start.assert_called_once_with(
498+
span, parent_context=context
499+
)
500+
485501
def test_origin(self):
486502
context = trace_api.SpanContext(
487503
trace_id=0x000000000000000000000000DEADBEEF,

instrumentation/opentelemetry-instrumentation-falcon/tests/app.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
class HelloWorldResource:
77
def _handle_request(self, _, resp):
8+
# pylint: disable=no-member
89
resp.status = falcon.HTTP_201
910
resp.body = "Hello World"
1011

opentelemetry-sdk/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Make `SpanProcessor.on_start` accept parent Context
6+
([#1251](https://github.com/open-telemetry/opentelemetry-python/pull/1251))
7+
58
## Version 0.14b0
69

710
Released 2020-10-13

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

+35-13
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,19 @@ class SpanProcessor:
6868
in the same order as they were registered.
6969
"""
7070

71-
def on_start(self, span: "Span") -> None:
71+
def on_start(
72+
self,
73+
span: "Span",
74+
parent_context: Optional[context_api.Context] = None,
75+
) -> None:
7276
"""Called when a :class:`opentelemetry.trace.Span` is started.
7377
7478
This method is called synchronously on the thread that starts the
7579
span, therefore it should not block or throw an exception.
7680
7781
Args:
7882
span: The :class:`opentelemetry.trace.Span` that just started.
83+
parent_context: The parent context of the span that just started.
7984
"""
8085

8186
def on_end(self, span: "Span") -> None:
@@ -124,9 +129,13 @@ def add_span_processor(self, span_processor: SpanProcessor) -> None:
124129
with self._lock:
125130
self._span_processors = self._span_processors + (span_processor,)
126131

127-
def on_start(self, span: "Span") -> None:
132+
def on_start(
133+
self,
134+
span: "Span",
135+
parent_context: Optional[context_api.Context] = None,
136+
) -> None:
128137
for sp in self._span_processors:
129-
sp.on_start(span)
138+
sp.on_start(span, parent_context=parent_context)
130139

131140
def on_end(self, span: "Span") -> None:
132141
for sp in self._span_processors:
@@ -192,17 +201,26 @@ def add_span_processor(self, span_processor: SpanProcessor) -> None:
192201
self._span_processors = self._span_processors + (span_processor,)
193202

194203
def _submit_and_await(
195-
self, func: Callable[[SpanProcessor], Callable[..., None]], *args: Any
204+
self,
205+
func: Callable[[SpanProcessor], Callable[..., None]],
206+
*args: Any,
207+
**kwargs: Any
196208
):
197209
futures = []
198210
for sp in self._span_processors:
199-
future = self._executor.submit(func(sp), *args)
211+
future = self._executor.submit(func(sp), *args, **kwargs)
200212
futures.append(future)
201213
for future in futures:
202214
future.result()
203215

204-
def on_start(self, span: "Span") -> None:
205-
self._submit_and_await(lambda sp: sp.on_start, span)
216+
def on_start(
217+
self,
218+
span: "Span",
219+
parent_context: Optional[context_api.Context] = None,
220+
) -> None:
221+
self._submit_and_await(
222+
lambda sp: sp.on_start, span, parent_context=parent_context
223+
)
206224

207225
def on_end(self, span: "Span") -> None:
208226
self._submit_and_await(lambda sp: sp.on_end, span)
@@ -584,7 +602,11 @@ def add_event(
584602
)
585603
)
586604

587-
def start(self, start_time: Optional[int] = None) -> None:
605+
def start(
606+
self,
607+
start_time: Optional[int] = None,
608+
parent_context: Optional[context_api.Context] = None,
609+
) -> None:
588610
with self._lock:
589611
if not self.is_recording():
590612
return
@@ -596,7 +618,7 @@ def start(self, start_time: Optional[int] = None) -> None:
596618
if has_started:
597619
logger.warning("Calling start() on a started span.")
598620
return
599-
self.span_processor.on_start(self)
621+
self.span_processor.on_start(self, parent_context=parent_context)
600622

601623
def end(self, end_time: Optional[int] = None) -> None:
602624
with self._lock:
@@ -764,7 +786,7 @@ def start_span( # pylint: disable=too-many-locals
764786
if sampling_result.decision.is_sampled()
765787
else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT)
766788
)
767-
context = trace_api.SpanContext(
789+
span_context = trace_api.SpanContext(
768790
trace_id,
769791
self.source.ids_generator.generate_span_id(),
770792
is_remote=False,
@@ -777,7 +799,7 @@ def start_span( # pylint: disable=too-many-locals
777799
# pylint:disable=protected-access
778800
span = _Span(
779801
name=name,
780-
context=context,
802+
context=span_context,
781803
parent=parent_span_context,
782804
sampler=self.source.sampler,
783805
resource=self.source.resource,
@@ -788,9 +810,9 @@ def start_span( # pylint: disable=too-many-locals
788810
instrumentation_info=self.instrumentation_info,
789811
set_status_on_exception=set_status_on_exception,
790812
)
791-
span.start(start_time=start_time)
813+
span.start(start_time=start_time, parent_context=context)
792814
else:
793-
span = trace_api.DefaultSpan(context=context)
815+
span = trace_api.DefaultSpan(context=span_context)
794816
return span
795817

796818
@contextmanager

opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from enum import Enum
2222

2323
from opentelemetry.configuration import Configuration
24-
from opentelemetry.context import attach, detach, set_value
24+
from opentelemetry.context import Context, attach, detach, set_value
2525
from opentelemetry.sdk.trace import Span, SpanProcessor
2626
from opentelemetry.util import time_ns
2727

@@ -70,7 +70,9 @@ class SimpleExportSpanProcessor(SpanProcessor):
7070
def __init__(self, span_exporter: SpanExporter):
7171
self.span_exporter = span_exporter
7272

73-
def on_start(self, span: Span) -> None:
73+
def on_start(
74+
self, span: Span, parent_context: typing.Optional[Context] = None
75+
) -> None:
7476
pass
7577

7678
def on_end(self, span: Span) -> None:
@@ -172,7 +174,9 @@ def __init__(
172174
] * self.max_export_batch_size # type: typing.List[typing.Optional[Span]]
173175
self.worker_thread.start()
174176

175-
def on_start(self, span: Span) -> None:
177+
def on_start(
178+
self, span: Span, parent_context: typing.Optional[Context] = None
179+
) -> None:
176180
pass
177181

178182
def on_end(self, span: Span) -> None:

opentelemetry-sdk/tests/error_handler/test_error_handler.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525

2626

2727
class TestErrorHandler(TestCase):
28-
def test_default_error_handler(self):
28+
@patch("opentelemetry.sdk.error_handler.iter_entry_points")
29+
def test_default_error_handler(self, mock_iter_entry_points):
2930

3031
with self.assertLogs(logger, ERROR):
3132
with GlobalErrorHandler():

opentelemetry-sdk/tests/trace/export/test_export.py

+41
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from unittest import mock
2222

2323
from opentelemetry import trace as trace_api
24+
from opentelemetry.configuration import Configuration
25+
from opentelemetry.context import Context
2426
from opentelemetry.sdk import trace
2527
from opentelemetry.sdk.trace import export
2628

@@ -100,6 +102,23 @@ def test_simple_span_processor_no_context(self):
100102

101103
self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
102104

105+
def test_on_start_accepts_context(self):
106+
# pylint: disable=no-self-use
107+
tracer_provider = trace.TracerProvider()
108+
tracer = tracer_provider.get_tracer(__name__)
109+
110+
exporter = MySpanExporter([])
111+
span_processor = mock.Mock(
112+
wraps=export.SimpleExportSpanProcessor(exporter)
113+
)
114+
tracer_provider.add_span_processor(span_processor)
115+
116+
context = Context()
117+
span = tracer.start_span("foo", context=context)
118+
span_processor.on_start.assert_called_once_with(
119+
span, parent_context=context
120+
)
121+
103122
def test_simple_span_processor_not_sampled(self):
104123
tracer_provider = trace.TracerProvider(
105124
sampler=trace.sampling.ALWAYS_OFF
@@ -136,6 +155,11 @@ def _create_start_and_end_span(name, span_processor):
136155

137156

138157
class TestBatchExportSpanProcessor(unittest.TestCase):
158+
def tearDown(self) -> None:
159+
# reset global state of configuration object
160+
# pylint: disable=protected-access
161+
Configuration._reset()
162+
139163
@mock.patch.dict(
140164
"os.environ",
141165
{
@@ -156,6 +180,23 @@ def test_batch_span_processor_environment_variables(self):
156180
self.assertEqual(batch_span_processor.max_export_batch_size, 3)
157181
self.assertEqual(batch_span_processor.export_timeout_millis, 4)
158182

183+
def test_on_start_accepts_parent_context(self):
184+
# pylint: disable=no-self-use
185+
my_exporter = MySpanExporter(destination=[])
186+
span_processor = mock.Mock(
187+
wraps=export.BatchExportSpanProcessor(my_exporter)
188+
)
189+
tracer_provider = trace.TracerProvider()
190+
tracer_provider.add_span_processor(span_processor)
191+
tracer = tracer_provider.get_tracer(__name__)
192+
193+
context = Context()
194+
span = tracer.start_span("foo", context=context)
195+
196+
span_processor.on_start.assert_called_once_with(
197+
span, parent_context=context
198+
)
199+
159200
def test_shutdown(self):
160201
spans_names_list = []
161202

opentelemetry-sdk/tests/trace/test_span_processor.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
import typing
1818
import unittest
1919
from threading import Event
20+
from typing import Optional
2021
from unittest import mock
2122

2223
from opentelemetry import trace as trace_api
24+
from opentelemetry.context import Context
2325
from opentelemetry.sdk import trace
2426

2527

@@ -36,7 +38,9 @@ def __init__(self, name, span_list):
3638
self.name = name
3739
self.span_list = span_list
3840

39-
def on_start(self, span: "trace.Span") -> None:
41+
def on_start(
42+
self, span: "trace.Span", parent_context: Optional[Context] = None
43+
) -> None:
4044
self.span_list.append(span_event_start_fmt(self.name, span.name))
4145

4246
def on_end(self, span: "trace.Span") -> None:
@@ -160,10 +164,13 @@ def test_on_start(self):
160164
multi_processor.add_span_processor(mock_processor)
161165

162166
span = self.create_default_span()
163-
multi_processor.on_start(span)
167+
context = Context()
168+
multi_processor.on_start(span, parent_context=context)
164169

165170
for mock_processor in mocks:
166-
mock_processor.on_start.assert_called_once_with(span)
171+
mock_processor.on_start.assert_called_once_with(
172+
span, parent_context=context
173+
)
167174
multi_processor.shutdown()
168175

169176
def test_on_end(self):

opentelemetry-sdk/tests/trace/test_trace.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
import subprocess
1818
import unittest
1919
from logging import ERROR, WARNING
20+
from typing import Optional
2021
from unittest import mock
2122

2223
from opentelemetry import trace as trace_api
24+
from opentelemetry.context import Context
2325
from opentelemetry.sdk import resources, trace
2426
from opentelemetry.sdk.trace import Resource, sampling
2527
from opentelemetry.sdk.util import ns_to_iso_str
@@ -435,6 +437,8 @@ def test_disallow_direct_span_creation(self):
435437

436438

437439
class TestSpan(unittest.TestCase):
440+
# pylint: disable=too-many-public-methods
441+
438442
def setUp(self):
439443
self.tracer = new_tracer()
440444

@@ -734,6 +738,20 @@ def test_start_span(self):
734738
)
735739
self.assertIs(span.status.description, "Test description")
736740

741+
def test_start_accepts_context(self):
742+
# pylint: disable=no-self-use
743+
span_processor = mock.Mock(spec=trace.SpanProcessor)
744+
span = trace._Span(
745+
"name",
746+
mock.Mock(spec=trace_api.SpanContext),
747+
span_processor=span_processor,
748+
)
749+
context = Context()
750+
span.start(parent_context=context)
751+
span_processor.on_start.assert_called_once_with(
752+
span, parent_context=context
753+
)
754+
737755
def test_span_override_start_and_end_time(self):
738756
"""Span sending custom start_time and end_time values"""
739757
span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
@@ -899,7 +917,9 @@ def __init__(self, name, span_list):
899917
self.name = name
900918
self.span_list = span_list
901919

902-
def on_start(self, span: "trace.Span") -> None:
920+
def on_start(
921+
self, span: "trace.Span", parent_context: Optional[Context] = None
922+
) -> None:
903923
self.span_list.append(span_event_start_fmt(self.name, span.name))
904924

905925
def on_end(self, span: "trace.Span") -> None:

0 commit comments

Comments
 (0)