From c77ecd0bbec1de1025fb330242bac6a2246bbd09 Mon Sep 17 00:00:00 2001 From: ajuijas Date: Fri, 13 Mar 2026 15:48:54 +0530 Subject: [PATCH 1/3] fix(logs): optimize LogRecord memory by removing redundant context Removed direct storage of the Context object in LogRecord to prevent memory inflation when logs are buffered. Correlation IDs (TraceId, SpanId, TraceFlags) are still preserved. Resolves #4957 --- CHANGELOG.md | 2 ++ opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py | 1 - opentelemetry-sdk/tests/logs/test_handler.py | 1 - opentelemetry-sdk/tests/logs/test_log_record.py | 1 - opentelemetry-sdk/tests/logs/test_logs.py | 2 -- 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfed95eb4aa..632aeabd22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-api`: Optimize `LogRecord` memory by removing direct storage of the `Context` object. + ([#4957](https://github.com/open-telemetry/opentelemetry-python/issues/4957)) - `opentelemetry-sdk`: Add file configuration support with YAML/JSON loading, environment variable substitution, and schema validation against the vendored OTel config JSON schema ([#4898](https://github.com/open-telemetry/opentelemetry-python/pull/4898)) - Fix intermittent CI failures in `getting-started` and `tracecontext` jobs caused by GitHub git CDN SHA propagation lag by installing contrib packages from the already-checked-out local copy instead of a second git clone diff --git a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py index bbcfcddc846..46f16b75f31 100644 --- a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py @@ -118,7 +118,6 @@ def __init__( if observed_timestamp is None: observed_timestamp = time_ns() self.observed_timestamp = observed_timestamp - self.context = context self.trace_id = trace_id or span_context.trace_id self.span_id = span_id or span_context.span_id self.trace_flags = trace_flags or span_context.trace_flags diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 37de01508eb..385f4c90799 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -346,7 +346,6 @@ def test_log_record_trace_correlation(self): record.log_record.severity_number, SeverityNumber.FATAL, ) - self.assertEqual(record.log_record.context, mock_context) span_context = span.get_span_context() self.assertEqual( record.log_record.trace_id, span_context.trace_id diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index 3f9c4763018..de54fab4b60 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -221,7 +221,6 @@ def test_log_record_from_api_log_record(self): self.assertEqual(record.log_record.timestamp, 1) self.assertEqual(record.log_record.observed_timestamp, 2) - self.assertEqual(record.log_record.context, get_current()) # trace_id, span_id, and trace_flags come from the context's span self.assertEqual(record.log_record.trace_id, 0) self.assertEqual(record.log_record.span_id, 0) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 6a9c95685cd..71fb0c89f66 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -180,7 +180,6 @@ def test_can_emit_api_logrecord(self): self.assertTrue(isinstance(log_record, LogRecord)) self.assertEqual(log_record.timestamp, None) self.assertEqual(log_record.observed_timestamp, 0) - self.assertIsNotNone(log_record.context) self.assertEqual(log_record.severity_number, None) self.assertEqual(log_record.severity_text, None) self.assertEqual(log_record.body, "a log line") @@ -208,7 +207,6 @@ def test_can_emit_with_keywords_arguments(self): self.assertTrue(isinstance(result_log_record, LogRecord)) self.assertEqual(result_log_record.timestamp, 100) self.assertEqual(result_log_record.observed_timestamp, 101) - self.assertIsNotNone(result_log_record.context) self.assertEqual( result_log_record.severity_number, SeverityNumber.WARN ) From 1070ad9d4bdb3c18529e191d0c96e3536b50225f Mon Sep 17 00:00:00 2001 From: ajuijas Date: Sat, 14 Mar 2026 20:24:23 +0530 Subject: [PATCH 2/3] fix(logs): reduce memory usage in BatchLogRecordProcessor (#4957) Clear LogRecord context references before buffering in the SDK to prevent memory inflation while maintaining context access for synchronous processors. --- CHANGELOG.md | 2 +- .../opentelemetry/sdk/_logs/_internal/export/__init__.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f054841350..f367fe8e743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- `opentelemetry-api`: Optimize `LogRecord` memory by removing direct storage of the `Context` object. +- `opentelemetry-sdk`: Optimize `LogRecord` memory in `BatchLogRecordProcessor` by clearing the `Context` object before buffering. ([#4957](https://github.com/open-telemetry/opentelemetry-python/issues/4957)) - `opentelemetry-sdk`: Add file configuration support with YAML/JSON loading, environment variable substitution, and schema validation against the vendored OTel config JSON schema ([#4898](https://github.com/open-telemetry/opentelemetry-python/pull/4898)) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py index 52ce2d6e6ff..499a04a3e21 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -14,6 +14,7 @@ from __future__ import annotations import abc +import copy import enum import logging import sys @@ -286,8 +287,13 @@ def on_emit(self, log_record: ReadWriteLogRecord) -> None: if log_record.resource is not None else Resource.create({}) ) + # Shallow copy the API log record to break the reference to the potentially large context + # while keeping the original context intact for other processors. + api_log_record = copy.copy(log_record.log_record) + api_log_record.context = None + readable_log_record = ReadableLogRecord( - log_record=log_record.log_record, + log_record=api_log_record, resource=resource, instrumentation_scope=log_record.instrumentation_scope, limits=log_record.limits, From d20e6f2b618eb6bd3d486be1425e9b34ecb240b0 Mon Sep 17 00:00:00 2001 From: ajuijas Date: Sat, 14 Mar 2026 20:31:11 +0530 Subject: [PATCH 3/3] Revert "fix(logs): optimize LogRecord memory by removing redundant context" This reverts commit c77ecd0bbec1de1025fb330242bac6a2246bbd09. --- opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py | 1 + opentelemetry-sdk/tests/logs/test_handler.py | 1 + opentelemetry-sdk/tests/logs/test_log_record.py | 1 + opentelemetry-sdk/tests/logs/test_logs.py | 2 ++ 4 files changed, 5 insertions(+) diff --git a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py index 46f16b75f31..bbcfcddc846 100644 --- a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py +++ b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py @@ -118,6 +118,7 @@ def __init__( if observed_timestamp is None: observed_timestamp = time_ns() self.observed_timestamp = observed_timestamp + self.context = context self.trace_id = trace_id or span_context.trace_id self.span_id = span_id or span_context.span_id self.trace_flags = trace_flags or span_context.trace_flags diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 385f4c90799..37de01508eb 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -346,6 +346,7 @@ def test_log_record_trace_correlation(self): record.log_record.severity_number, SeverityNumber.FATAL, ) + self.assertEqual(record.log_record.context, mock_context) span_context = span.get_span_context() self.assertEqual( record.log_record.trace_id, span_context.trace_id diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index de54fab4b60..3f9c4763018 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -221,6 +221,7 @@ def test_log_record_from_api_log_record(self): self.assertEqual(record.log_record.timestamp, 1) self.assertEqual(record.log_record.observed_timestamp, 2) + self.assertEqual(record.log_record.context, get_current()) # trace_id, span_id, and trace_flags come from the context's span self.assertEqual(record.log_record.trace_id, 0) self.assertEqual(record.log_record.span_id, 0) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 71fb0c89f66..6a9c95685cd 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -180,6 +180,7 @@ def test_can_emit_api_logrecord(self): self.assertTrue(isinstance(log_record, LogRecord)) self.assertEqual(log_record.timestamp, None) self.assertEqual(log_record.observed_timestamp, 0) + self.assertIsNotNone(log_record.context) self.assertEqual(log_record.severity_number, None) self.assertEqual(log_record.severity_text, None) self.assertEqual(log_record.body, "a log line") @@ -207,6 +208,7 @@ def test_can_emit_with_keywords_arguments(self): self.assertTrue(isinstance(result_log_record, LogRecord)) self.assertEqual(result_log_record.timestamp, 100) self.assertEqual(result_log_record.observed_timestamp, 101) + self.assertIsNotNone(result_log_record.context) self.assertEqual( result_log_record.severity_number, SeverityNumber.WARN )