From e864c58f4ec0d005779d558c822a74829c6cd7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20V=C3=A1squez?= Date: Fri, 30 Aug 2019 15:56:50 -0500 Subject: [PATCH] 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. --- .../sdk/trace/export/__init__.py | 1 + .../trace/export/in_memory_span_exporter.py | 58 +++++++++++ .../export/test_in_memory_span_exporter.py | 97 +++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/trace/export/in_memory_span_exporter.py create mode 100644 opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py index 26c9c1df568..b7235044ea3 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py @@ -64,6 +64,7 @@ def on_start(self, span: Span) -> None: def on_end(self, span: Span) -> None: try: self.span_exporter.export((span,)) + # pylint: disable=broad-except except Exception as exc: logging.warning("Exception while exporting data: %s", exc) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/in_memory_span_exporter.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/in_memory_span_exporter.py new file mode 100644 index 00000000000..aa5ef1aa329 --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/in_memory_span_exporter.py @@ -0,0 +1,58 @@ +# Copyright 2019, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import threading +import typing + +from .. import Span +from . import SpanExporter, SpanExportResult + + +class InMemorySpanExporter(SpanExporter): + """Implementation of :class:`.Exporter` that stores spans in memory. + + This class can be used for testing purposes. It stores the exported spans + in a list in memory that can be retrieve using the + :func:`.get_finished_spans` method + """ + + def __init__(self): + self._finished_spans = [] + self._stopped = False + self._lock = threading.Lock() + + def clear(self): + """Clear list of collected spans.""" + with self._lock: + self._finished_spans.clear() + + def get_finished_spans(self): + """Get list of collected spans.""" + with self._lock: + return tuple(self._finished_spans) + + def export(self, spans: typing.Sequence[Span]) -> SpanExportResult: + """Stores a list of spans in memory.""" + if self._stopped: + return SpanExportResult.FAILED_NOT_RETRYABLE + with self._lock: + self._finished_spans.extend(spans) + return SpanExportResult.SUCCESS + + def shutdown(self): + """Shut downs the exporter. + + Calls to export after the exporter has been shut down will fail. + """ + self._stopped = True diff --git a/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py new file mode 100644 index 00000000000..01d4487e2ec --- /dev/null +++ b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py @@ -0,0 +1,97 @@ +# Copyright 2019, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from unittest import mock + +from opentelemetry import trace as trace_api +from opentelemetry.sdk import trace +from opentelemetry.sdk.trace import export +from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, +) + + +class TestInMemorySpanExporter(unittest.TestCase): + def test_get_finished_spans(self): + tracer = trace.Tracer() + + memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(memory_exporter) + tracer.add_span_processor(span_processor) + + with tracer.start_span("foo"): + with tracer.start_span("bar"): + with tracer.start_span("xxx"): + pass + + span_list = memory_exporter.get_finished_spans() + spans_names_list = [span.name for span in span_list] + self.assertListEqual(["xxx", "bar", "foo"], spans_names_list) + + def test_clear(self): + tracer = trace.Tracer() + + memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(memory_exporter) + tracer.add_span_processor(span_processor) + + with tracer.start_span("foo"): + with tracer.start_span("bar"): + with tracer.start_span("xxx"): + pass + + memory_exporter.clear() + span_list = memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 0) + + def test_shutdown(self): + tracer = trace.Tracer() + + memory_exporter = InMemorySpanExporter() + span_processor = export.SimpleExportSpanProcessor(memory_exporter) + tracer.add_span_processor(span_processor) + + with tracer.start_span("foo"): + with tracer.start_span("bar"): + with tracer.start_span("xxx"): + pass + + span_list = memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 3) + + memory_exporter.shutdown() + + # after shutdown no new spans are accepted + with tracer.start_span("foo"): + with tracer.start_span("bar"): + with tracer.start_span("xxx"): + pass + + span_list = memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 3) + + def test_return_code(self): + span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) + span_list = (span,) + memory_exporter = InMemorySpanExporter() + + ret = memory_exporter.export(span_list) + self.assertEqual(ret, export.SpanExportResult.SUCCESS) + + memory_exporter.shutdown() + + # after shutdown export should fail + ret = memory_exporter.export(span_list) + self.assertEqual(ret, export.SpanExportResult.FAILED_NOT_RETRYABLE)