Skip to content

Commit d5e5471

Browse files
mauriciovasquezbernalOberon00
authored andcommitted
sdk: add Exporter interface, SimpleSpanProcessor and InMemorySpanExporter (#119)
* sdk/trace: add Exporter interface and SimpleExportSpanProcessor Exporter is an interface that allows different services to export recorded spans in its own format. SimpleExportSpanProcessor is an implementation of SpanProcessor that passes ended spans directly to a configured Exporter. The current interface for exporters directly receives an SDK Span, it could be improved in the future to receive a different object containing a representation of the Span. * sdk/trace/exporter: add InMemorySpanExporter InMemorySpanExporter is a simple implementation of the Exporter interface that saves the exported spans in a list in memory. This class is useful for testing purposes.
1 parent 731ed02 commit d5e5471

File tree

5 files changed

+290
-0
lines changed

5 files changed

+290
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2019, OpenTelemetry 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 logging
16+
import typing
17+
from enum import Enum
18+
19+
from .. import Span, SpanProcessor
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
class SpanExportResult(Enum):
25+
SUCCESS = 0
26+
FAILED_RETRYABLE = 1
27+
FAILED_NOT_RETRYABLE = 2
28+
29+
30+
class SpanExporter:
31+
"""Interface for exporting spans.
32+
33+
Interface to be implemented by services that want to export recorded in
34+
its own format.
35+
36+
To export data this MUST be registered to the :class`..Tracer` using a
37+
`SimpleExportSpanProcessor` or a `BatchSpanProcessor`.
38+
"""
39+
40+
def export(self, spans: typing.Sequence[Span]) -> "SpanExportResult":
41+
"""Exports a batch of telemetry data.
42+
43+
Args:
44+
spans: The list of `Span`s to be exported
45+
46+
Returns:
47+
The result of the export
48+
"""
49+
50+
def shutdown(self) -> None:
51+
"""Shuts down the exporter.
52+
53+
Called when the SDK is shut down.
54+
"""
55+
56+
57+
class SimpleExportSpanProcessor(SpanProcessor):
58+
"""Simple SpanProcessor implementation.
59+
60+
SimpleExportSpanProcessor is an implementation of `SpanProcessor` that
61+
passes ended spans directly to the configured `SpanExporter`.
62+
"""
63+
64+
def __init__(self, span_exporter: SpanExporter):
65+
self.span_exporter = span_exporter
66+
67+
def on_start(self, span: Span) -> None:
68+
pass
69+
70+
def on_end(self, span: Span) -> None:
71+
try:
72+
self.span_exporter.export((span,))
73+
# pylint: disable=broad-except
74+
except Exception as exc:
75+
logger.warning("Exception while exporting data: %s", exc)
76+
77+
def shutdown(self) -> None:
78+
self.span_exporter.shutdown()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright 2019, OpenTelemetry 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 threading
16+
import typing
17+
18+
from .. import Span
19+
from . import SpanExporter, SpanExportResult
20+
21+
22+
class InMemorySpanExporter(SpanExporter):
23+
"""Implementation of :class:`.Exporter` that stores spans in memory.
24+
25+
This class can be used for testing purposes. It stores the exported spans
26+
in a list in memory that can be retrieved using the
27+
:func:`.get_finished_spans` method.
28+
"""
29+
30+
def __init__(self):
31+
self._finished_spans = []
32+
self._stopped = False
33+
self._lock = threading.Lock()
34+
35+
def clear(self):
36+
"""Clear list of collected spans."""
37+
with self._lock:
38+
self._finished_spans.clear()
39+
40+
def get_finished_spans(self):
41+
"""Get list of collected spans."""
42+
with self._lock:
43+
return tuple(self._finished_spans)
44+
45+
def export(self, spans: typing.Sequence[Span]) -> SpanExportResult:
46+
"""Stores a list of spans in memory."""
47+
if self._stopped:
48+
return SpanExportResult.FAILED_NOT_RETRYABLE
49+
with self._lock:
50+
self._finished_spans.extend(spans)
51+
return SpanExportResult.SUCCESS
52+
53+
def shutdown(self):
54+
"""Shut downs the exporter.
55+
56+
Calls to export after the exporter has been shut down will fail.
57+
"""
58+
self._stopped = True
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2019, OpenTelemetry 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.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2019, OpenTelemetry 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+
17+
from opentelemetry.sdk import trace
18+
from opentelemetry.sdk.trace import export
19+
20+
21+
class TestSimpleExportSpanProcessor(unittest.TestCase):
22+
def test_simple_span_processor(self):
23+
class MySpanExporter(export.SpanExporter):
24+
def __init__(self, destination):
25+
self.destination = destination
26+
27+
def export(self, spans: trace.Span) -> export.SpanExportResult:
28+
self.destination.extend(span.name for span in spans)
29+
return export.SpanExportResult.SUCCESS
30+
31+
tracer = trace.Tracer()
32+
33+
spans_names_list = []
34+
35+
my_exporter = MySpanExporter(destination=spans_names_list)
36+
span_processor = export.SimpleExportSpanProcessor(my_exporter)
37+
tracer.add_span_processor(span_processor)
38+
39+
with tracer.start_span("foo"):
40+
with tracer.start_span("bar"):
41+
with tracer.start_span("xxx"):
42+
pass
43+
44+
self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright 2019, OpenTelemetry 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+
from unittest import mock
17+
18+
from opentelemetry import trace as trace_api
19+
from opentelemetry.sdk import trace
20+
from opentelemetry.sdk.trace import export
21+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
22+
InMemorySpanExporter,
23+
)
24+
25+
26+
class TestInMemorySpanExporter(unittest.TestCase):
27+
def test_get_finished_spans(self):
28+
tracer = trace.Tracer()
29+
30+
memory_exporter = InMemorySpanExporter()
31+
span_processor = export.SimpleExportSpanProcessor(memory_exporter)
32+
tracer.add_span_processor(span_processor)
33+
34+
with tracer.start_span("foo"):
35+
with tracer.start_span("bar"):
36+
with tracer.start_span("xxx"):
37+
pass
38+
39+
span_list = memory_exporter.get_finished_spans()
40+
spans_names_list = [span.name for span in span_list]
41+
self.assertListEqual(["xxx", "bar", "foo"], spans_names_list)
42+
43+
def test_clear(self):
44+
tracer = trace.Tracer()
45+
46+
memory_exporter = InMemorySpanExporter()
47+
span_processor = export.SimpleExportSpanProcessor(memory_exporter)
48+
tracer.add_span_processor(span_processor)
49+
50+
with tracer.start_span("foo"):
51+
with tracer.start_span("bar"):
52+
with tracer.start_span("xxx"):
53+
pass
54+
55+
memory_exporter.clear()
56+
span_list = memory_exporter.get_finished_spans()
57+
self.assertEqual(len(span_list), 0)
58+
59+
def test_shutdown(self):
60+
tracer = trace.Tracer()
61+
62+
memory_exporter = InMemorySpanExporter()
63+
span_processor = export.SimpleExportSpanProcessor(memory_exporter)
64+
tracer.add_span_processor(span_processor)
65+
66+
with tracer.start_span("foo"):
67+
with tracer.start_span("bar"):
68+
with tracer.start_span("xxx"):
69+
pass
70+
71+
span_list = memory_exporter.get_finished_spans()
72+
self.assertEqual(len(span_list), 3)
73+
74+
memory_exporter.shutdown()
75+
76+
# after shutdown no new spans are accepted
77+
with tracer.start_span("foo"):
78+
with tracer.start_span("bar"):
79+
with tracer.start_span("xxx"):
80+
pass
81+
82+
span_list = memory_exporter.get_finished_spans()
83+
self.assertEqual(len(span_list), 3)
84+
85+
def test_return_code(self):
86+
span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
87+
span_list = (span,)
88+
memory_exporter = InMemorySpanExporter()
89+
90+
ret = memory_exporter.export(span_list)
91+
self.assertEqual(ret, export.SpanExportResult.SUCCESS)
92+
93+
memory_exporter.shutdown()
94+
95+
# after shutdown export should fail
96+
ret = memory_exporter.export(span_list)
97+
self.assertEqual(ret, export.SpanExportResult.FAILED_NOT_RETRYABLE)

0 commit comments

Comments
 (0)