From c11f724e2f6aedc2017556f2966f441a15eb4578 Mon Sep 17 00:00:00 2001 From: shayannab Date: Fri, 12 Jun 2026 13:18:36 +0530 Subject: [PATCH 1/5] fix(sdk): add max_spans limit to InMemorySpanExporter --- .../sdk/trace/export/in_memory_span_exporter.py | 12 +++++++++--- .../trace/export/test_in_memory_span_exporter.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) 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 index 25a66294659..508bb46f6be 100644 --- 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 @@ -3,6 +3,7 @@ import threading import typing +from collections import deque from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult @@ -14,10 +15,15 @@ class InMemorySpanExporter(SpanExporter): This class can be used for testing purposes. It stores the exported spans in a list in memory that can be retrieved using the :func:`.get_finished_spans` method. + + Args: + max_spans: Maximum number of spans to store in memory. When the limit + is reached, the oldest spans are dropped automatically. If ``None`` + (default), there is no limit. """ - def __init__(self) -> None: - self._finished_spans: list[ReadableSpan] = [] + def __init__(self, max_spans: typing.Optional[int] = None) -> None: + self._finished_spans: deque[ReadableSpan] = deque(maxlen=max_spans) self._stopped = False self._lock = threading.Lock() @@ -47,4 +53,4 @@ def shutdown(self) -> None: self._stopped = True def force_flush(self, timeout_millis: int = 30000) -> bool: - return True + return True \ No newline at end of file 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 index 7f30f70dd97..258d3d8389c 100644 --- a/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py +++ b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py @@ -62,3 +62,18 @@ def test_return_code(self): # after shutdown export should fail ret = memory_exporter.export(span_list) self.assertEqual(ret, export.SpanExportResult.FAILURE) + def test_max_spans_limit(self): + """Test that max_spans limits memory usage by dropping oldest spans.""" + exporter = InMemorySpanExporter(max_spans=2) + span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext)) + exporter.export([span, span, span]) + span_list = exporter.get_finished_spans() + self.assertEqual(len(span_list), 2) + + def test_max_spans_none_is_unlimited(self): + """Test that max_spans=None (default) imposes no limit.""" + exporter = InMemorySpanExporter(max_spans=None) + span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext)) + exporter.export([span] * 100) + span_list = exporter.get_finished_spans() + self.assertEqual(len(span_list), 100) \ No newline at end of file From 8c88b6e9bd632c3d272fd225c0aad6ca34794cbb Mon Sep 17 00:00:00 2001 From: shayannab Date: Sat, 20 Jun 2026 19:50:17 +0530 Subject: [PATCH 2/5] test(sdk): improve test coverage and modernize type hints in InMemorySpanExporter - Update InMemorySpanExporter type hint from typing.Optional[int] to int | None. - Refactor test_max_spans_limit to test distinct spans and assert order. --- .../sdk/trace/export/in_memory_span_exporter.py | 5 ++--- .../tests/trace/export/test_in_memory_span_exporter.py | 10 +++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) 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 index 94e104fc997..739a3d58153 100644 --- 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 @@ -3,7 +3,6 @@ import collections.abc import threading -import typing from collections import deque from opentelemetry.sdk.trace import ReadableSpan @@ -23,7 +22,7 @@ class InMemorySpanExporter(SpanExporter): (default), there is no limit. """ - def __init__(self, max_spans: typing.Optional[int] = None) -> None: + def __init__(self, max_spans: int | None = None) -> None: self._finished_spans: deque[ReadableSpan] = deque(maxlen=max_spans) self._stopped = False self._lock = threading.Lock() @@ -56,4 +55,4 @@ def shutdown(self) -> None: self._stopped = True def force_flush(self, timeout_millis: int = 30000) -> bool: - return True \ No newline at end of file + return 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 index 258d3d8389c..9bad9c7f9be 100644 --- a/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py +++ b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py @@ -62,13 +62,17 @@ def test_return_code(self): # after shutdown export should fail ret = memory_exporter.export(span_list) self.assertEqual(ret, export.SpanExportResult.FAILURE) + def test_max_spans_limit(self): """Test that max_spans limits memory usage by dropping oldest spans.""" exporter = InMemorySpanExporter(max_spans=2) - span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext)) - exporter.export([span, span, span]) + span1 = trace._Span("span1", mock.Mock(spec=trace_api.SpanContext)) + span2 = trace._Span("span2", mock.Mock(spec=trace_api.SpanContext)) + span3 = trace._Span("span3", mock.Mock(spec=trace_api.SpanContext)) + exporter.export([span1, span2, span3]) span_list = exporter.get_finished_spans() self.assertEqual(len(span_list), 2) + self.assertSequenceEqual((span2, span3), span_list) def test_max_spans_none_is_unlimited(self): """Test that max_spans=None (default) imposes no limit.""" @@ -76,4 +80,4 @@ def test_max_spans_none_is_unlimited(self): span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext)) exporter.export([span] * 100) span_list = exporter.get_finished_spans() - self.assertEqual(len(span_list), 100) \ No newline at end of file + self.assertEqual(len(span_list), 100) From 1aa77ae462b972e05f530556a6e250e02f405751 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 9 Jul 2026 18:53:26 +0200 Subject: [PATCH 3/5] Apply suggestion from @xrmx --- .../opentelemetry/sdk/trace/export/in_memory_span_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 739a3d58153..c4f4ea49d27 100644 --- 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 @@ -13,7 +13,7 @@ class InMemorySpanExporter(SpanExporter): """Implementation of :class:`.SpanExporter` 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 retrieved using the + in a deque in memory that can be retrieved using the :func:`.get_finished_spans` method. Args: From dc7e16a16aa8f14b69304a00cd73eca33fb54224 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 10 Jul 2026 09:50:44 +0200 Subject: [PATCH 4/5] Create 5296.added --- 5296.added | 1 + 1 file changed, 1 insertion(+) create mode 100644 5296.added diff --git a/5296.added b/5296.added new file mode 100644 index 00000000000..eed34f357c9 --- /dev/null +++ b/5296.added @@ -0,0 +1 @@ +`opentelemetry-sdk`: Make it possible to limit the size of stored spans in InMemorySpanExporter From e29762e90cf9d5aa400b4a4e8783bb35ad9c60c3 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 10 Jul 2026 09:51:47 +0200 Subject: [PATCH 5/5] Rename 5296.added to .changelog/5296.added --- 5296.added => .changelog/5296.added | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename 5296.added => .changelog/5296.added (100%) diff --git a/5296.added b/.changelog/5296.added similarity index 100% rename from 5296.added rename to .changelog/5296.added