Skip to content

Commit 95797e2

Browse files
committed
feat: implement on ending in span processor
1 parent 1797c1d commit 95797e2

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
([#4755](https://github.com/open-telemetry/opentelemetry-python/pull/4755))
1717
- logs: extend Logger.emit to accept separated keyword arguments
1818
([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737))
19+
- feat: implement on ending in span processor
20+
([#4775](https://github.com/open-telemetry/opentelemetry-python/pull/4775))
1921

2022
## Version 1.37.0/0.58b0 (2025-09-11)
2123

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ def on_start(
113113
parent_context: The parent context of the span that just started.
114114
"""
115115

116+
def on_ending(self, span: "Span") -> None:
117+
"""Called when a :class:`opentelemetry.trace.Span` is ending.
118+
119+
This method is called synchronously on the thread that ends the
120+
span, therefore it should not block or throw an exception.
121+
122+
Args:
123+
span: The :class:`opentelemetry.trace.Span` that is ending.
124+
"""
125+
116126
def on_end(self, span: "ReadableSpan") -> None:
117127
"""Called when a :class:`opentelemetry.trace.Span` is ended.
118128
@@ -170,6 +180,10 @@ def on_start(
170180
for sp in self._span_processors:
171181
sp.on_start(span, parent_context=parent_context)
172182

183+
def on_ending(self, span: "Span") -> None:
184+
for sp in self._span_processors:
185+
sp.on_ending(span)
186+
173187
def on_end(self, span: "ReadableSpan") -> None:
174188
for sp in self._span_processors:
175189
sp.on_end(span)
@@ -254,6 +268,9 @@ def on_start(
254268
lambda sp: sp.on_start, span, parent_context=parent_context
255269
)
256270

271+
def on_ending(self, span: "Span") -> None:
272+
self._submit_and_await(lambda sp: sp.on_ending, span)
273+
257274
def on_end(self, span: "ReadableSpan") -> None:
258275
self._submit_and_await(lambda sp: sp.on_end, span)
259276

@@ -945,6 +962,7 @@ def end(self, end_time: Optional[int] = None) -> None:
945962

946963
self._end_time = end_time if end_time is not None else time_ns()
947964

965+
self._span_processor.on_ending(self)
948966
self._span_processor.on_end(self._readable_span())
949967

950968
@_check_span_ended

opentelemetry-sdk/tests/trace/test_span_processor.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ def span_event_start_fmt(span_processor_name, span_name):
3232
return span_processor_name + ":" + span_name + ":start"
3333

3434

35+
def span_event_ending_fmt(span_processor_name, span_name):
36+
return span_processor_name + ":" + span_name + ":ending"
37+
38+
3539
def span_event_end_fmt(span_processor_name, span_name):
3640
return span_processor_name + ":" + span_name + ":end"
3741

@@ -46,6 +50,9 @@ def on_start(
4650
) -> None:
4751
self.span_list.append(span_event_start_fmt(self.name, span.name))
4852

53+
def on_ending(self, span: "trace.Span") -> None:
54+
self.span_list.append(span_event_ending_fmt(self.name, span.name))
55+
4956
def on_end(self, span: "trace.Span") -> None:
5057
self.span_list.append(span_event_end_fmt(self.name, span.name))
5158

@@ -82,10 +89,13 @@ def test_span_processor(self):
8289
with tracer.start_as_current_span("baz"):
8390
expected_list.append(span_event_start_fmt("SP1", "baz"))
8491

92+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
8593
expected_list.append(span_event_end_fmt("SP1", "baz"))
8694

95+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
8796
expected_list.append(span_event_end_fmt("SP1", "bar"))
8897

98+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
8999
expected_list.append(span_event_end_fmt("SP1", "foo"))
90100

91101
self.assertListEqual(spans_calls_list, expected_list)
@@ -108,12 +118,18 @@ def test_span_processor(self):
108118
expected_list.append(span_event_start_fmt("SP1", "baz"))
109119
expected_list.append(span_event_start_fmt("SP2", "baz"))
110120

121+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
122+
expected_list.append(span_event_ending_fmt("SP2", "baz"))
111123
expected_list.append(span_event_end_fmt("SP1", "baz"))
112124
expected_list.append(span_event_end_fmt("SP2", "baz"))
113125

126+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
127+
expected_list.append(span_event_ending_fmt("SP2", "bar"))
114128
expected_list.append(span_event_end_fmt("SP1", "bar"))
115129
expected_list.append(span_event_end_fmt("SP2", "bar"))
116130

131+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
132+
expected_list.append(span_event_ending_fmt("SP2", "foo"))
117133
expected_list.append(span_event_end_fmt("SP1", "foo"))
118134
expected_list.append(span_event_end_fmt("SP2", "foo"))
119135

@@ -136,10 +152,13 @@ def test_add_span_processor_after_span_creation(self):
136152
# add span processor after spans have been created
137153
tracer_provider.add_span_processor(sp)
138154

155+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
139156
expected_list.append(span_event_end_fmt("SP1", "baz"))
140157

158+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
141159
expected_list.append(span_event_end_fmt("SP1", "bar"))
142160

161+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
143162
expected_list.append(span_event_end_fmt("SP1", "foo"))
144163

145164
self.assertListEqual(spans_calls_list, expected_list)
@@ -176,6 +195,20 @@ def test_on_start(self):
176195
)
177196
multi_processor.shutdown()
178197

198+
def test_on_ending(self):
199+
multi_processor = self.create_multi_span_processor()
200+
201+
mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 5)]
202+
for mock_processor in mocks:
203+
multi_processor.add_span_processor(mock_processor)
204+
205+
span = self.create_default_span()
206+
multi_processor.on_ending(span)
207+
208+
for mock_processor in mocks:
209+
mock_processor.on_ending.assert_called_once_with(span)
210+
multi_processor.shutdown()
211+
179212
def test_on_end(self):
180213
multi_processor = self.create_multi_span_processor()
181214

opentelemetry-sdk/tests/trace/test_trace.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,10 @@ def span_event_start_fmt(span_processor_name, span_name):
14281428
return span_processor_name + ":" + span_name + ":start"
14291429

14301430

1431+
def span_event_ending_fmt(span_processor_name, span_name):
1432+
return span_processor_name + ":" + span_name + ":ending"
1433+
1434+
14311435
def span_event_end_fmt(span_processor_name, span_name):
14321436
return span_processor_name + ":" + span_name + ":end"
14331437

@@ -1442,6 +1446,9 @@ def on_start(
14421446
) -> None:
14431447
self.span_list.append(span_event_start_fmt(self.name, span.name))
14441448

1449+
def on_ending(self, span: "trace.ReadableSpan") -> None:
1450+
self.span_list.append(span_event_ending_fmt(self.name, span.name))
1451+
14451452
def on_end(self, span: "trace.ReadableSpan") -> None:
14461453
self.span_list.append(span_event_end_fmt(self.name, span.name))
14471454

@@ -1478,10 +1485,13 @@ def test_span_processor(self):
14781485
with tracer.start_as_current_span("baz"):
14791486
expected_list.append(span_event_start_fmt("SP1", "baz"))
14801487

1488+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
14811489
expected_list.append(span_event_end_fmt("SP1", "baz"))
14821490

1491+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
14831492
expected_list.append(span_event_end_fmt("SP1", "bar"))
14841493

1494+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
14851495
expected_list.append(span_event_end_fmt("SP1", "foo"))
14861496

14871497
self.assertListEqual(spans_calls_list, expected_list)
@@ -1504,12 +1514,18 @@ def test_span_processor(self):
15041514
expected_list.append(span_event_start_fmt("SP1", "baz"))
15051515
expected_list.append(span_event_start_fmt("SP2", "baz"))
15061516

1517+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
1518+
expected_list.append(span_event_ending_fmt("SP2", "baz"))
15071519
expected_list.append(span_event_end_fmt("SP1", "baz"))
15081520
expected_list.append(span_event_end_fmt("SP2", "baz"))
15091521

1522+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
1523+
expected_list.append(span_event_ending_fmt("SP2", "bar"))
15101524
expected_list.append(span_event_end_fmt("SP1", "bar"))
15111525
expected_list.append(span_event_end_fmt("SP2", "bar"))
15121526

1527+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
1528+
expected_list.append(span_event_ending_fmt("SP2", "foo"))
15131529
expected_list.append(span_event_end_fmt("SP1", "foo"))
15141530
expected_list.append(span_event_end_fmt("SP2", "foo"))
15151531

@@ -1532,10 +1548,13 @@ def test_add_span_processor_after_span_creation(self):
15321548
# add span processor after spans have been created
15331549
tracer_provider.add_span_processor(sp)
15341550

1551+
expected_list.append(span_event_ending_fmt("SP1", "baz"))
15351552
expected_list.append(span_event_end_fmt("SP1", "baz"))
15361553

1554+
expected_list.append(span_event_ending_fmt("SP1", "bar"))
15371555
expected_list.append(span_event_end_fmt("SP1", "bar"))
15381556

1557+
expected_list.append(span_event_ending_fmt("SP1", "foo"))
15391558
expected_list.append(span_event_end_fmt("SP1", "foo"))
15401559

15411560
self.assertListEqual(spans_calls_list, expected_list)

0 commit comments

Comments
 (0)