From 53338934081217963c6a7dccbb137edc961fd83f Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:04:01 +0000 Subject: [PATCH 01/28] removing LogData and extending SDK LogRecord to have instrumentation scope --- CHANGELOG.md | 2 + .../common/_internal/_log_encoder/__init__.py | 36 +-- .../tests/test_log_encoder.py | 224 ++++++++---------- .../otlp/proto/grpc/_log_exporter/__init__.py | 5 +- .../tests/logs/test_otlp_logs_exporter.py | 86 +++---- .../otlp/proto/http/_log_exporter/__init__.py | 4 +- .../tests/test_proto_log_exporter.py | 111 ++++----- .../sdk/_logs/_internal/__init__.py | 37 ++- .../sdk/_logs/_internal/export/__init__.py | 26 +- .../export/in_memory_log_exporter.py | 6 +- opentelemetry-sdk/tests/logs/test_export.py | 50 ++-- opentelemetry-sdk/tests/logs/test_handler.py | 8 +- .../tests/logs/test_multi_log_processor.py | 6 +- .../shared_internal/test_batch_processor.py | 4 +- 14 files changed, 271 insertions(+), 334 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad3ae2efd8..35951569d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4620](https://github.com/open-telemetry/opentelemetry-python/pull/4620)) - Set expected User-Agent in HTTP headers for grpc OTLP exporter ([#4658](https://github.com/open-telemetry/opentelemetry-python/pull/4658)) +- Remove LogData and extend SDK LogRecord to have instrumentation scope + ([#](https://github.com/open-telemetry/opentelemetry-python/pull/)) ## Version 1.34.0/0.55b0 (2025-06-04) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 000e56ed8bf..2105913413f 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -30,47 +30,47 @@ ResourceLogs, ScopeLogs, ) -from opentelemetry.sdk._logs import LogData +from opentelemetry.sdk._logs import LogRecord as SDKLogRecord -def encode_logs(batch: Sequence[LogData]) -> ExportLogsServiceRequest: +def encode_logs(batch: Sequence[SDKLogRecord]) -> ExportLogsServiceRequest: return ExportLogsServiceRequest(resource_logs=_encode_resource_logs(batch)) -def _encode_log(log_data: LogData) -> PB2LogRecord: +def _encode_log(log_record: SDKLogRecord) -> PB2LogRecord: span_id = ( None - if log_data.log_record.span_id == 0 - else _encode_span_id(log_data.log_record.span_id) + if log_record.span_id == 0 + else _encode_span_id(log_record.span_id) ) trace_id = ( None - if log_data.log_record.trace_id == 0 - else _encode_trace_id(log_data.log_record.trace_id) + if log_record.trace_id == 0 + else _encode_trace_id(log_record.trace_id) ) - body = log_data.log_record.body + body = log_record.body return PB2LogRecord( - time_unix_nano=log_data.log_record.timestamp, - observed_time_unix_nano=log_data.log_record.observed_timestamp, + time_unix_nano=log_record.timestamp, + observed_time_unix_nano=log_record.observed_timestamp, span_id=span_id, trace_id=trace_id, - flags=int(log_data.log_record.trace_flags), + flags=int(log_record.trace_flags), body=_encode_value(body, allow_null=True), - severity_text=log_data.log_record.severity_text, + severity_text=log_record.severity_text, attributes=_encode_attributes( - log_data.log_record.attributes, allow_null=True + log_record.attributes, allow_null=True ), - dropped_attributes_count=log_data.log_record.dropped_attributes, - severity_number=log_data.log_record.severity_number.value, - event_name=log_data.log_record.event_name, + dropped_attributes_count=log_record.dropped_attributes, + severity_number=log_record.severity_number.value, + event_name=log_record.event_name, ) -def _encode_resource_logs(batch: Sequence[LogData]) -> List[ResourceLogs]: +def _encode_resource_logs(batch: Sequence[SDKLogRecord]) -> List[ResourceLogs]: sdk_resource_logs = defaultdict(lambda: defaultdict(list)) for sdk_log in batch: - sdk_resource = sdk_log.log_record.resource + sdk_resource = sdk_log.resource sdk_instrumentation = sdk_log.instrumentation_scope or None pb2_log = _encode_log(sdk_log) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 5407d9f1bca..2307bc70a60 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -45,7 +45,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as PB2Resource, ) -from opentelemetry.sdk._logs import LogData, LogLimits +from opentelemetry.sdk._logs import LogLimits from opentelemetry.sdk._logs import LogRecord as SDKLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope @@ -88,7 +88,7 @@ def test_dropped_attributes_count(self): ) @staticmethod - def _get_sdk_log_data() -> List[LogData]: + def _get_sdk_log_data() -> List[SDKLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -99,35 +99,31 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log1 = LogData( - log_record=SDKLogRecord( - timestamp=1644650195189786880, - observed_timestamp=1644650195189786881, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", - resource=SDKResource( - {"first_resource": "value"}, - "resource_schema_url", - ), - attributes={"a": 1, "b": "c"}, + log1 = SDKLogRecord( + timestamp=1644650195189786880, + observed_timestamp=1644650195189786881, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + resource=SDKResource( + {"first_resource": "value"}, + "resource_schema_url", ), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), ) - log2 = LogData( - log_record=SDKLogRecord( - timestamp=1644650249738562048, - observed_timestamp=1644650249738562049, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", - resource=SDKResource({"second_resource": "CASE"}), - attributes={}, - ), + log2 = SDKLogRecord( + timestamp=1644650249738562048, + observed_timestamp=1644650249738562049, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + resource=SDKResource({"second_resource": "CASE"}), + attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -143,17 +139,15 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log3 = LogData( - log_record=SDKLogRecord( - timestamp=1644650427658989056, - observed_timestamp=1644650427658989057, - context=ctx_log3, - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG, - body="To our galaxy", - resource=SDKResource({"second_resource": "CASE"}), - attributes={"a": 1, "b": "c"}, - ), + log3 = SDKLogRecord( + timestamp=1644650427658989056, + observed_timestamp=1644650427658989057, + context=ctx_log3, + severity_text="DEBUG", + severity_number=SeverityNumber.DEBUG, + body="To our galaxy", + resource=SDKResource({"second_resource": "CASE"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=None, ) @@ -167,20 +161,18 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log4 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683008, - observed_timestamp=1644650584292683009, - context=ctx_log4, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Love is the one thing that transcends time and space", - resource=SDKResource( - {"first_resource": "value"}, - "resource_schema_url", - ), - attributes={"filename": "model.py", "func_name": "run_method"}, + log4 = SDKLogRecord( + timestamp=1644650584292683008, + observed_timestamp=1644650584292683009, + context=ctx_log4, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Love is the one thing that transcends time and space", + resource=SDKResource( + {"first_resource": "value"}, + "resource_schema_url", ), + attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "another_name", "another_version" ), @@ -196,17 +188,15 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log5 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683009, - observed_timestamp=1644650584292683010, - context=ctx_log5, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body={"error": None, "array_with_nones": [1, None, 2]}, - resource=SDKResource({}), - attributes={}, - ), + log5 = SDKLogRecord( + timestamp=1644650584292683009, + observed_timestamp=1644650584292683010, + context=ctx_log5, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body={"error": None, "array_with_nones": [1, None, 2]}, + resource=SDKResource({}), + attributes={}, instrumentation_scope=InstrumentationScope( "last_name", "last_version" ), @@ -222,20 +212,18 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log6 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683022, - observed_timestamp=1644650584292683022, - context=ctx_log6, - severity_text="ERROR", - severity_number=SeverityNumber.ERROR, - body="This instrumentation scope has a schema url", - resource=SDKResource( - {"first_resource": "value"}, - "resource_schema_url", - ), - attributes={"filename": "model.py", "func_name": "run_method"}, + log6 = SDKLogRecord( + timestamp=1644650584292683022, + observed_timestamp=1644650584292683022, + context=ctx_log6, + severity_text="ERROR", + severity_number=SeverityNumber.ERROR, + body="This instrumentation scope has a schema url", + resource=SDKResource( + {"first_resource": "value"}, + "resource_schema_url", ), + attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "scope_with_url", "scope_with_url_version", @@ -253,20 +241,18 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log7 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683033, - observed_timestamp=1644650584292683033, - context=ctx_log7, - severity_text="FATAL", - severity_number=SeverityNumber.FATAL, - body="This instrumentation scope has a schema url and attributes", - resource=SDKResource( - {"first_resource": "value"}, - "resource_schema_url", - ), - attributes={"filename": "model.py", "func_name": "run_method"}, + log7 = SDKLogRecord( + timestamp=1644650584292683033, + observed_timestamp=1644650584292683033, + context=ctx_log7, + severity_text="FATAL", + severity_number=SeverityNumber.FATAL, + body="This instrumentation scope has a schema url and attributes", + resource=SDKResource( + {"first_resource": "value"}, + "resource_schema_url", ), + attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "scope_with_attributes", "scope_with_attributes_version", @@ -285,21 +271,17 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log8 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683044, - observed_timestamp=1644650584292683044, - context=ctx_log8, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Test export of extended attributes", - resource=SDKResource({}), - attributes={ - "extended": { - "sequence": [{"inner": "mapping", "none": None}] - } - }, - ), + log8 = SDKLogRecord( + timestamp=1644650584292683044, + observed_timestamp=1644650584292683044, + context=ctx_log8, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Test export of extended attributes", + resource=SDKResource({}), + attributes={ + "extended": {"sequence": [{"inner": "mapping", "none": None}]} + }, instrumentation_scope=InstrumentationScope( "extended_name", "extended_version" ), @@ -601,7 +583,7 @@ def get_test_logs( return sdk_logs, pb2_service_request @staticmethod - def _get_test_logs_dropped_attributes() -> List[LogData]: + def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -612,17 +594,15 @@ def _get_test_logs_dropped_attributes() -> List[LogData]: ) ) ) - log1 = LogData( - log_record=SDKLogRecord( - timestamp=1644650195189786880, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", - resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c", "user_id": "B121092"}, - limits=LogLimits(max_attributes=1), - ), + log1 = SDKLogRecord( + timestamp=1644650195189786880, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + resource=SDKResource({"first_resource": "value"}), + attributes={"a": 1, "b": "c", "user_id": "B121092"}, + limits=LogLimits(max_attributes=1), instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -630,16 +610,14 @@ def _get_test_logs_dropped_attributes() -> List[LogData]: ctx_log2 = set_span_in_context( NonRecordingSpan(SpanContext(0, 0, False)) ) - log2 = LogData( - log_record=SDKLogRecord( - timestamp=1644650249738562048, - context=ctx_log2, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", - resource=SDKResource({"second_resource": "CASE"}), - attributes={}, - ), + log2 = SDKLogRecord( + timestamp=1644650249738562048, + context=ctx_log2, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + resource=SDKResource({"second_resource": "CASE"}), + attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index 70f3df444a4..d568d207027 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -28,7 +28,6 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2_grpc import ( LogsServiceStub, ) -from opentelemetry.sdk._logs import LogData from opentelemetry.sdk._logs import LogRecord as SDKLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult from opentelemetry.sdk.environment_variables import ( @@ -105,11 +104,11 @@ def __init__( ) def _translate_data( - self, data: Sequence[LogData] + self, data: Sequence[SDKLogRecord] ) -> ExportLogsServiceRequest: return encode_logs(data) - def export(self, batch: Sequence[LogData]) -> LogExportResult: + def export(self, batch: Sequence[SDKLogRecord]) -> LogExportResult: return self._export(batch) def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index a8e015e8216..c0972d1aaa0 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -39,7 +39,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as OTLPResource, ) -from opentelemetry.sdk._logs import LogData, LogRecord +from opentelemetry.sdk._logs import LogRecord from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, @@ -74,16 +74,14 @@ def setUp(self): ) ) ) - self.log_data_1 = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_1, - severity_text="WARNING", - severity_number=SeverityNumber.WARN, - body="Zhengzhou, We have a heaviest rains in 1000 years", - resource=SDKResource({"key": "value"}), - attributes={"a": 1, "b": "c"}, - ), + self.log_data_1 = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_1, + severity_text="WARNING", + severity_number=SeverityNumber.WARN, + body="Zhengzhou, We have a heaviest rains in 1000 years", + resource=SDKResource({"key": "value"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -98,16 +96,14 @@ def setUp(self): ) ) ) - self.log_data_2 = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_2, - severity_text="INFO", - severity_number=SeverityNumber.INFO2, - body="Sydney, Opera House is closed", - resource=SDKResource({"key": "value"}), - attributes={"custom_attr": [1, 2, 3]}, - ), + self.log_data_2 = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_2, + severity_text="INFO", + severity_number=SeverityNumber.INFO2, + body="Sydney, Opera House is closed", + resource=SDKResource({"key": "value"}), + attributes={"custom_attr": [1, 2, 3]}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -122,15 +118,13 @@ def setUp(self): ) ) ) - self.log_data_3 = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_3, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Mumbai, Boil water before drinking", - resource=SDKResource({"service": "myapp"}), - ), + self.log_data_3 = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_3, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Mumbai, Boil water before drinking", + resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "third_name", "third_version" ), @@ -140,15 +134,13 @@ def setUp(self): SpanContext(0, 5213367945872657629, False, TraceFlags(0x01)) ) ) - self.log_data_4 = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_4, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Invalid trace id check", - resource=SDKResource({"service": "myapp"}), - ), + self.log_data_4 = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_4, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Invalid trace id check", + resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "fourth_name", "fourth_version" ), @@ -163,15 +155,13 @@ def setUp(self): ) ) ) - self.log_data_5 = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_5, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Invalid span id check", - resource=SDKResource({"service": "myapp"}), - ), + self.log_data_5 = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_5, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Invalid span id check", + resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "fifth_name", "fifth_version" ), diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index c64f269b9ed..433bfdcf008 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -32,7 +32,7 @@ from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, ) -from opentelemetry.sdk._logs import LogData +from opentelemetry.sdk._logs import LogRecord from opentelemetry.sdk._logs.export import ( LogExporter, LogExportResult, @@ -156,7 +156,7 @@ def _export(self, serialized_data: bytes, timeout_sec: float): ) return resp - def export(self, batch: Sequence[LogData]) -> LogExportResult: + def export(self, batch: Sequence[LogRecord]) -> LogExportResult: if self._shutdown: _logger.warning("Exporter already shutdown, ignoring batch") return LogExportResult.FAILURE diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index 024015d0fa5..c4bfe271de2 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -38,7 +38,6 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, ) -from opentelemetry.sdk._logs import LogData from opentelemetry.sdk._logs import LogRecord as SDKLogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( @@ -232,16 +231,14 @@ def test_exported_log_without_trace_id(self): ) ) ) - log = LogData( - log_record=SDKLogRecord( - timestamp=1644650195189786182, - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Invalid trace id check", - resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, - ), + log = SDKLogRecord( + timestamp=1644650195189786182, + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Invalid trace id check", + resource=SDKResource({"first_resource": "value"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope("name", "version"), ) log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log) @@ -268,16 +265,14 @@ def test_exported_log_without_span_id(self): ) ) - log = LogData( - log_record=SDKLogRecord( - timestamp=1644650195189786360, - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Invalid span id check", - resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, - ), + log = SDKLogRecord( + timestamp=1644650195189786360, + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Invalid span id check", + resource=SDKResource({"first_resource": "value"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope("name", "version"), ) log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log) @@ -293,7 +288,7 @@ def test_exported_log_without_span_id(self): self.fail("No log records found") @staticmethod - def _get_sdk_log_data() -> List[LogData]: + def _get_sdk_log_data() -> List[SDKLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -304,16 +299,14 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log1 = LogData( - log_record=SDKLogRecord( - timestamp=1644650195189786880, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", - resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, - ), + log1 = SDKLogRecord( + timestamp=1644650195189786880, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + resource=SDKResource({"first_resource": "value"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -328,16 +321,14 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log2 = LogData( - log_record=SDKLogRecord( - timestamp=1644650249738562048, - context=ctx_log2, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", - resource=SDKResource({"second_resource": "CASE"}), - attributes={}, - ), + log2 = SDKLogRecord( + timestamp=1644650249738562048, + context=ctx_log2, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + resource=SDKResource({"second_resource": "CASE"}), + attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -352,16 +343,14 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log3 = LogData( - log_record=SDKLogRecord( - timestamp=1644650427658989056, - context=ctx_log3, - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG, - body="To our galaxy", - resource=SDKResource({"second_resource": "CASE"}), - attributes={"a": 1, "b": "c"}, - ), + log3 = SDKLogRecord( + timestamp=1644650427658989056, + context=ctx_log3, + severity_text="DEBUG", + severity_number=SeverityNumber.DEBUG, + body="To our galaxy", + resource=SDKResource({"second_resource": "CASE"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=None, ) ctx_log4 = set_span_in_context( @@ -374,16 +363,14 @@ def _get_sdk_log_data() -> List[LogData]: ) ) ) - log4 = LogData( - log_record=SDKLogRecord( - timestamp=1644650584292683008, - context=ctx_log4, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Love is the one thing that transcends time and space", - resource=SDKResource({"first_resource": "value"}), - attributes={"filename": "model.py", "func_name": "run_method"}, - ), + log4 = SDKLogRecord( + timestamp=1644650584292683008, + context=ctx_log4, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Love is the one thing that transcends time and space", + resource=SDKResource({"first_resource": "value"}), + attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "another_name", "another_version" ), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index d033261de08..eded2d90238 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -244,6 +244,7 @@ def __init__( # pylint:disable=too-many-locals attributes: _ExtendedAttributes | None = None, limits: LogLimits | None = _UnsetLogLimits, event_name: str | None = None, + instrumentation_scope: InstrumentationScope | None = None, ): if trace_id or span_id or trace_flags: warnings.warn( @@ -282,6 +283,7 @@ def __init__( # pylint:disable=too-many-locals self.resource = ( resource if isinstance(resource, Resource) else Resource.create({}) ) + self.instrumentation_scope = instrumentation_scope if self.dropped_attributes > 0: warnings.warn( "Log record attributes were dropped due to limits", @@ -336,16 +338,11 @@ def dropped_attributes(self) -> int: return 0 -class LogData: - """Readable LogRecord data plus associated InstrumentationLibrary.""" - - def __init__( - self, - log_record: LogRecord, - instrumentation_scope: InstrumentationScope, - ): - self.log_record = log_record - self.instrumentation_scope = instrumentation_scope +@deprecated( + "Use LogRecord. Since logs are not stable yet this WILL be removed in future releases." +) +class LogData(LogRecord): + pass class LogRecordProcessor(abc.ABC): @@ -357,8 +354,8 @@ class LogRecordProcessor(abc.ABC): """ @abc.abstractmethod - def on_emit(self, log_data: LogData): - """Emits the `LogData`""" + def on_emit(self, log_record: LogRecord): + """Emits the `LogRecord`""" @abc.abstractmethod def shutdown(self): @@ -401,9 +398,9 @@ def add_log_record_processor( with self._lock: self._log_record_processors += (log_record_processor,) - def on_emit(self, log_data: LogData) -> None: + def on_emit(self, log_record: LogRecord) -> None: for lp in self._log_record_processors: - lp.on_emit(log_data) + lp.on_emit(log_record) def shutdown(self) -> None: """Shutdown the log processors one by one""" @@ -475,8 +472,8 @@ def _submit_and_wait( for future in futures: future.result() - def on_emit(self, log_data: LogData): - self._submit_and_wait(lambda lp: lp.on_emit, log_data) + def on_emit(self, log_record: LogRecord): + self._submit_and_wait(lambda lp: lp.on_emit, log_record) def shutdown(self): self._submit_and_wait(lambda lp: lp.shutdown) @@ -676,11 +673,11 @@ def resource(self): return self._resource def emit(self, record: LogRecord): - """Emits the :class:`LogData` by associating :class:`LogRecord` - and instrumentation info. + """Emits the :class:`LogRecord` by setting instrumentation scope + and forwarding to the processor. """ - log_data = LogData(record, self._instrumentation_scope) - self._multi_log_record_processor.on_emit(log_data) + record.instrumentation_scope = self._instrumentation_scope + self._multi_log_record_processor.on_emit(record) class LoggerProvider(APILoggerProvider): 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 ec629221b86..79343af7362 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -26,7 +26,7 @@ detach, set_value, ) -from opentelemetry.sdk._logs import LogData, LogRecord, LogRecordProcessor +from opentelemetry.sdk._logs import LogRecord, LogRecordProcessor from opentelemetry.sdk._shared_internal import BatchProcessor from opentelemetry.sdk.environment_variables import ( OTEL_BLRP_EXPORT_TIMEOUT, @@ -59,10 +59,10 @@ class LogExporter(abc.ABC): """ @abc.abstractmethod - def export(self, batch: Sequence[LogData]): + def export(self, batch: Sequence[LogRecord]): """Exports a batch of logs. Args: - batch: The list of `LogData` objects to be exported + batch: The list of `LogRecord` objects to be exported Returns: The result of the export """ @@ -92,9 +92,9 @@ def __init__( self.out = out self.formatter = formatter - def export(self, batch: Sequence[LogData]): - for data in batch: - self.out.write(self.formatter(data.log_record)) + def export(self, batch: Sequence[LogRecord]): + for log_record in batch: + self.out.write(self.formatter(log_record)) self.out.flush() return LogExportResult.SUCCESS @@ -104,21 +104,20 @@ def shutdown(self): class SimpleLogRecordProcessor(LogRecordProcessor): """This is an implementation of LogRecordProcessor which passes - received logs in the export-friendly LogData representation to the - configured LogExporter, as soon as they are emitted. + received logs directly to the configured LogExporter, as soon as they are emitted. """ def __init__(self, exporter: LogExporter): self._exporter = exporter self._shutdown = False - def on_emit(self, log_data: LogData): + def on_emit(self, log_record: LogRecord): if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") return token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) try: - self._exporter.export((log_data,)) + self._exporter.export((log_record,)) except Exception: # pylint: disable=broad-exception-caught _logger.exception("Exception while exporting logs.") detach(token) @@ -133,8 +132,7 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: # pylint: disable=n class BatchLogRecordProcessor(LogRecordProcessor): """This is an implementation of LogRecordProcessor which creates batches of - received logs in the export-friendly LogData representation and - send to the configured LogExporter, as soon as they are emitted. + received logs and sends them to the configured LogExporter. `BatchLogRecordProcessor` is configurable with the following environment variables which correspond to constructor parameters: @@ -186,8 +184,8 @@ def __init__( "Log", ) - def on_emit(self, log_data: LogData) -> None: - return self._batch_processor.emit(log_data) + def on_emit(self, log_record: LogRecord) -> None: + return self._batch_processor.emit(log_record) def shutdown(self): return self._batch_processor.shutdown() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py index 68cb6b7389a..b4e0239c246 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py @@ -15,7 +15,7 @@ import threading import typing -from opentelemetry.sdk._logs import LogData +from opentelemetry.sdk._logs import LogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult @@ -36,11 +36,11 @@ def clear(self) -> None: with self._lock: self._logs.clear() - def get_finished_logs(self) -> typing.Tuple[LogData, ...]: + def get_finished_logs(self) -> typing.Tuple[LogRecord, ...]: with self._lock: return tuple(self._logs) - def export(self, batch: typing.Sequence[LogData]) -> LogExportResult: + def export(self, batch: typing.Sequence[LogRecord]) -> LogExportResult: if self._stopped: return LogExportResult.FAILURE with self._lock: diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 4b8d98693c5..7db79683730 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -26,7 +26,6 @@ from opentelemetry._logs import SeverityNumber from opentelemetry.sdk import trace from opentelemetry.sdk._logs import ( - LogData, LoggerProvider, LoggingHandler, LogRecord, @@ -54,8 +53,7 @@ ) from opentelemetry.trace.span import INVALID_SPAN_CONTEXT -EMPTY_LOG = LogData( - log_record=LogRecord(), +EMPTY_LOG = LogRecord( instrumentation_scope=InstrumentationScope("example", "example"), ) @@ -76,7 +74,7 @@ def test_simple_log_record_processor_default_level(self): logger.warning("Something is wrong") finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) - warning_log_record = finished_logs[0].log_record + warning_log_record = finished_logs[0] self.assertEqual(warning_log_record.body, "Something is wrong") self.assertEqual(warning_log_record.severity_text, "WARN") self.assertEqual( @@ -106,8 +104,8 @@ def test_simple_log_record_processor_custom_level(self): finished_logs = exporter.get_finished_logs() # Make sure only level >= logging.CRITICAL logs are recorded self.assertEqual(len(finished_logs), 2) - critical_log_record = finished_logs[0].log_record - fatal_log_record = finished_logs[1].log_record + critical_log_record = finished_logs[0] + fatal_log_record = finished_logs[1] self.assertEqual(critical_log_record.body, "Error message") self.assertEqual(critical_log_record.severity_text, "ERROR") self.assertEqual( @@ -140,7 +138,7 @@ def test_simple_log_record_processor_trace_correlation(self): logger.warning("Warning message") finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) - log_record = finished_logs[0].log_record + log_record = finished_logs[0] self.assertEqual(log_record.body, "Warning message") self.assertEqual(log_record.severity_text, "WARN") self.assertEqual(log_record.severity_number, SeverityNumber.WARN) @@ -159,7 +157,7 @@ def test_simple_log_record_processor_trace_correlation(self): logger.critical("Critical message within span") finished_logs = exporter.get_finished_logs() - log_record = finished_logs[0].log_record + log_record = finished_logs[0] self.assertEqual(log_record.body, "Critical message within span") self.assertEqual(log_record.severity_text, "CRITICAL") self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) @@ -187,7 +185,7 @@ def test_simple_log_record_processor_shutdown(self): logger.warning("Something is wrong") finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) - warning_log_record = finished_logs[0].log_record + warning_log_record = finished_logs[0] self.assertEqual(warning_log_record.body, "Something is wrong") self.assertEqual(warning_log_record.severity_text, "WARN") self.assertEqual( @@ -230,10 +228,7 @@ def test_simple_log_record_processor_different_msg_types(self): (["list", "of", "strings"], "WARN"), ({"key": "value"}, "ERROR"), ] - emitted = [ - (item.log_record.body, item.log_record.severity_text) - for item in finished_logs - ] + emitted = [(item.body, item.severity_text) for item in finished_logs] self.assertEqual(expected, emitted) for item in finished_logs: self.assertEqual( @@ -283,7 +278,7 @@ def test_simple_log_record_processor_custom_single_obj(self): (["a non-string with a percent-s", "%s"]), ] for emitted, expected in zip(finished_logs, expected): - self.assertEqual(emitted.log_record.body, expected) + self.assertEqual(emitted.body, expected) self.assertEqual(emitted.instrumentation_scope.name, "single_obj") def test_simple_log_record_processor_different_msg_types_with_formatter( @@ -329,10 +324,7 @@ def test_simple_log_record_processor_different_msg_types_with_formatter( ), ("different_msg_types - ERROR - {'key': 'value'}", "ERROR"), ] - emitted = [ - (item.log_record.body, item.log_record.severity_text) - for item in finished_logs - ] + emitted = [(item.body, item.severity_text) for item in finished_logs] self.assertEqual(expected, emitted) @@ -608,16 +600,14 @@ def test_export(self): # pylint: disable=no-self-use ) ) ) - log_data = LogData( - log_record=LogRecord( - timestamp=int(time.time() * 1e9), - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Zhengzhou, We have a heaviest rains in 1000 years", - resource=SDKResource({"key": "value"}), - attributes={"a": 1, "b": "c"}, - ), + log_record = LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Zhengzhou, We have a heaviest rains in 1000 years", + resource=SDKResource({"key": "value"}), + attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -627,9 +617,9 @@ def test_export(self): # pylint: disable=no-self-use # the exporter instance instead. with patch.object(exporter, "out") as mock_stdout: - exporter.export([log_data]) + exporter.export([log_record]) mock_stdout.write.assert_called_once_with( - log_data.log_record.to_json() + os.linesep + log_record.to_json() + os.linesep ) self.assertEqual(mock_stdout.write.call_count, 1) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 55526dc2b6a..363bc2b8aba 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -22,9 +22,9 @@ from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk import trace from opentelemetry.sdk._logs import ( - LogData, LoggerProvider, LoggingHandler, + LogRecord, LogRecordProcessor, ) from opentelemetry.semconv._incubating.attributes import code_attributes @@ -384,8 +384,8 @@ class FakeProcessor(LogRecordProcessor): def __init__(self): self.log_data_emitted = [] - def on_emit(self, log_data: LogData): - self.log_data_emitted.append(log_data) + def on_emit(self, log_record: LogRecord): + self.log_data_emitted.append(log_record) def shutdown(self): pass @@ -397,4 +397,4 @@ def emit_count(self): return len(self.log_data_emitted) def get_log_record(self, i): - return self.log_data_emitted[i].log_record + return self.log_data_emitted[i] diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index e121f136223..37b20dfff44 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -38,12 +38,10 @@ def __init__(self, exporter, logs_list): self._log_list = logs_list self._closed = False - def on_emit(self, log_data): + def on_emit(self, log_record: LogRecord): if self._closed: return - self._log_list.append( - (log_data.log_record.body, log_data.log_record.severity_text) - ) + self._log_list.append((log_record.body, log_record.severity_text)) def shutdown(self): self._closed = True diff --git a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py index 4888d81779d..fd390b754e5 100644 --- a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py +++ b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py @@ -25,7 +25,6 @@ import pytest from opentelemetry.sdk._logs import ( - LogData, LogRecord, ) from opentelemetry.sdk._logs.export import ( @@ -35,8 +34,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.util.instrumentation import InstrumentationScope -EMPTY_LOG = LogData( - log_record=LogRecord(), +EMPTY_LOG = LogRecord( instrumentation_scope=InstrumentationScope("example", "example"), ) From c3c3bac3c9bac72fbc4a2f3312627a880b96803e Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:14:56 +0000 Subject: [PATCH 02/28] Fix tests --- .../common/_internal/_log_encoder/__init__.py | 4 +- .../tests/test_log_encoder.py | 4 +- .../tests/logs/test_otlp_logs_exporter.py | 40 ++++++++----------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 2105913413f..769e36ece70 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -57,9 +57,7 @@ def _encode_log(log_record: SDKLogRecord) -> PB2LogRecord: flags=int(log_record.trace_flags), body=_encode_value(body, allow_null=True), severity_text=log_record.severity_text, - attributes=_encode_attributes( - log_record.attributes, allow_null=True - ), + attributes=_encode_attributes(log_record.attributes, allow_null=True), dropped_attributes_count=log_record.dropped_attributes, severity_number=log_record.severity_number.value, event_name=log_record.event_name, diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 2307bc70a60..9dc6224d842 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -65,7 +65,7 @@ def test_encode(self): def test_encode_no_body(self): sdk_logs, expected_encoding = self.get_test_logs() for log in sdk_logs: - log.log_record.body = None + log.body = None for resource_log in expected_encoding.resource_logs: for scope_log in resource_log.scope_logs: @@ -77,7 +77,7 @@ def test_encode_no_body(self): def test_dropped_attributes_count(self): sdk_logs = self._get_test_logs_dropped_attributes() encoded_logs = encode_logs(sdk_logs) - self.assertTrue(hasattr(sdk_logs[0].log_record, "dropped_attributes")) + self.assertTrue(hasattr(sdk_logs[0], "dropped_attributes")) self.assertEqual( # pylint:disable=no-member encoded_logs.resource_logs[0] diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index c0972d1aaa0..97e2546ca84 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -351,9 +351,9 @@ def test_translate_log_data(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_1.log_record.timestamp, - observed_time_unix_nano=self.log_data_1.log_record.observed_timestamp, - severity_number=self.log_data_1.log_record.severity_number.value, + time_unix_nano=self.log_data_1.timestamp, + observed_time_unix_nano=self.log_data_1.observed_timestamp, + severity_number=self.log_data_1.severity_number.value, severity_text="WARNING", span_id=int.to_bytes( 5213367945872657620, 8, "big" @@ -376,9 +376,7 @@ def test_translate_log_data(self): value=AnyValue(string_value="c"), ), ], - flags=int( - self.log_data_1.log_record.trace_flags - ), + flags=int(self.log_data_1.trace_flags), ) ], ) @@ -411,9 +409,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_1.log_record.timestamp, - observed_time_unix_nano=self.log_data_1.log_record.observed_timestamp, - severity_number=self.log_data_1.log_record.severity_number.value, + time_unix_nano=self.log_data_1.timestamp, + observed_time_unix_nano=self.log_data_1.observed_timestamp, + severity_number=self.log_data_1.severity_number.value, severity_text="WARNING", span_id=int.to_bytes( 5213367945872657620, 8, "big" @@ -436,9 +434,7 @@ def test_translate_multiple_logs(self): value=AnyValue(string_value="c"), ), ], - flags=int( - self.log_data_1.log_record.trace_flags - ), + flags=int(self.log_data_1.trace_flags), ) ], ), @@ -449,9 +445,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_2.log_record.timestamp, - observed_time_unix_nano=self.log_data_2.log_record.observed_timestamp, - severity_number=self.log_data_2.log_record.severity_number.value, + time_unix_nano=self.log_data_2.timestamp, + observed_time_unix_nano=self.log_data_2.observed_timestamp, + severity_number=self.log_data_2.severity_number.value, severity_text="INFO", span_id=int.to_bytes( 5213367945872657623, 8, "big" @@ -470,9 +466,7 @@ def test_translate_multiple_logs(self): value=_encode_value([1, 2, 3]), ), ], - flags=int( - self.log_data_2.log_record.trace_flags - ), + flags=int(self.log_data_2.trace_flags), ) ], ), @@ -495,9 +489,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_3.log_record.timestamp, - observed_time_unix_nano=self.log_data_3.log_record.observed_timestamp, - severity_number=self.log_data_3.log_record.severity_number.value, + time_unix_nano=self.log_data_3.timestamp, + observed_time_unix_nano=self.log_data_3.observed_timestamp, + severity_number=self.log_data_3.severity_number.value, severity_text="ERROR", span_id=int.to_bytes( 5213367945872657628, 8, "big" @@ -511,9 +505,7 @@ def test_translate_multiple_logs(self): "Mumbai, Boil water before drinking" ), attributes=[], - flags=int( - self.log_data_3.log_record.trace_flags - ), + flags=int(self.log_data_3.trace_flags), ) ], ) From b7cda72a511df3b03001aeb70fa1e6fbd7cc4efd Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:31:23 +0000 Subject: [PATCH 03/28] Keep LogData to avoid errors --- .../sdk/_logs/_internal/__init__.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index eded2d90238..3aaa6888c9e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -341,8 +341,14 @@ def dropped_attributes(self) -> int: @deprecated( "Use LogRecord. Since logs are not stable yet this WILL be removed in future releases." ) -class LogData(LogRecord): - pass +class LogData: + def __init__( + self, + log_record: LogRecord, + instrumentation_scope: InstrumentationScope, + ): + self.log_record = log_record + self.instrumentation_scope = instrumentation_scope class LogRecordProcessor(abc.ABC): @@ -400,7 +406,23 @@ def add_log_record_processor( def on_emit(self, log_record: LogRecord) -> None: for lp in self._log_record_processors: - lp.on_emit(log_record) + # Handle backward compatibility with processors that might expect LogData + import inspect + + sig = inspect.signature(lp.on_emit) + param_name = ( + list(sig.parameters.keys())[0] if sig.parameters else None + ) + + # Check if the processor expects LogData (backward compatibility) + if param_name and "log_data" in param_name.lower(): + # Create LogData for backward compatibility + log_data = LogData( + log_record, log_record.instrumentation_scope + ) + lp.on_emit(log_data) + else: + lp.on_emit(log_record) def shutdown(self) -> None: """Shutdown the log processors one by one""" @@ -473,7 +495,26 @@ def _submit_and_wait( future.result() def on_emit(self, log_record: LogRecord): - self._submit_and_wait(lambda lp: lp.on_emit, log_record) + # Handle backward compatibility with processors that might expect LogData + def _emit_with_compat(lp): + import inspect + + sig = inspect.signature(lp.on_emit) + param_name = ( + list(sig.parameters.keys())[0] if sig.parameters else None + ) + + # Check if the processor expects LogData (backward compatibility) + if param_name and "log_data" in param_name.lower(): + # Create LogData for backward compatibility + log_data = LogData( + log_record, log_record.instrumentation_scope + ) + return lp.on_emit(log_data) + else: + return lp.on_emit(log_record) + + self._submit_and_wait(lambda lp: lambda: _emit_with_compat(lp)) def shutdown(self): self._submit_and_wait(lambda lp: lp.shutdown) From 489fb85092c9ecda60c49a9046047539700463b1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:58:43 +0000 Subject: [PATCH 04/28] Removing LogData --- CHANGELOG.md | 2 +- .../src/opentelemetry/sdk/_logs/__init__.py | 2 - .../sdk/_logs/_internal/__init__.py | 52 +------------------ 3 files changed, 3 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35951569d21..88b01571ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set expected User-Agent in HTTP headers for grpc OTLP exporter ([#4658](https://github.com/open-telemetry/opentelemetry-python/pull/4658)) - Remove LogData and extend SDK LogRecord to have instrumentation scope - ([#](https://github.com/open-telemetry/opentelemetry-python/pull/)) + ([#4676](https://github.com/open-telemetry/opentelemetry-python/pull/4676)) ## Version 1.34.0/0.55b0 (2025-06-04) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index dbb108b7dba..d5474e0686e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -14,7 +14,6 @@ from opentelemetry.sdk._logs._internal import ( - LogData, LogDeprecatedInitWarning, LogDroppedAttributesWarning, Logger, @@ -26,7 +25,6 @@ ) __all__ = [ - "LogData", "Logger", "LoggerProvider", "LoggingHandler", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 3aaa6888c9e..a86f0793cd7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -338,19 +338,6 @@ def dropped_attributes(self) -> int: return 0 -@deprecated( - "Use LogRecord. Since logs are not stable yet this WILL be removed in future releases." -) -class LogData: - def __init__( - self, - log_record: LogRecord, - instrumentation_scope: InstrumentationScope, - ): - self.log_record = log_record - self.instrumentation_scope = instrumentation_scope - - class LogRecordProcessor(abc.ABC): """Interface to hook the log record emitting action. @@ -406,23 +393,7 @@ def add_log_record_processor( def on_emit(self, log_record: LogRecord) -> None: for lp in self._log_record_processors: - # Handle backward compatibility with processors that might expect LogData - import inspect - - sig = inspect.signature(lp.on_emit) - param_name = ( - list(sig.parameters.keys())[0] if sig.parameters else None - ) - - # Check if the processor expects LogData (backward compatibility) - if param_name and "log_data" in param_name.lower(): - # Create LogData for backward compatibility - log_data = LogData( - log_record, log_record.instrumentation_scope - ) - lp.on_emit(log_data) - else: - lp.on_emit(log_record) + lp.on_emit(log_record) def shutdown(self) -> None: """Shutdown the log processors one by one""" @@ -495,26 +466,7 @@ def _submit_and_wait( future.result() def on_emit(self, log_record: LogRecord): - # Handle backward compatibility with processors that might expect LogData - def _emit_with_compat(lp): - import inspect - - sig = inspect.signature(lp.on_emit) - param_name = ( - list(sig.parameters.keys())[0] if sig.parameters else None - ) - - # Check if the processor expects LogData (backward compatibility) - if param_name and "log_data" in param_name.lower(): - # Create LogData for backward compatibility - log_data = LogData( - log_record, log_record.instrumentation_scope - ) - return lp.on_emit(log_data) - else: - return lp.on_emit(log_record) - - self._submit_and_wait(lambda lp: lambda: _emit_with_compat(lp)) + self._submit_and_wait(lambda lp: lp.on_emit, log_record) def shutdown(self): self._submit_and_wait(lambda lp: lp.shutdown) From 59a4f7ed336fe133ae58cb6ee1a60dba23bcd0b2 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:09:28 +0000 Subject: [PATCH 05/28] Update --- .../common/_internal/_log_encoder/__init__.py | 2 +- .../tests/test_log_encoder.py | 3 +- .../otlp/proto/grpc/_log_exporter/__init__.py | 2 +- .../tests/logs/test_otlp_logs_exporter.py | 12 +-- .../otlp/proto/http/_log_exporter/__init__.py | 4 +- .../tests/test_proto_log_exporter.py | 2 +- .../src/opentelemetry/sdk/_events/__init__.py | 9 +- .../src/opentelemetry/sdk/_logs/__init__.py | 6 +- .../sdk/_logs/_internal/__init__.py | 89 ++++++------------- .../sdk/_logs/_internal/export/__init__.py | 17 ++-- .../export/in_memory_log_exporter.py | 6 +- opentelemetry-sdk/tests/events/test_events.py | 4 +- opentelemetry-sdk/tests/logs/test_export.py | 6 +- opentelemetry-sdk/tests/logs/test_handler.py | 6 +- .../tests/logs/test_log_record.py | 48 +++------- .../tests/logs/test_multi_log_processor.py | 6 +- .../shared_internal/test_batch_processor.py | 4 +- 17 files changed, 79 insertions(+), 147 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 769e36ece70..87768e63b35 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -30,7 +30,7 @@ ResourceLogs, ScopeLogs, ) -from opentelemetry.sdk._logs import LogRecord as SDKLogRecord +from opentelemetry.sdk._logs import SDKLogRecord def encode_logs(batch: Sequence[SDKLogRecord]) -> ExportLogsServiceRequest: diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 9dc6224d842..24766f09dd0 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -45,8 +45,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as PB2Resource, ) -from opentelemetry.sdk._logs import LogLimits -from opentelemetry.sdk._logs import LogRecord as SDKLogRecord +from opentelemetry.sdk._logs import LogLimits, SDKLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import ( diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index d568d207027..1989a254fa0 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -28,7 +28,7 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2_grpc import ( LogsServiceStub, ) -from opentelemetry.sdk._logs import LogRecord as SDKLogRecord +from opentelemetry.sdk._logs import SDKLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index 97e2546ca84..896c17e0c03 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -39,7 +39,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as OTLPResource, ) -from opentelemetry.sdk._logs import LogRecord +from opentelemetry.sdk._logs import SDKLogRecord from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, @@ -74,7 +74,7 @@ def setUp(self): ) ) ) - self.log_data_1 = LogRecord( + self.log_data_1 = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_1, severity_text="WARNING", @@ -96,7 +96,7 @@ def setUp(self): ) ) ) - self.log_data_2 = LogRecord( + self.log_data_2 = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_2, severity_text="INFO", @@ -118,7 +118,7 @@ def setUp(self): ) ) ) - self.log_data_3 = LogRecord( + self.log_data_3 = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_3, severity_text="ERROR", @@ -134,7 +134,7 @@ def setUp(self): SpanContext(0, 5213367945872657629, False, TraceFlags(0x01)) ) ) - self.log_data_4 = LogRecord( + self.log_data_4 = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_4, severity_text="ERROR", @@ -155,7 +155,7 @@ def setUp(self): ) ) ) - self.log_data_5 = LogRecord( + self.log_data_5 = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_5, severity_text="ERROR", diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index 433bfdcf008..528cf8075ca 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -32,7 +32,7 @@ from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, ) -from opentelemetry.sdk._logs import LogRecord +from opentelemetry.sdk._logs import SDKLogRecord from opentelemetry.sdk._logs.export import ( LogExporter, LogExportResult, @@ -156,7 +156,7 @@ def _export(self, serialized_data: bytes, timeout_sec: float): ) return resp - def export(self, batch: Sequence[LogRecord]) -> LogExportResult: + def export(self, batch: Sequence[SDKLogRecord]) -> LogExportResult: if self._shutdown: _logger.warning("Exporter already shutdown, ignoring batch") return LogExportResult.FAILURE diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index c4bfe271de2..ab072fdee5a 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -38,7 +38,7 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, ) -from opentelemetry.sdk._logs import LogRecord as SDKLogRecord +from opentelemetry.sdk._logs import SDKLogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index c427a48e2f8..aadb387ab99 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -15,12 +15,11 @@ from time import time_ns from typing import Optional -from opentelemetry import trace from opentelemetry._events import Event from opentelemetry._events import EventLogger as APIEventLogger from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider -from opentelemetry.sdk._logs import Logger, LoggerProvider, LogRecord +from opentelemetry.sdk._logs import Logger, LoggerProvider, SDKLogRecord from opentelemetry.util.types import _ExtendedAttributes _logger = logging.getLogger(__name__) @@ -49,13 +48,9 @@ def emit(self, event: Event) -> None: if isinstance(self._logger, NoOpLogger): # Do nothing if SDK is disabled return - span_context = trace.get_current_span().get_span_context() - log_record = LogRecord( + log_record = SDKLogRecord( timestamp=event.timestamp or time_ns(), observed_timestamp=None, - trace_id=event.trace_id or span_context.trace_id, - span_id=event.span_id or span_context.span_id, - trace_flags=event.trace_flags or span_context.trace_flags, severity_text=None, severity_number=event.severity_number or SeverityNumber.INFO, body=event.body, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index d5474e0686e..ffcb02b1061 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -14,14 +14,13 @@ from opentelemetry.sdk._logs._internal import ( - LogDeprecatedInitWarning, LogDroppedAttributesWarning, Logger, LoggerProvider, LoggingHandler, LogLimits, - LogRecord, LogRecordProcessor, + SDKLogRecord, ) __all__ = [ @@ -29,8 +28,7 @@ "LoggerProvider", "LoggingHandler", "LogLimits", - "LogRecord", "LogRecordProcessor", - "LogDeprecatedInitWarning", "LogDroppedAttributesWarning", + "SDKLogRecord", ] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 296cbcbeca8..0e9a4a8e411 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -27,12 +27,10 @@ from time import time_ns from typing import Any, Callable, Tuple, Union, cast, overload # noqa -from typing_extensions import deprecated - from opentelemetry._logs import Logger as APILogger from opentelemetry._logs import LoggerProvider as APILoggerProvider -from opentelemetry._logs import LogRecord as APILogRecord from opentelemetry._logs import ( + LogRecord, NoOpLogger, SeverityNumber, get_logger, @@ -56,7 +54,6 @@ format_trace_id, get_current_span, ) -from opentelemetry.trace.span import TraceFlags from opentelemetry.util.types import AnyValue, _ExtendedAttributes _logger = logging.getLogger(__name__) @@ -84,18 +81,6 @@ class LogDroppedAttributesWarning(UserWarning): warnings.simplefilter("once", LogDroppedAttributesWarning) -class LogDeprecatedInitWarning(UserWarning): - """Custom warning to indicate deprecated LogRecord init was used. - - This class is used to filter and handle these specific warnings separately - from other warnings, ensuring that they are only shown once without - interfering with default user warnings. - """ - - -warnings.simplefilter("once", LogDeprecatedInitWarning) - - class LogLimits: """This class is based on a SpanLimits class in the Tracing module. @@ -187,10 +172,10 @@ def _from_env_if_absent( ) -class LogRecord(APILogRecord): - """A LogRecord instance represents an event being logged. +class SDKLogRecord(LogRecord): + """A SDKLogRecord instance represents an event being logged. - LogRecord instances are created and emitted via `Logger` + SDKLogRecord instances are created and emitted via `Logger` every time something is logged. They contain all the information pertinent to the event being logged. """ @@ -210,33 +195,11 @@ def __init__( event_name: str | None = None, ): ... - @overload - @deprecated( - "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead." # noqa: E501 - ) - def __init__( - self, - timestamp: int | None = None, - observed_timestamp: int | None = None, - trace_id: int | None = None, - span_id: int | None = None, - trace_flags: TraceFlags | None = None, - severity_text: str | None = None, - severity_number: SeverityNumber | None = None, - body: AnyValue | None = None, - resource: Resource | None = None, - attributes: _ExtendedAttributes | None = None, - limits: LogLimits | None = _UnsetLogLimits, - ): ... - def __init__( # pylint:disable=too-many-locals self, timestamp: int | None = None, observed_timestamp: int | None = None, context: Context | None = None, - trace_id: int | None = None, - span_id: int | None = None, - trace_flags: TraceFlags | None = None, severity_text: str | None = None, severity_number: SeverityNumber | None = None, body: AnyValue | None = None, @@ -246,13 +209,6 @@ def __init__( # pylint:disable=too-many-locals event_name: str | None = None, instrumentation_scope: InstrumentationScope | None = None, ): - if trace_id or span_id or trace_flags: - warnings.warn( - "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead.", - LogDeprecatedInitWarning, - stacklevel=2, - ) - if not context: context = get_current() @@ -264,9 +220,9 @@ def __init__( # pylint:disable=too-many-locals "timestamp": timestamp, "observed_timestamp": observed_timestamp, "context": context, - "trace_id": trace_id or span_context.trace_id, - "span_id": span_id or span_context.span_id, - "trace_flags": trace_flags or span_context.trace_flags, + "trace_id": span_context.trace_id, + "span_id": span_context.span_id, + "trace_flags": span_context.trace_flags, "severity_text": severity_text, "severity_number": severity_number, "body": body, @@ -292,7 +248,7 @@ def __init__( # pylint:disable=too-many-locals ) def __eq__(self, other: object) -> bool: - if not isinstance(other, LogRecord): + if not isinstance(other, SDKLogRecord): return NotImplemented return self.__dict__ == other.__dict__ @@ -347,8 +303,8 @@ class LogRecordProcessor(abc.ABC): """ @abc.abstractmethod - def on_emit(self, log_record: LogRecord): - """Emits the `LogRecord`""" + def on_emit(self, log_record: SDKLogRecord): + """Emits the `SDKLogRecord`""" @abc.abstractmethod def shutdown(self): @@ -391,7 +347,7 @@ def add_log_record_processor( with self._lock: self._log_record_processors += (log_record_processor,) - def on_emit(self, log_record: LogRecord) -> None: + def on_emit(self, log_record: SDKLogRecord) -> None: for lp in self._log_record_processors: lp.on_emit(log_record) @@ -465,7 +421,7 @@ def _submit_and_wait( for future in futures: future.result() - def on_emit(self, log_record: LogRecord): + def on_emit(self, log_record: SDKLogRecord): self._submit_and_wait(lambda lp: lp.on_emit, log_record) def shutdown(self): @@ -575,7 +531,7 @@ def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes: ) return attributes - def _translate(self, record: logging.LogRecord) -> LogRecord: + def _translate(self, record: logging.LogRecord) -> SDKLogRecord: timestamp = int(record.created * 1e9) observered_timestamp = time_ns() attributes = self._get_attributes(record) @@ -610,7 +566,7 @@ def _translate(self, record: logging.LogRecord) -> LogRecord: ) logger = get_logger(record.name, logger_provider=self._logger_provider) - return LogRecord( + return SDKLogRecord( timestamp=timestamp, observed_timestamp=observered_timestamp, context=get_current() or None, @@ -669,11 +625,22 @@ def resource(self): return self._resource def emit(self, record: LogRecord): - """Emits the :class:`LogRecord` by setting instrumentation scope + """Emits the :class:`SDKLogRecord` by setting instrumentation scope and forwarding to the processor. """ - record.instrumentation_scope = self._instrumentation_scope - self._multi_log_record_processor.on_emit(record) + sdk_log_record = SDKLogRecord( + timestamp=record.timestamp, + observed_timestamp=record.observed_timestamp, + context=record.context, + severity_text=record.severity_text, + severity_number=record.severity_number, + body=record.body, + attributes=record.attributes, + event_name=record.event_name, + resource=self._resource, + instrumentation_scope=self._instrumentation_scope, + ) + self._multi_log_record_processor.on_emit(sdk_log_record) class LoggerProvider(APILoggerProvider): 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 79343af7362..4946d9b9162 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -26,7 +26,7 @@ detach, set_value, ) -from opentelemetry.sdk._logs import LogRecord, LogRecordProcessor +from opentelemetry.sdk._logs import LogRecordProcessor, SDKLogRecord from opentelemetry.sdk._shared_internal import BatchProcessor from opentelemetry.sdk.environment_variables import ( OTEL_BLRP_EXPORT_TIMEOUT, @@ -59,10 +59,10 @@ class LogExporter(abc.ABC): """ @abc.abstractmethod - def export(self, batch: Sequence[LogRecord]): + def export(self, batch: Sequence[SDKLogRecord]): """Exports a batch of logs. Args: - batch: The list of `LogRecord` objects to be exported + batch: The list of `SDKLogRecord` objects to be exported Returns: The result of the export """ @@ -86,13 +86,14 @@ class ConsoleLogExporter(LogExporter): def __init__( self, out: IO = sys.stdout, - formatter: Callable[[LogRecord], str] = lambda record: record.to_json() - + linesep, + formatter: Callable[ + [SDKLogRecord], str + ] = lambda record: record.to_json() + linesep, ): self.out = out self.formatter = formatter - def export(self, batch: Sequence[LogRecord]): + def export(self, batch: Sequence[SDKLogRecord]): for log_record in batch: self.out.write(self.formatter(log_record)) self.out.flush() @@ -111,7 +112,7 @@ def __init__(self, exporter: LogExporter): self._exporter = exporter self._shutdown = False - def on_emit(self, log_record: LogRecord): + def on_emit(self, log_record: SDKLogRecord): if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") return @@ -184,7 +185,7 @@ def __init__( "Log", ) - def on_emit(self, log_record: LogRecord) -> None: + def on_emit(self, log_record: SDKLogRecord) -> None: return self._batch_processor.emit(log_record) def shutdown(self): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py index b4e0239c246..c947995d045 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py @@ -15,7 +15,7 @@ import threading import typing -from opentelemetry.sdk._logs import LogRecord +from opentelemetry.sdk._logs import SDKLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult @@ -36,11 +36,11 @@ def clear(self) -> None: with self._lock: self._logs.clear() - def get_finished_logs(self) -> typing.Tuple[LogRecord, ...]: + def get_finished_logs(self) -> typing.Tuple[SDKLogRecord, ...]: with self._lock: return tuple(self._logs) - def export(self, batch: typing.Sequence[LogRecord]) -> LogExportResult: + def export(self, batch: typing.Sequence[SDKLogRecord]) -> LogExportResult: if self._stopped: return LogExportResult.FAILURE with self._lock: diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index 7b8d42ff316..77915e7cd66 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -107,7 +107,7 @@ def test_event_logger(self, logger_mock): "name", "version", "schema_url", {"key": "value"} ) - @patch("opentelemetry.sdk._events.LogRecord") + @patch("opentelemetry.sdk._events.SDKLogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit(self, logger_mock, log_record_mock): logger_provider = LoggerProvider() @@ -161,7 +161,7 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): ) logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst) - @patch("opentelemetry.sdk._events.LogRecord") + @patch("opentelemetry.sdk._events.SDKLogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit_sdk_disabled( self, logger_mock, log_record_mock diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 7db79683730..983c2ca3c51 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -28,7 +28,7 @@ from opentelemetry.sdk._logs import ( LoggerProvider, LoggingHandler, - LogRecord, + SDKLogRecord, ) from opentelemetry.sdk._logs._internal.export import _logger from opentelemetry.sdk._logs.export import ( @@ -53,7 +53,7 @@ ) from opentelemetry.trace.span import INVALID_SPAN_CONTEXT -EMPTY_LOG = LogRecord( +EMPTY_LOG = SDKLogRecord( instrumentation_scope=InstrumentationScope("example", "example"), ) @@ -600,7 +600,7 @@ def test_export(self): # pylint: disable=no-self-use ) ) ) - log_record = LogRecord( + log_record = SDKLogRecord( timestamp=int(time.time() * 1e9), context=ctx, severity_text="WARN", diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 363bc2b8aba..a98839d17e0 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -24,8 +24,8 @@ from opentelemetry.sdk._logs import ( LoggerProvider, LoggingHandler, - LogRecord, LogRecordProcessor, + SDKLogRecord, ) from opentelemetry.semconv._incubating.attributes import code_attributes from opentelemetry.semconv.attributes import exception_attributes @@ -118,7 +118,7 @@ def test_log_record_observed_timestamp(self): self.assertIsNotNone(log_record.observed_timestamp) def test_log_record_user_attributes(self): - """Attributes can be injected into logs by adding them to the LogRecord""" + """Attributes can be injected into logs by adding them to the SDKLogRecord""" processor, logger = set_up_test_logging(logging.WARNING) # Assert emit gets called for warning message @@ -384,7 +384,7 @@ class FakeProcessor(LogRecordProcessor): def __init__(self): self.log_data_emitted = [] - def on_emit(self, log_record: LogRecord): + def on_emit(self, log_record: SDKLogRecord): self.log_data_emitted.append(log_record) def shutdown(self): diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index dc9c0aab103..aa00fca2322 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -18,20 +18,17 @@ from opentelemetry._logs.severity import SeverityNumber from opentelemetry.attributes import BoundedAttributes -from opentelemetry.context import get_current from opentelemetry.sdk._logs import ( - LogDeprecatedInitWarning, LogDroppedAttributesWarning, LogLimits, - LogRecord, + SDKLogRecord, ) from opentelemetry.sdk.resources import Resource -from opentelemetry.trace.span import TraceFlags class TestLogRecord(unittest.TestCase): def test_log_record_to_json(self): - log_record = LogRecord( + log_record = SDKLogRecord( timestamp=0, observed_timestamp=0, body={"key": "logLine", "bytes": b"123"}, @@ -51,7 +48,7 @@ def test_log_record_to_json(self): ) def test_log_record_to_json_serializes_severity_number_as_int(self): - actual = LogRecord( + actual = SDKLogRecord( timestamp=0, severity_number=SeverityNumber.WARN, observed_timestamp=0, @@ -65,14 +62,14 @@ def test_log_record_to_json_serializes_severity_number_as_int(self): def test_log_record_bounded_attributes(self): attr = {"key": "value"} - result = LogRecord(timestamp=0, body="a log line", attributes=attr) + result = SDKLogRecord(timestamp=0, body="a log line", attributes=attr) self.assertTrue(isinstance(result.attributes, BoundedAttributes)) def test_log_record_dropped_attributes_empty_limits(self): attr = {"key": "value"} - result = LogRecord(timestamp=0, body="a log line", attributes=attr) + result = SDKLogRecord(timestamp=0, body="a log line", attributes=attr) self.assertTrue(result.dropped_attributes == 0) @@ -82,7 +79,7 @@ def test_log_record_dropped_attributes_set_limits_max_attribute(self): max_attributes=1, ) - result = LogRecord( + result = SDKLogRecord( timestamp=0, body="a log line", attributes=attr, limits=limits ) self.assertTrue(result.dropped_attributes == 1) @@ -96,7 +93,7 @@ def test_log_record_dropped_attributes_set_limits_max_attribute_length( max_attribute_length=1, ) - result = LogRecord( + result = SDKLogRecord( timestamp=0, body="a log line", attributes=attr, limits=limits ) self.assertTrue(result.dropped_attributes == 0) @@ -110,7 +107,7 @@ def test_log_record_dropped_attributes_set_limits(self): max_attribute_length=1, ) - result = LogRecord( + result = SDKLogRecord( timestamp=0, body="a log line", attributes=attr, limits=limits ) self.assertTrue(result.dropped_attributes == 1) @@ -125,7 +122,7 @@ def test_log_record_dropped_attributes_set_limits_warning_once(self): with warnings.catch_warnings(record=True) as cw: for _ in range(10): - LogRecord( + SDKLogRecord( timestamp=0, body="a log line", attributes=attr, @@ -142,33 +139,8 @@ def test_log_record_dropped_attributes_unset_limits(self): attr = {"key": "value", "key2": "value2"} limits = LogLimits() - result = LogRecord( + result = SDKLogRecord( timestamp=0, body="a log line", attributes=attr, limits=limits ) self.assertTrue(result.dropped_attributes == 0) self.assertEqual(attr, result.attributes) - - def test_log_record_deprecated_init_warning(self): - test_cases = [ - {"trace_id": 123}, - {"span_id": 123}, - {"trace_flags": TraceFlags(0x01)}, - ] - - for params in test_cases: - with self.subTest(params=params): - with warnings.catch_warnings(record=True) as cw: - for _ in range(10): - LogRecord(**params) - - self.assertEqual(len(cw), 1) - self.assertIsInstance(cw[-1].message, LogDeprecatedInitWarning) - self.assertIn( - "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead.", - str(cw[-1].message), - ) - - with warnings.catch_warnings(record=True) as cw: - for _ in range(10): - LogRecord(context=get_current()) - self.assertEqual(len(cw), 0) diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index 37b20dfff44..146b5fddda5 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -26,8 +26,8 @@ ConcurrentMultiLogRecordProcessor, LoggerProvider, LoggingHandler, - LogRecord, LogRecordProcessor, + SDKLogRecord, SynchronousMultiLogRecordProcessor, ) @@ -38,7 +38,7 @@ def __init__(self, exporter, logs_list): self._log_list = logs_list self._closed = False - def on_emit(self, log_record: LogRecord): + def on_emit(self, log_record: SDKLogRecord): if self._closed: return self._log_list.append((log_record.body, log_record.severity_text)) @@ -103,7 +103,7 @@ def _get_multi_log_record_processor(self): pass def make_record(self): - return LogRecord( + return SDKLogRecord( timestamp=1622300111608942000, severity_text="WARN", severity_number=SeverityNumber.WARN, diff --git a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py index fd390b754e5..b2fe09ca331 100644 --- a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py +++ b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py @@ -25,7 +25,7 @@ import pytest from opentelemetry.sdk._logs import ( - LogRecord, + SDKLogRecord, ) from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, @@ -34,7 +34,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.util.instrumentation import InstrumentationScope -EMPTY_LOG = LogRecord( +EMPTY_LOG = SDKLogRecord( instrumentation_scope=InstrumentationScope("example", "example"), ) From 7591d05809510bc93e1793d59272d0eccf254e5a Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:17:29 +0000 Subject: [PATCH 06/28] Update test --- opentelemetry-sdk/tests/events/test_events.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index 77915e7cd66..f93695acd8f 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -146,9 +146,6 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): log_record_mock.assert_called_once_with( timestamp=now, observed_timestamp=None, - trace_id=trace_id, - span_id=span_id, - trace_flags=trace_flags, severity_text=None, severity_number=SeverityNumber.ERROR, body="test body", From 9ed04bb265ef3804285ee5236ab1d02dd81b72d8 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:52:46 +0000 Subject: [PATCH 07/28] Update --- .../common/_internal/_log_encoder/__init__.py | 30 +-- .../tests/logs/test_otlp_logs_exporter.py | 68 ++++--- .../tests/test_proto_log_exporter.py | 86 ++++---- .../src/opentelemetry/sdk/_events/__init__.py | 21 +- .../sdk/_logs/_internal/__init__.py | 119 ++++------- opentelemetry-sdk/tests/logs/test_export.py | 101 ++++++---- opentelemetry-sdk/tests/logs/test_handler.py | 186 +++++++++++------- .../tests/logs/test_log_record.py | 84 +++++--- .../tests/logs/test_multi_log_processor.py | 16 +- 9 files changed, 410 insertions(+), 301 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 87768e63b35..bd84e67f1a3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -37,30 +37,32 @@ def encode_logs(batch: Sequence[SDKLogRecord]) -> ExportLogsServiceRequest: return ExportLogsServiceRequest(resource_logs=_encode_resource_logs(batch)) -def _encode_log(log_record: SDKLogRecord) -> PB2LogRecord: +def _encode_log(sdk_log_record: SDKLogRecord) -> PB2LogRecord: span_id = ( None - if log_record.span_id == 0 - else _encode_span_id(log_record.span_id) + if sdk_log_record.log_record.span_id == 0 + else _encode_span_id(sdk_log_record.log_record.span_id) ) trace_id = ( None - if log_record.trace_id == 0 - else _encode_trace_id(log_record.trace_id) + if sdk_log_record.log_record.trace_id == 0 + else _encode_trace_id(sdk_log_record.log_record.trace_id) ) - body = log_record.body + body = sdk_log_record.log_record.body return PB2LogRecord( - time_unix_nano=log_record.timestamp, - observed_time_unix_nano=log_record.observed_timestamp, + time_unix_nano=sdk_log_record.log_record.timestamp, + observed_time_unix_nano=sdk_log_record.log_record.observed_timestamp, span_id=span_id, trace_id=trace_id, - flags=int(log_record.trace_flags), + flags=int(sdk_log_record.log_record.trace_flags), body=_encode_value(body, allow_null=True), - severity_text=log_record.severity_text, - attributes=_encode_attributes(log_record.attributes, allow_null=True), - dropped_attributes_count=log_record.dropped_attributes, - severity_number=log_record.severity_number.value, - event_name=log_record.event_name, + severity_text=sdk_log_record.log_record.severity_text, + attributes=_encode_attributes( + sdk_log_record.log_record.attributes, allow_null=True + ), + dropped_attributes_count=sdk_log_record.dropped_attributes, + severity_number=sdk_log_record.log_record.severity_number.value, + event_name=sdk_log_record.log_record.event_name, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index 896c17e0c03..d395207cfb4 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -22,7 +22,7 @@ from google.protobuf.json_format import MessageToDict from grpc import ChannelCredentials, Compression -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.exporter.otlp.proto.common._internal import _encode_value from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( OTLPLogExporter, @@ -75,13 +75,15 @@ def setUp(self): ) ) self.log_data_1 = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_1, - severity_text="WARNING", - severity_number=SeverityNumber.WARN, - body="Zhengzhou, We have a heaviest rains in 1000 years", + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_1, + severity_text="WARNING", + severity_number=SeverityNumber.WARN, + body="Zhengzhou, We have a heaviest rains in 1000 years", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"key": "value"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -97,13 +99,15 @@ def setUp(self): ) ) self.log_data_2 = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_2, - severity_text="INFO", - severity_number=SeverityNumber.INFO2, - body="Sydney, Opera House is closed", - resource=SDKResource({"key": "value"}), - attributes={"custom_attr": [1, 2, 3]}, + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_2, + severity_text="INFO", + severity_number=SeverityNumber.INFO2, + body="Sydney, Opera House is closed", + resource=SDKResource({"key": "value"}), + attributes={"custom_attr": [1, 2, 3]}, + ), instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -119,11 +123,13 @@ def setUp(self): ) ) self.log_data_3 = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_3, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Mumbai, Boil water before drinking", + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_3, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Mumbai, Boil water before drinking", + ), resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "third_name", "third_version" @@ -135,11 +141,13 @@ def setUp(self): ) ) self.log_data_4 = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_4, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Invalid trace id check", + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_4, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Invalid trace id check", + ), resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "fourth_name", "fourth_version" @@ -156,11 +164,13 @@ def setUp(self): ) ) self.log_data_5 = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx_log_data_5, - severity_text="ERROR", - severity_number=SeverityNumber.WARN, - body="Invalid span id check", + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx_log_data_5, + severity_text="ERROR", + severity_number=SeverityNumber.WARN, + body="Invalid span id check", + ), resource=SDKResource({"service": "myapp"}), instrumentation_scope=InstrumentationScope( "fifth_name", "fifth_version" diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index ab072fdee5a..1ea5e6f0ea3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -25,7 +25,7 @@ from requests import Session from requests.models import Response -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.exporter.otlp.proto.http import Compression from opentelemetry.exporter.otlp.proto.http._log_exporter import ( DEFAULT_COMPRESSION, @@ -232,13 +232,15 @@ def test_exported_log_without_trace_id(self): ) ) log = SDKLogRecord( - timestamp=1644650195189786182, - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Invalid trace id check", + LogRecord( + timestamp=1644650195189786182, + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Invalid trace id check", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope("name", "version"), ) log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log) @@ -266,13 +268,15 @@ def test_exported_log_without_span_id(self): ) log = SDKLogRecord( - timestamp=1644650195189786360, - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Invalid span id check", + LogRecord( + timestamp=1644650195189786360, + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Invalid span id check", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope("name", "version"), ) log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log) @@ -300,13 +304,15 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log1 = SDKLogRecord( - timestamp=1644650195189786880, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", + LogRecord( + timestamp=1644650195189786880, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), @@ -322,13 +328,15 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log2 = SDKLogRecord( - timestamp=1644650249738562048, - context=ctx_log2, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", + LogRecord( + timestamp=1644650249738562048, + context=ctx_log2, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + attributes={}, + ), resource=SDKResource({"second_resource": "CASE"}), - attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -344,13 +352,15 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log3 = SDKLogRecord( - timestamp=1644650427658989056, - context=ctx_log3, - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG, - body="To our galaxy", + LogRecord( + timestamp=1644650427658989056, + context=ctx_log3, + severity_text="DEBUG", + severity_number=SeverityNumber.DEBUG, + body="To our galaxy", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"second_resource": "CASE"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=None, ) ctx_log4 = set_span_in_context( @@ -364,13 +374,15 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log4 = SDKLogRecord( - timestamp=1644650584292683008, - context=ctx_log4, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Love is the one thing that transcends time and space", + LogRecord( + timestamp=1644650584292683008, + context=ctx_log4, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Love is the one thing that transcends time and space", + attributes={"filename": "model.py", "func_name": "run_method"}, + ), resource=SDKResource({"first_resource": "value"}), - attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "another_name", "another_version" ), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index aadb387ab99..8a09497cac0 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -18,7 +18,12 @@ from opentelemetry._events import Event from opentelemetry._events import EventLogger as APIEventLogger from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider -from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider +from opentelemetry._logs import ( + LogRecord, + NoOpLogger, + SeverityNumber, + get_logger_provider, +) from opentelemetry.sdk._logs import Logger, LoggerProvider, SDKLogRecord from opentelemetry.util.types import _ExtendedAttributes @@ -49,13 +54,15 @@ def emit(self, event: Event) -> None: # Do nothing if SDK is disabled return log_record = SDKLogRecord( - timestamp=event.timestamp or time_ns(), - observed_timestamp=None, - severity_text=None, - severity_number=event.severity_number or SeverityNumber.INFO, - body=event.body, + LogRecord( + timestamp=event.timestamp or time_ns(), + observed_timestamp=None, + severity_text=None, + severity_number=event.severity_number or SeverityNumber.INFO, + body=event.body, + attributes=event.attributes, + ), resource=getattr(self._logger, "resource", None), - attributes=event.attributes, ) self._logger.emit(log_record) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 0e9a4a8e411..05875123f32 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -38,7 +38,6 @@ ) from opentelemetry.attributes import _VALID_ANY_VALUE_TYPES, BoundedAttributes from opentelemetry.context import get_current -from opentelemetry.context.context import Context from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -52,11 +51,8 @@ from opentelemetry.trace import ( format_span_id, format_trace_id, - get_current_span, ) -from opentelemetry.util.types import AnyValue, _ExtendedAttributes - -_logger = logging.getLogger(__name__) +from opentelemetry.util.types import _ExtendedAttributes _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128 _ENV_VALUE_UNSET = "" @@ -172,7 +168,7 @@ def _from_env_if_absent( ) -class SDKLogRecord(LogRecord): +class SDKLogRecord: """A SDKLogRecord instance represents an event being logged. SDKLogRecord instances are created and emitted via `Logger` @@ -183,59 +179,30 @@ class SDKLogRecord(LogRecord): @overload def __init__( self, - timestamp: int | None = None, - observed_timestamp: int | None = None, - context: Context | None = None, - severity_text: str | None = None, - severity_number: SeverityNumber | None = None, - body: AnyValue | None = None, + log_record: LogRecord, resource: Resource | None = None, - attributes: _ExtendedAttributes | None = None, limits: LogLimits | None = _UnsetLogLimits, - event_name: str | None = None, ): ... def __init__( # pylint:disable=too-many-locals self, - timestamp: int | None = None, - observed_timestamp: int | None = None, - context: Context | None = None, - severity_text: str | None = None, - severity_number: SeverityNumber | None = None, - body: AnyValue | None = None, + log_record: LogRecord | None = None, resource: Resource | None = None, - attributes: _ExtendedAttributes | None = None, limits: LogLimits | None = _UnsetLogLimits, - event_name: str | None = None, instrumentation_scope: InstrumentationScope | None = None, ): - if not context: - context = get_current() - - span = get_current_span(context) - span_context = span.get_span_context() + self.log_record = log_record + if self.log_record is not None: + self.log_record.attributes = BoundedAttributes( + maxlen=limits.max_attributes, + attributes=self.log_record.attributes + if bool(self.log_record.attributes) + else None, + immutable=False, + max_value_len=limits.max_attribute_length, + extended_attributes=True, + ) - super().__init__( - **{ - "timestamp": timestamp, - "observed_timestamp": observed_timestamp, - "context": context, - "trace_id": span_context.trace_id, - "span_id": span_context.span_id, - "trace_flags": span_context.trace_flags, - "severity_text": severity_text, - "severity_number": severity_number, - "body": body, - "attributes": BoundedAttributes( - maxlen=limits.max_attributes, - attributes=attributes if bool(attributes) else None, - immutable=False, - max_value_len=limits.max_attribute_length, - extended_attributes=True, - ), - "event_name": event_name, - } - ) self.resource = ( resource if isinstance(resource, Resource) else Resource.create({}) ) @@ -255,30 +222,36 @@ def __eq__(self, other: object) -> bool: def to_json(self, indent: int | None = 4) -> str: return json.dumps( { - "body": self.body, - "severity_number": self.severity_number.value - if self.severity_number is not None + "body": self.log_record.body, + "severity_number": self.log_record.severity_number.value + if self.log_record.severity_number is not None else None, - "severity_text": self.severity_text, + "severity_text": self.log_record.severity_text, "attributes": ( - dict(self.attributes) if bool(self.attributes) else None + dict(self.log_record.attributes) + if bool(self.log_record.attributes) + else None ), "dropped_attributes": self.dropped_attributes, - "timestamp": ns_to_iso_str(self.timestamp), - "observed_timestamp": ns_to_iso_str(self.observed_timestamp), + "timestamp": ns_to_iso_str(self.log_record.timestamp), + "observed_timestamp": ns_to_iso_str( + self.log_record.observed_timestamp + ), "trace_id": ( - f"0x{format_trace_id(self.trace_id)}" - if self.trace_id is not None + f"0x{format_trace_id(self.log_record.trace_id)}" + if self.log_record.trace_id is not None else "" ), "span_id": ( - f"0x{format_span_id(self.span_id)}" - if self.span_id is not None + f"0x{format_span_id(self.log_record.span_id)}" + if self.log_record.span_id is not None else "" ), - "trace_flags": self.trace_flags, + "trace_flags": self.log_record.trace_flags, "resource": json.loads(self.resource.to_json()), - "event_name": self.event_name if self.event_name else "", + "event_name": self.log_record.event_name + if self.log_record.event_name + else "", }, indent=indent, cls=BytesEncoder, @@ -286,11 +259,10 @@ def to_json(self, indent: int | None = 4) -> str: @property def dropped_attributes(self) -> int: - attributes: BoundedAttributes = cast( - BoundedAttributes, self.attributes - ) - if attributes: - return attributes.dropped + if self.log_record and isinstance( + self.log_record.attributes, BoundedAttributes + ): + return self.log_record.attributes.dropped return 0 @@ -531,7 +503,7 @@ def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes: ) return attributes - def _translate(self, record: logging.LogRecord) -> SDKLogRecord: + def _translate(self, record: logging.LogRecord) -> LogRecord: timestamp = int(record.created * 1e9) observered_timestamp = time_ns() attributes = self._get_attributes(record) @@ -565,15 +537,13 @@ def _translate(self, record: logging.LogRecord) -> SDKLogRecord: "WARN" if record.levelname == "WARNING" else record.levelname ) - logger = get_logger(record.name, logger_provider=self._logger_provider) - return SDKLogRecord( + return LogRecord( timestamp=timestamp, observed_timestamp=observered_timestamp, context=get_current() or None, severity_text=level_name, severity_number=severity_number, body=body, - resource=logger.resource, attributes=attributes, ) @@ -629,14 +599,7 @@ def emit(self, record: LogRecord): and forwarding to the processor. """ sdk_log_record = SDKLogRecord( - timestamp=record.timestamp, - observed_timestamp=record.observed_timestamp, - context=record.context, - severity_text=record.severity_text, - severity_number=record.severity_number, - body=record.body, - attributes=record.attributes, - event_name=record.event_name, + log_record=record, resource=self._resource, instrumentation_scope=self._instrumentation_scope, ) diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 983c2ca3c51..ae1897126f2 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -23,7 +23,7 @@ from pytest import mark -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.sdk import trace from opentelemetry.sdk._logs import ( LoggerProvider, @@ -75,10 +75,12 @@ def test_simple_log_record_processor_default_level(self): finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) warning_log_record = finished_logs[0] - self.assertEqual(warning_log_record.body, "Something is wrong") - self.assertEqual(warning_log_record.severity_text, "WARN") self.assertEqual( - warning_log_record.severity_number, SeverityNumber.WARN + warning_log_record.log_record.body, "Something is wrong" + ) + self.assertEqual(warning_log_record.log_record.severity_text, "WARN") + self.assertEqual( + warning_log_record.log_record.severity_number, SeverityNumber.WARN ) self.assertEqual( finished_logs[0].instrumentation_scope.name, "default_level" @@ -106,15 +108,16 @@ def test_simple_log_record_processor_custom_level(self): self.assertEqual(len(finished_logs), 2) critical_log_record = finished_logs[0] fatal_log_record = finished_logs[1] - self.assertEqual(critical_log_record.body, "Error message") - self.assertEqual(critical_log_record.severity_text, "ERROR") + self.assertEqual(critical_log_record.log_record.body, "Error message") + self.assertEqual(critical_log_record.log_record.severity_text, "ERROR") self.assertEqual( - critical_log_record.severity_number, SeverityNumber.ERROR + critical_log_record.log_record.severity_number, + SeverityNumber.ERROR, ) - self.assertEqual(fatal_log_record.body, "Critical message") - self.assertEqual(fatal_log_record.severity_text, "CRITICAL") + self.assertEqual(fatal_log_record.log_record.body, "Critical message") + self.assertEqual(fatal_log_record.log_record.severity_text, "CRITICAL") self.assertEqual( - fatal_log_record.severity_number, SeverityNumber.FATAL + fatal_log_record.log_record.severity_number, SeverityNumber.FATAL ) self.assertEqual( finished_logs[0].instrumentation_scope.name, "custom_level" @@ -138,14 +141,20 @@ def test_simple_log_record_processor_trace_correlation(self): logger.warning("Warning message") finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) - log_record = finished_logs[0] - self.assertEqual(log_record.body, "Warning message") - self.assertEqual(log_record.severity_text, "WARN") - self.assertEqual(log_record.severity_number, SeverityNumber.WARN) - self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id) - self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id) + sdk_record = finished_logs[0] + self.assertEqual(sdk_record.log_record.body, "Warning message") + self.assertEqual(sdk_record.log_record.severity_text, "WARN") + self.assertEqual( + sdk_record.log_record.severity_number, SeverityNumber.WARN + ) + self.assertEqual( + sdk_record.log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id + ) self.assertEqual( - log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags + sdk_record.log_record.span_id, INVALID_SPAN_CONTEXT.span_id + ) + self.assertEqual( + sdk_record.log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags ) self.assertEqual( finished_logs[0].instrumentation_scope.name, "trace_correlation" @@ -157,18 +166,28 @@ def test_simple_log_record_processor_trace_correlation(self): logger.critical("Critical message within span") finished_logs = exporter.get_finished_logs() - log_record = finished_logs[0] - self.assertEqual(log_record.body, "Critical message within span") - self.assertEqual(log_record.severity_text, "CRITICAL") - self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) + sdk_record = finished_logs[0] + self.assertEqual( + sdk_record.log_record.body, "Critical message within span" + ) + self.assertEqual(sdk_record.log_record.severity_text, "CRITICAL") + self.assertEqual( + sdk_record.log_record.severity_number, SeverityNumber.FATAL + ) self.assertEqual( finished_logs[0].instrumentation_scope.name, "trace_correlation", ) span_context = span.get_span_context() - self.assertEqual(log_record.trace_id, span_context.trace_id) - self.assertEqual(log_record.span_id, span_context.span_id) - self.assertEqual(log_record.trace_flags, span_context.trace_flags) + self.assertEqual( + sdk_record.log_record.trace_id, span_context.trace_id + ) + self.assertEqual( + sdk_record.log_record.span_id, span_context.span_id + ) + self.assertEqual( + sdk_record.log_record.trace_flags, span_context.trace_flags + ) def test_simple_log_record_processor_shutdown(self): exporter = InMemoryLogExporter() @@ -186,10 +205,12 @@ def test_simple_log_record_processor_shutdown(self): finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) warning_log_record = finished_logs[0] - self.assertEqual(warning_log_record.body, "Something is wrong") - self.assertEqual(warning_log_record.severity_text, "WARN") self.assertEqual( - warning_log_record.severity_number, SeverityNumber.WARN + warning_log_record.log_record.body, "Something is wrong" + ) + self.assertEqual(warning_log_record.log_record.severity_text, "WARN") + self.assertEqual( + warning_log_record.log_record.severity_number, SeverityNumber.WARN ) self.assertEqual( finished_logs[0].instrumentation_scope.name, "shutdown" @@ -228,7 +249,10 @@ def test_simple_log_record_processor_different_msg_types(self): (["list", "of", "strings"], "WARN"), ({"key": "value"}, "ERROR"), ] - emitted = [(item.body, item.severity_text) for item in finished_logs] + emitted = [ + (item.log_record.body, item.log_record.severity_text) + for item in finished_logs + ] self.assertEqual(expected, emitted) for item in finished_logs: self.assertEqual( @@ -278,7 +302,7 @@ def test_simple_log_record_processor_custom_single_obj(self): (["a non-string with a percent-s", "%s"]), ] for emitted, expected in zip(finished_logs, expected): - self.assertEqual(emitted.body, expected) + self.assertEqual(emitted.log_record.body, expected) self.assertEqual(emitted.instrumentation_scope.name, "single_obj") def test_simple_log_record_processor_different_msg_types_with_formatter( @@ -324,7 +348,10 @@ def test_simple_log_record_processor_different_msg_types_with_formatter( ), ("different_msg_types - ERROR - {'key': 'value'}", "ERROR"), ] - emitted = [(item.body, item.severity_text) for item in finished_logs] + emitted = [ + (item.log_record.body, item.log_record.severity_text) + for item in finished_logs + ] self.assertEqual(expected, emitted) @@ -601,13 +628,15 @@ def test_export(self): # pylint: disable=no-self-use ) ) log_record = SDKLogRecord( - timestamp=int(time.time() * 1e9), - context=ctx, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Zhengzhou, We have a heaviest rains in 1000 years", + LogRecord( + timestamp=int(time.time() * 1e9), + context=ctx, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Zhengzhou, We have a heaviest rains in 1000 years", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"key": "value"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index a98839d17e0..22e68dbe27a 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -99,13 +99,18 @@ def test_log_record_no_span_context(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id) - self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id) + self.assertIsNotNone(sdk_log_record) self.assertEqual( - log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags + sdk_log_record.log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id + ) + self.assertEqual( + sdk_log_record.log_record.span_id, INVALID_SPAN_CONTEXT.span_id + ) + self.assertEqual( + sdk_log_record.log_record.trace_flags, + INVALID_SPAN_CONTEXT.trace_flags, ) def test_log_record_observed_timestamp(self): @@ -114,8 +119,8 @@ def test_log_record_observed_timestamp(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") - log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record.observed_timestamp) + sdk_log_record = processor.get_log_record(0) + self.assertIsNotNone(sdk_log_record.log_record.observed_timestamp) def test_log_record_user_attributes(self): """Attributes can be injected into logs by adding them to the SDKLogRecord""" @@ -125,26 +130,33 @@ def test_log_record_user_attributes(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message", extra={"http.status_code": 200}) - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertEqual(len(log_record.attributes), 4) - self.assertEqual(log_record.attributes["http.status_code"], 200) + self.assertIsNotNone(sdk_log_record) + self.assertEqual(len(sdk_log_record.log_record.attributes), 4) + self.assertEqual( + sdk_log_record.log_record.attributes["http.status_code"], 200 + ) self.assertTrue( - log_record.attributes[code_attributes.CODE_FILE_PATH].endswith( - "test_handler.py" - ) + sdk_log_record.log_record.attributes[ + code_attributes.CODE_FILE_PATH + ].endswith("test_handler.py") ) self.assertEqual( - log_record.attributes[code_attributes.CODE_FUNCTION_NAME], + sdk_log_record.log_record.attributes[ + code_attributes.CODE_FUNCTION_NAME + ], "test_log_record_user_attributes", ) # The line of the log statement is not a constant (changing tests may change that), # so only check that the attribute is present. self.assertTrue( - code_attributes.CODE_LINE_NUMBER in log_record.attributes + code_attributes.CODE_LINE_NUMBER + in sdk_log_record.log_record.attributes + ) + self.assertTrue( + isinstance(sdk_log_record.log_record.attributes, BoundedAttributes) ) - self.assertTrue(isinstance(log_record.attributes, BoundedAttributes)) def test_log_record_exception(self): """Exception information will be included in attributes""" @@ -156,20 +168,24 @@ def test_log_record_exception(self): with self.assertLogs(level=logging.ERROR): logger.exception("Zero Division Error") - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertTrue(isinstance(log_record.body, str)) - self.assertEqual(log_record.body, "Zero Division Error") + self.assertIsNotNone(sdk_log_record) + self.assertTrue(isinstance(sdk_log_record.log_record.body, str)) + self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_TYPE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_TYPE + ], ZeroDivisionError.__name__, ) self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_MESSAGE + ], "division by zero", ) - stack_trace = log_record.attributes[ + stack_trace = sdk_log_record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -190,19 +206,23 @@ def test_log_record_recursive_exception(self): with self.assertLogs(level=logging.ERROR): logger.exception("Zero Division Error") - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertEqual(log_record.body, "Zero Division Error") + self.assertIsNotNone(sdk_log_record) + self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_TYPE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_TYPE + ], ZeroDivisionError.__name__, ) self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_MESSAGE + ], "division by zero", ) - stack_trace = log_record.attributes[ + stack_trace = sdk_log_record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -221,18 +241,21 @@ def test_log_exc_info_false(self): with self.assertLogs(level=logging.ERROR): logger.error("Zero Division Error", exc_info=False) - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertEqual(log_record.body, "Zero Division Error") + self.assertIsNotNone(sdk_log_record) + self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") self.assertNotIn( - exception_attributes.EXCEPTION_TYPE, log_record.attributes + exception_attributes.EXCEPTION_TYPE, + sdk_log_record.log_record.attributes, ) self.assertNotIn( - exception_attributes.EXCEPTION_MESSAGE, log_record.attributes + exception_attributes.EXCEPTION_MESSAGE, + sdk_log_record.log_record.attributes, ) self.assertNotIn( - exception_attributes.EXCEPTION_STACKTRACE, log_record.attributes + exception_attributes.EXCEPTION_STACKTRACE, + sdk_log_record.log_record.attributes, ) def test_log_record_exception_with_object_payload(self): @@ -248,20 +271,26 @@ def __str__(self): with self.assertLogs(level=logging.ERROR): logger.exception(exception) - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(log_record) - self.assertTrue(isinstance(log_record.body, str)) - self.assertEqual(log_record.body, "CustomException stringified") + self.assertIsNotNone(sdk_log_record) + self.assertTrue(isinstance(sdk_log_record.log_record.body, str)) + self.assertEqual( + sdk_log_record.log_record.body, "CustomException stringified" + ) self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_TYPE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_TYPE + ], CustomException.__name__, ) self.assertEqual( - log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], + sdk_log_record.log_record.attributes[ + exception_attributes.EXCEPTION_MESSAGE + ], "CustomException message", ) - stack_trace = log_record.attributes[ + stack_trace = sdk_log_record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -283,21 +312,32 @@ def test_log_record_trace_correlation(self): with self.assertLogs(level=logging.CRITICAL): logger.critical("Critical message within span") - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) self.assertEqual( - log_record.body, "Critical message within span" + sdk_log_record.log_record.body, + "Critical message within span", ) - self.assertEqual(log_record.severity_text, "CRITICAL") self.assertEqual( - log_record.severity_number, SeverityNumber.FATAL + sdk_log_record.log_record.severity_text, "CRITICAL" + ) + self.assertEqual( + sdk_log_record.log_record.severity_number, + SeverityNumber.FATAL, + ) + self.assertEqual( + sdk_log_record.log_record.context, mock_context ) - self.assertEqual(log_record.context, mock_context) span_context = span.get_span_context() - self.assertEqual(log_record.trace_id, span_context.trace_id) - self.assertEqual(log_record.span_id, span_context.span_id) self.assertEqual( - log_record.trace_flags, span_context.trace_flags + sdk_log_record.log_record.trace_id, span_context.trace_id + ) + self.assertEqual( + sdk_log_record.log_record.span_id, span_context.span_id + ) + self.assertEqual( + sdk_log_record.log_record.trace_flags, + span_context.trace_flags, ) def test_log_record_trace_correlation_deprecated(self): @@ -308,29 +348,41 @@ def test_log_record_trace_correlation_deprecated(self): with self.assertLogs(level=logging.CRITICAL): logger.critical("Critical message within span") - log_record = processor.get_log_record(0) + sdk_log_record = processor.get_log_record(0) - self.assertEqual(log_record.body, "Critical message within span") - self.assertEqual(log_record.severity_text, "CRITICAL") - self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) + self.assertEqual( + sdk_log_record.log_record.body, "Critical message within span" + ) + self.assertEqual( + sdk_log_record.log_record.severity_text, "CRITICAL" + ) + self.assertEqual( + sdk_log_record.log_record.severity_number, SeverityNumber.FATAL + ) span_context = span.get_span_context() - self.assertEqual(log_record.trace_id, span_context.trace_id) - self.assertEqual(log_record.span_id, span_context.span_id) - self.assertEqual(log_record.trace_flags, span_context.trace_flags) + self.assertEqual( + sdk_log_record.log_record.trace_id, span_context.trace_id + ) + self.assertEqual( + sdk_log_record.log_record.span_id, span_context.span_id + ) + self.assertEqual( + sdk_log_record.log_record.trace_flags, span_context.trace_flags + ) def test_warning_without_formatter(self): processor, logger = set_up_test_logging(logging.WARNING) logger.warning("Test message") - log_record = processor.get_log_record(0) - self.assertEqual(log_record.body, "Test message") + sdk_log_record = processor.get_log_record(0) + self.assertEqual(sdk_log_record.log_record.body, "Test message") def test_exception_without_formatter(self): processor, logger = set_up_test_logging(logging.WARNING) logger.exception("Test exception") - log_record = processor.get_log_record(0) - self.assertEqual(log_record.body, "Test exception") + sdk_log_record = processor.get_log_record(0) + self.assertEqual(sdk_log_record.log_record.body, "Test exception") def test_warning_with_formatter(self): processor, logger = set_up_test_logging( @@ -341,8 +393,10 @@ def test_warning_with_formatter(self): ) logger.warning("Test message") - log_record = processor.get_log_record(0) - self.assertEqual(log_record.body, "foo - WARNING - Test message") + sdk_log_record = processor.get_log_record(0) + self.assertEqual( + sdk_log_record.log_record.body, "foo - WARNING - Test message" + ) def test_log_body_is_always_string_with_formatter(self): processor, logger = set_up_test_logging( @@ -353,8 +407,8 @@ def test_log_body_is_always_string_with_formatter(self): ) logger.warning(["something", "of", "note"]) - log_record = processor.get_log_record(0) - self.assertIsInstance(log_record.body, str) + sdk_log_record = processor.get_log_record(0) + self.assertIsInstance(sdk_log_record.log_record.body, str) @patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"}) def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error( diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index aa00fca2322..7d360e4a751 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -16,7 +16,7 @@ import unittest import warnings -from opentelemetry._logs.severity import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.attributes import BoundedAttributes from opentelemetry.sdk._logs import ( LogDroppedAttributesWarning, @@ -29,17 +29,19 @@ class TestLogRecord(unittest.TestCase): def test_log_record_to_json(self): log_record = SDKLogRecord( - timestamp=0, - observed_timestamp=0, - body={"key": "logLine", "bytes": b"123"}, + LogRecord( + timestamp=0, + observed_timestamp=0, + body={"key": "logLine", "bytes": b"123"}, + attributes={ + "mapping": {"key": "value"}, + "none": None, + "sequence": [1, 2], + "str": "string", + }, + event_name="a.event", + ), resource=Resource({"service.name": "foo"}), - attributes={ - "mapping": {"key": "value"}, - "none": None, - "sequence": [1, 2], - "str": "string", - }, - event_name="a.event", ) self.assertEqual( @@ -49,10 +51,12 @@ def test_log_record_to_json(self): def test_log_record_to_json_serializes_severity_number_as_int(self): actual = SDKLogRecord( - timestamp=0, - severity_number=SeverityNumber.WARN, - observed_timestamp=0, - body="a log line", + LogRecord( + timestamp=0, + severity_number=SeverityNumber.WARN, + observed_timestamp=0, + body="a log line", + ), resource=Resource({"service.name": "foo"}), ) @@ -62,14 +66,20 @@ def test_log_record_to_json_serializes_severity_number_as_int(self): def test_log_record_bounded_attributes(self): attr = {"key": "value"} - result = SDKLogRecord(timestamp=0, body="a log line", attributes=attr) + result = SDKLogRecord( + LogRecord(timestamp=0, body="a log line", attributes=attr) + ) - self.assertTrue(isinstance(result.attributes, BoundedAttributes)) + self.assertTrue( + isinstance(result.log_record.attributes, BoundedAttributes) + ) def test_log_record_dropped_attributes_empty_limits(self): attr = {"key": "value"} - result = SDKLogRecord(timestamp=0, body="a log line", attributes=attr) + result = SDKLogRecord( + LogRecord(timestamp=0, body="a log line", attributes=attr) + ) self.assertTrue(result.dropped_attributes == 0) @@ -80,7 +90,8 @@ def test_log_record_dropped_attributes_set_limits_max_attribute(self): ) result = SDKLogRecord( - timestamp=0, body="a log line", attributes=attr, limits=limits + LogRecord(timestamp=0, body="a log line", attributes=attr), + limits=limits, ) self.assertTrue(result.dropped_attributes == 1) @@ -94,10 +105,15 @@ def test_log_record_dropped_attributes_set_limits_max_attribute_length( ) result = SDKLogRecord( - timestamp=0, body="a log line", attributes=attr, limits=limits + LogRecord( + timestamp=0, + body="a log line", + attributes=attr, + ), + limits=limits, ) self.assertTrue(result.dropped_attributes == 0) - self.assertEqual(expected, result.attributes) + self.assertEqual(expected, result.log_record.attributes) def test_log_record_dropped_attributes_set_limits(self): attr = {"key": "value", "key2": "value2"} @@ -108,10 +124,15 @@ def test_log_record_dropped_attributes_set_limits(self): ) result = SDKLogRecord( - timestamp=0, body="a log line", attributes=attr, limits=limits + LogRecord( + timestamp=0, + body="a log line", + attributes=attr, + ), + limits=limits, ) self.assertTrue(result.dropped_attributes == 1) - self.assertEqual(expected, result.attributes) + self.assertEqual(expected, result.log_record.attributes) def test_log_record_dropped_attributes_set_limits_warning_once(self): attr = {"key1": "value1", "key2": "value2"} @@ -123,9 +144,11 @@ def test_log_record_dropped_attributes_set_limits_warning_once(self): with warnings.catch_warnings(record=True) as cw: for _ in range(10): SDKLogRecord( - timestamp=0, - body="a log line", - attributes=attr, + LogRecord( + timestamp=0, + body="a log line", + attributes=attr, + ), limits=limits, ) self.assertEqual(len(cw), 1) @@ -140,7 +163,12 @@ def test_log_record_dropped_attributes_unset_limits(self): limits = LogLimits() result = SDKLogRecord( - timestamp=0, body="a log line", attributes=attr, limits=limits + LogRecord( + timestamp=0, + body="a log line", + attributes=attr, + ), + limits=limits, ) self.assertTrue(result.dropped_attributes == 0) - self.assertEqual(attr, result.attributes) + self.assertEqual(attr, result.log_record.attributes) diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index 146b5fddda5..623ee03a73b 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -21,7 +21,7 @@ from abc import ABC, abstractmethod from unittest.mock import Mock -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.sdk._logs._internal import ( ConcurrentMultiLogRecordProcessor, LoggerProvider, @@ -41,7 +41,9 @@ def __init__(self, exporter, logs_list): def on_emit(self, log_record: SDKLogRecord): if self._closed: return - self._log_list.append((log_record.body, log_record.severity_text)) + self._log_list.append( + (log_record.log_record.body, log_record.log_record.severity_text) + ) def shutdown(self): self._closed = True @@ -104,10 +106,12 @@ def _get_multi_log_record_processor(self): def make_record(self): return SDKLogRecord( - timestamp=1622300111608942000, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Warning message", + LogRecord( + timestamp=1622300111608942000, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Warning message", + ) ) def test_on_emit(self): From 13f927dba2eb51e28388f3c6d3650c06684a522f Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Fri, 18 Jul 2025 21:59:39 +0000 Subject: [PATCH 08/28] Update event test --- opentelemetry-sdk/tests/events/test_events.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index f93695acd8f..c21acb4c568 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -143,19 +143,6 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): log_record_mock_inst = Mock() log_record_mock.return_value = log_record_mock_inst event_logger.emit(event) - log_record_mock.assert_called_once_with( - timestamp=now, - observed_timestamp=None, - severity_text=None, - severity_number=SeverityNumber.ERROR, - body="test body", - resource=event_logger._logger.resource, - attributes={ - "key": "val", - "foo": "bar", - "event.name": "test_event", - }, - ) logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst) @patch("opentelemetry.sdk._events.SDKLogRecord") From 180c18924c24bc5e5ae10f87f8cd05fa632fd358 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Sat, 19 Jul 2025 00:14:49 +0000 Subject: [PATCH 09/28] Update --- .../tests/test_log_encoder.py | 164 ++++++++++-------- .../tests/logs/test_otlp_logs_exporter.py | 42 +++-- 2 files changed, 118 insertions(+), 88 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 24766f09dd0..c86cd74a918 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -15,7 +15,7 @@ import unittest from typing import List, Tuple -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.exporter.otlp.proto.common._internal import ( _encode_attributes, _encode_span_id, @@ -64,7 +64,7 @@ def test_encode(self): def test_encode_no_body(self): sdk_logs, expected_encoding = self.get_test_logs() for log in sdk_logs: - log.body = None + log.log_record.body = None for resource_log in expected_encoding.resource_logs: for scope_log in resource_log.scope_logs: @@ -99,30 +99,34 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log1 = SDKLogRecord( - timestamp=1644650195189786880, - observed_timestamp=1644650195189786881, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", + LogRecord( + timestamp=1644650195189786880, + observed_timestamp=1644650195189786881, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource( {"first_resource": "value"}, "resource_schema_url", ), - attributes={"a": 1, "b": "c"}, instrumentation_scope=InstrumentationScope( "first_name", "first_version" ), ) log2 = SDKLogRecord( - timestamp=1644650249738562048, - observed_timestamp=1644650249738562049, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", + LogRecord( + timestamp=1644650249738562048, + observed_timestamp=1644650249738562049, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + attributes={}, + ), resource=SDKResource({"second_resource": "CASE"}), - attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -139,14 +143,16 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log3 = SDKLogRecord( - timestamp=1644650427658989056, - observed_timestamp=1644650427658989057, - context=ctx_log3, - severity_text="DEBUG", - severity_number=SeverityNumber.DEBUG, - body="To our galaxy", + LogRecord( + timestamp=1644650427658989056, + observed_timestamp=1644650427658989057, + context=ctx_log3, + severity_text="DEBUG", + severity_number=SeverityNumber.DEBUG, + body="To our galaxy", + attributes={"a": 1, "b": "c"}, + ), resource=SDKResource({"second_resource": "CASE"}), - attributes={"a": 1, "b": "c"}, instrumentation_scope=None, ) @@ -161,17 +167,19 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log4 = SDKLogRecord( - timestamp=1644650584292683008, - observed_timestamp=1644650584292683009, - context=ctx_log4, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Love is the one thing that transcends time and space", + LogRecord( + timestamp=1644650584292683008, + observed_timestamp=1644650584292683009, + context=ctx_log4, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Love is the one thing that transcends time and space", + attributes={"filename": "model.py", "func_name": "run_method"}, + ), resource=SDKResource( {"first_resource": "value"}, "resource_schema_url", ), - attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "another_name", "another_version" ), @@ -188,14 +196,16 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log5 = SDKLogRecord( - timestamp=1644650584292683009, - observed_timestamp=1644650584292683010, - context=ctx_log5, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body={"error": None, "array_with_nones": [1, None, 2]}, + LogRecord( + timestamp=1644650584292683009, + observed_timestamp=1644650584292683010, + context=ctx_log5, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body={"error": None, "array_with_nones": [1, None, 2]}, + attributes={}, + ), resource=SDKResource({}), - attributes={}, instrumentation_scope=InstrumentationScope( "last_name", "last_version" ), @@ -212,17 +222,19 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log6 = SDKLogRecord( - timestamp=1644650584292683022, - observed_timestamp=1644650584292683022, - context=ctx_log6, - severity_text="ERROR", - severity_number=SeverityNumber.ERROR, - body="This instrumentation scope has a schema url", + LogRecord( + timestamp=1644650584292683022, + observed_timestamp=1644650584292683022, + context=ctx_log6, + severity_text="ERROR", + severity_number=SeverityNumber.ERROR, + body="This instrumentation scope has a schema url", + attributes={"filename": "model.py", "func_name": "run_method"}, + ), resource=SDKResource( {"first_resource": "value"}, "resource_schema_url", ), - attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "scope_with_url", "scope_with_url_version", @@ -241,17 +253,19 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log7 = SDKLogRecord( - timestamp=1644650584292683033, - observed_timestamp=1644650584292683033, - context=ctx_log7, - severity_text="FATAL", - severity_number=SeverityNumber.FATAL, - body="This instrumentation scope has a schema url and attributes", + LogRecord( + timestamp=1644650584292683033, + observed_timestamp=1644650584292683033, + context=ctx_log7, + severity_text="FATAL", + severity_number=SeverityNumber.FATAL, + body="This instrumentation scope has a schema url and attributes", + attributes={"filename": "model.py", "func_name": "run_method"}, + ), resource=SDKResource( {"first_resource": "value"}, "resource_schema_url", ), - attributes={"filename": "model.py", "func_name": "run_method"}, instrumentation_scope=InstrumentationScope( "scope_with_attributes", "scope_with_attributes_version", @@ -271,16 +285,20 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) log8 = SDKLogRecord( - timestamp=1644650584292683044, - observed_timestamp=1644650584292683044, - context=ctx_log8, - severity_text="INFO", - severity_number=SeverityNumber.INFO, - body="Test export of extended attributes", + LogRecord( + timestamp=1644650584292683044, + observed_timestamp=1644650584292683044, + context=ctx_log8, + severity_text="INFO", + severity_number=SeverityNumber.INFO, + body="Test export of extended attributes", + attributes={ + "extended": { + "sequence": [{"inner": "mapping", "none": None}] + } + }, + ), resource=SDKResource({}), - attributes={ - "extended": {"sequence": [{"inner": "mapping", "none": None}]} - }, instrumentation_scope=InstrumentationScope( "extended_name", "extended_version" ), @@ -594,13 +612,15 @@ def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: ) ) log1 = SDKLogRecord( - timestamp=1644650195189786880, - context=ctx_log1, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Do not go gentle into that good night. Rage, rage against the dying of the light", + LogRecord( + timestamp=1644650195189786880, + context=ctx_log1, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Do not go gentle into that good night. Rage, rage against the dying of the light", + attributes={"a": 1, "b": "c", "user_id": "B121092"}, + ), resource=SDKResource({"first_resource": "value"}), - attributes={"a": 1, "b": "c", "user_id": "B121092"}, limits=LogLimits(max_attributes=1), instrumentation_scope=InstrumentationScope( "first_name", "first_version" @@ -610,13 +630,15 @@ def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: NonRecordingSpan(SpanContext(0, 0, False)) ) log2 = SDKLogRecord( - timestamp=1644650249738562048, - context=ctx_log2, - severity_text="WARN", - severity_number=SeverityNumber.WARN, - body="Cooper, this is no time for caution!", + LogRecord( + timestamp=1644650249738562048, + context=ctx_log2, + severity_text="WARN", + severity_number=SeverityNumber.WARN, + body="Cooper, this is no time for caution!", + attributes={}, + ), resource=SDKResource({"second_resource": "CASE"}), - attributes={}, instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index d395207cfb4..c963cffb87c 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -105,9 +105,9 @@ def setUp(self): severity_text="INFO", severity_number=SeverityNumber.INFO2, body="Sydney, Opera House is closed", - resource=SDKResource({"key": "value"}), attributes={"custom_attr": [1, 2, 3]}, ), + resource=SDKResource({"key": "value"}), instrumentation_scope=InstrumentationScope( "second_name", "second_version" ), @@ -361,9 +361,9 @@ def test_translate_log_data(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_1.timestamp, - observed_time_unix_nano=self.log_data_1.observed_timestamp, - severity_number=self.log_data_1.severity_number.value, + time_unix_nano=self.log_data_1.log_record.timestamp, + observed_time_unix_nano=self.log_data_1.log_record.observed_timestamp, + severity_number=self.log_data_1.log_record.severity_number.value, severity_text="WARNING", span_id=int.to_bytes( 5213367945872657620, 8, "big" @@ -386,7 +386,9 @@ def test_translate_log_data(self): value=AnyValue(string_value="c"), ), ], - flags=int(self.log_data_1.trace_flags), + flags=int( + self.log_data_1.log_record.trace_flags + ), ) ], ) @@ -419,9 +421,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_1.timestamp, - observed_time_unix_nano=self.log_data_1.observed_timestamp, - severity_number=self.log_data_1.severity_number.value, + time_unix_nano=self.log_data_1.log_record.timestamp, + observed_time_unix_nano=self.log_data_1.log_record.observed_timestamp, + severity_number=self.log_data_1.log_record.severity_number.value, severity_text="WARNING", span_id=int.to_bytes( 5213367945872657620, 8, "big" @@ -444,7 +446,9 @@ def test_translate_multiple_logs(self): value=AnyValue(string_value="c"), ), ], - flags=int(self.log_data_1.trace_flags), + flags=int( + self.log_data_1.log_record.trace_flags + ), ) ], ), @@ -455,9 +459,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_2.timestamp, - observed_time_unix_nano=self.log_data_2.observed_timestamp, - severity_number=self.log_data_2.severity_number.value, + time_unix_nano=self.log_data_2.log_record.timestamp, + observed_time_unix_nano=self.log_data_2.log_record.observed_timestamp, + severity_number=self.log_data_2.log_record.severity_number.value, severity_text="INFO", span_id=int.to_bytes( 5213367945872657623, 8, "big" @@ -476,7 +480,9 @@ def test_translate_multiple_logs(self): value=_encode_value([1, 2, 3]), ), ], - flags=int(self.log_data_2.trace_flags), + flags=int( + self.log_data_2.log_record.trace_flags + ), ) ], ), @@ -499,9 +505,9 @@ def test_translate_multiple_logs(self): log_records=[ PB2LogRecord( # pylint: disable=no-member - time_unix_nano=self.log_data_3.timestamp, - observed_time_unix_nano=self.log_data_3.observed_timestamp, - severity_number=self.log_data_3.severity_number.value, + time_unix_nano=self.log_data_3.log_record.timestamp, + observed_time_unix_nano=self.log_data_3.log_record.observed_timestamp, + severity_number=self.log_data_3.log_record.severity_number.value, severity_text="ERROR", span_id=int.to_bytes( 5213367945872657628, 8, "big" @@ -515,7 +521,9 @@ def test_translate_multiple_logs(self): "Mumbai, Boil water before drinking" ), attributes=[], - flags=int(self.log_data_3.trace_flags), + flags=int( + self.log_data_3.log_record.trace_flags + ), ) ], ) From 5f9e5506a1776a370afd0c684ecd9a053bf4f1f9 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:51:01 -0700 Subject: [PATCH 10/28] Address comments --- .../sdk/_logs/_internal/__init__.py | 35 ++++++++----------- opentelemetry-sdk/tests/logs/test_export.py | 1 + .../shared_internal/test_batch_processor.py | 4 +++ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 05875123f32..65086fa51cc 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -184,28 +184,25 @@ def __init__( limits: LogLimits | None = _UnsetLogLimits, ): ... - def __init__( # pylint:disable=too-many-locals + def __init__( self, - log_record: LogRecord | None = None, + log_record: LogRecord, resource: Resource | None = None, limits: LogLimits | None = _UnsetLogLimits, instrumentation_scope: InstrumentationScope | None = None, ): self.log_record = log_record - if self.log_record is not None: - self.log_record.attributes = BoundedAttributes( - maxlen=limits.max_attributes, - attributes=self.log_record.attributes - if bool(self.log_record.attributes) - else None, - immutable=False, - max_value_len=limits.max_attribute_length, - extended_attributes=True, - ) - - self.resource = ( - resource if isinstance(resource, Resource) else Resource.create({}) + self.log_record.attributes = BoundedAttributes( + maxlen=limits.max_attributes, + attributes=self.log_record.attributes + if bool(self.log_record.attributes) + else None, + immutable=False, + max_value_len=limits.max_attribute_length, + extended_attributes=True, ) + + self.resource = resource if resource else Resource.create({}) self.instrumentation_scope = instrumentation_scope if self.dropped_attributes > 0: warnings.warn( @@ -249,9 +246,7 @@ def to_json(self, indent: int | None = 4) -> str: ), "trace_flags": self.log_record.trace_flags, "resource": json.loads(self.resource.to_json()), - "event_name": self.log_record.event_name - if self.log_record.event_name - else "", + "event_name": self.log_record.event_name or "", }, indent=indent, cls=BytesEncoder, @@ -259,9 +254,7 @@ def to_json(self, indent: int | None = 4) -> str: @property def dropped_attributes(self) -> int: - if self.log_record and isinstance( - self.log_record.attributes, BoundedAttributes - ): + if isinstance(self.log_record.attributes, BoundedAttributes): return self.log_record.attributes.dropped return 0 diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index ae1897126f2..e4ef5760b98 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -54,6 +54,7 @@ from opentelemetry.trace.span import INVALID_SPAN_CONTEXT EMPTY_LOG = SDKLogRecord( + log_record=LogRecord(), instrumentation_scope=InstrumentationScope("example", "example"), ) diff --git a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py index b2fe09ca331..d733823095c 100644 --- a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py +++ b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py @@ -24,6 +24,9 @@ import pytest +from opentelemetry._logs import ( + LogRecord, +) from opentelemetry.sdk._logs import ( SDKLogRecord, ) @@ -35,6 +38,7 @@ from opentelemetry.sdk.util.instrumentation import InstrumentationScope EMPTY_LOG = SDKLogRecord( + log_record=LogRecord(), instrumentation_scope=InstrumentationScope("example", "example"), ) From ba283abe51ce8b13199809127f5cdbee1eab916a Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:55:24 -0700 Subject: [PATCH 11/28] Update --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 65086fa51cc..5507f035e43 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -195,7 +195,7 @@ def __init__( self.log_record.attributes = BoundedAttributes( maxlen=limits.max_attributes, attributes=self.log_record.attributes - if bool(self.log_record.attributes) + if self.log_record.attributes else None, immutable=False, max_value_len=limits.max_attribute_length, From c3f1607f8a8f81e06889e1f3db453653ebd4c9bb Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 11:32:56 -0700 Subject: [PATCH 12/28] Add ReadableLogRecord and ReadWriteLogRecord --- .../common/_internal/_log_encoder/__init__.py | 46 ++++++++++--------- .../tests/test_log_encoder.py | 28 +++++------ .../otlp/proto/grpc/_log_exporter/__init__.py | 10 ++-- .../tests/logs/test_otlp_logs_exporter.py | 12 ++--- .../otlp/proto/http/_log_exporter/__init__.py | 4 +- .../tests/test_proto_log_exporter.py | 16 +++---- .../src/opentelemetry/sdk/_events/__init__.py | 4 +- .../src/opentelemetry/sdk/_logs/__init__.py | 6 ++- .../sdk/_logs/_internal/__init__.py | 31 +++++++++---- .../sdk/_logs/_internal/export/__init__.py | 18 +++++--- .../export/in_memory_log_exporter.py | 8 ++-- opentelemetry-sdk/tests/events/test_events.py | 4 +- opentelemetry-sdk/tests/logs/test_export.py | 6 +-- opentelemetry-sdk/tests/logs/test_handler.py | 6 +-- .../tests/logs/test_log_record.py | 20 ++++---- .../tests/logs/test_multi_log_processor.py | 11 +++-- .../shared_internal/test_batch_processor.py | 4 +- 17 files changed, 131 insertions(+), 103 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index bd84e67f1a3..d584957efe4 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -30,49 +30,53 @@ ResourceLogs, ScopeLogs, ) -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs import ReadableLogRecord -def encode_logs(batch: Sequence[SDKLogRecord]) -> ExportLogsServiceRequest: +def encode_logs( + batch: Sequence[ReadableLogRecord], +) -> ExportLogsServiceRequest: return ExportLogsServiceRequest(resource_logs=_encode_resource_logs(batch)) -def _encode_log(sdk_log_record: SDKLogRecord) -> PB2LogRecord: +def _encode_log(readable_log_record: ReadableLogRecord) -> PB2LogRecord: span_id = ( None - if sdk_log_record.log_record.span_id == 0 - else _encode_span_id(sdk_log_record.log_record.span_id) + if readable_log_record.span_id == 0 + else _encode_span_id(readable_log_record.span_id) ) trace_id = ( None - if sdk_log_record.log_record.trace_id == 0 - else _encode_trace_id(sdk_log_record.log_record.trace_id) + if readable_log_record.trace_id == 0 + else _encode_trace_id(readable_log_record.trace_id) ) - body = sdk_log_record.log_record.body + body = readable_log_record.body return PB2LogRecord( - time_unix_nano=sdk_log_record.log_record.timestamp, - observed_time_unix_nano=sdk_log_record.log_record.observed_timestamp, + time_unix_nano=readable_log_record.timestamp, + observed_time_unix_nano=readable_log_record.observed_timestamp, span_id=span_id, trace_id=trace_id, - flags=int(sdk_log_record.log_record.trace_flags), + flags=int(readable_log_record.trace_flags), body=_encode_value(body, allow_null=True), - severity_text=sdk_log_record.log_record.severity_text, + severity_text=readable_log_record.severity_text, attributes=_encode_attributes( - sdk_log_record.log_record.attributes, allow_null=True + readable_log_record.attributes, allow_null=True ), - dropped_attributes_count=sdk_log_record.dropped_attributes, - severity_number=sdk_log_record.log_record.severity_number.value, - event_name=sdk_log_record.log_record.event_name, + dropped_attributes_count=readable_log_record.dropped_attributes, + severity_number=readable_log_record.severity_number.value, + event_name=readable_log_record.event_name, ) -def _encode_resource_logs(batch: Sequence[SDKLogRecord]) -> List[ResourceLogs]: +def _encode_resource_logs( + batch: Sequence[ReadableLogRecord], +) -> List[ResourceLogs]: sdk_resource_logs = defaultdict(lambda: defaultdict(list)) - for sdk_log in batch: - sdk_resource = sdk_log.resource - sdk_instrumentation = sdk_log.instrumentation_scope or None - pb2_log = _encode_log(sdk_log) + for readable_log in batch: + sdk_resource = readable_log.resource + sdk_instrumentation = readable_log.instrumentation_scope or None + pb2_log = _encode_log(readable_log) sdk_resource_logs[sdk_resource][sdk_instrumentation].append(pb2_log) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index c86cd74a918..e13f3a7d15f 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -45,7 +45,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as PB2Resource, ) -from opentelemetry.sdk._logs import LogLimits, SDKLogRecord +from opentelemetry.sdk._logs import LogLimits, ReadWriteLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import ( @@ -87,7 +87,7 @@ def test_dropped_attributes_count(self): ) @staticmethod - def _get_sdk_log_data() -> List[SDKLogRecord]: + def _get_sdk_log_data() -> List[ReadWriteLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -98,7 +98,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log1 = SDKLogRecord( + log1 = ReadWriteLogRecord( LogRecord( timestamp=1644650195189786880, observed_timestamp=1644650195189786881, @@ -117,7 +117,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ), ) - log2 = SDKLogRecord( + log2 = ReadWriteLogRecord( LogRecord( timestamp=1644650249738562048, observed_timestamp=1644650249738562049, @@ -142,7 +142,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log3 = SDKLogRecord( + log3 = ReadWriteLogRecord( LogRecord( timestamp=1644650427658989056, observed_timestamp=1644650427658989057, @@ -166,7 +166,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log4 = SDKLogRecord( + log4 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683008, observed_timestamp=1644650584292683009, @@ -195,7 +195,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log5 = SDKLogRecord( + log5 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683009, observed_timestamp=1644650584292683010, @@ -221,7 +221,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log6 = SDKLogRecord( + log6 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683022, observed_timestamp=1644650584292683022, @@ -252,7 +252,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log7 = SDKLogRecord( + log7 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683033, observed_timestamp=1644650584292683033, @@ -284,7 +284,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log8 = SDKLogRecord( + log8 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683044, observed_timestamp=1644650584292683044, @@ -307,7 +307,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: def get_test_logs( self, - ) -> Tuple[List[SDKLogRecord], ExportLogsServiceRequest]: + ) -> Tuple[List[ReadWriteLogRecord], ExportLogsServiceRequest]: sdk_logs = self._get_sdk_log_data() pb2_service_request = ExportLogsServiceRequest( @@ -600,7 +600,7 @@ def get_test_logs( return sdk_logs, pb2_service_request @staticmethod - def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: + def _get_test_logs_dropped_attributes() -> List[ReadWriteLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -611,7 +611,7 @@ def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: ) ) ) - log1 = SDKLogRecord( + log1 = ReadWriteLogRecord( LogRecord( timestamp=1644650195189786880, context=ctx_log1, @@ -629,7 +629,7 @@ def _get_test_logs_dropped_attributes() -> List[SDKLogRecord]: ctx_log2 = set_span_in_context( NonRecordingSpan(SpanContext(0, 0, False)) ) - log2 = SDKLogRecord( + log2 = ReadWriteLogRecord( LogRecord( timestamp=1644650249738562048, context=ctx_log2, diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index 1989a254fa0..a6850602fa8 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -28,7 +28,7 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2_grpc import ( LogsServiceStub, ) -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs import ReadableLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, @@ -44,7 +44,9 @@ class OTLPLogExporter( LogExporter, - OTLPExporterMixin[SDKLogRecord, ExportLogsServiceRequest, LogExportResult], + OTLPExporterMixin[ + ReadableLogRecord, ExportLogsServiceRequest, LogExportResult + ], ): _result = LogExportResult _stub = LogsServiceStub @@ -104,11 +106,11 @@ def __init__( ) def _translate_data( - self, data: Sequence[SDKLogRecord] + self, data: Sequence[ReadableLogRecord] ) -> ExportLogsServiceRequest: return encode_logs(data) - def export(self, batch: Sequence[SDKLogRecord]) -> LogExportResult: + def export(self, batch: Sequence[ReadableLogRecord]) -> LogExportResult: return self._export(batch) def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None: diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index c963cffb87c..94e8cc944c3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -39,7 +39,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as OTLPResource, ) -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs import ReadWriteLogRecord from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, @@ -74,7 +74,7 @@ def setUp(self): ) ) ) - self.log_data_1 = SDKLogRecord( + self.log_data_1 = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_1, @@ -98,7 +98,7 @@ def setUp(self): ) ) ) - self.log_data_2 = SDKLogRecord( + self.log_data_2 = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_2, @@ -122,7 +122,7 @@ def setUp(self): ) ) ) - self.log_data_3 = SDKLogRecord( + self.log_data_3 = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_3, @@ -140,7 +140,7 @@ def setUp(self): SpanContext(0, 5213367945872657629, False, TraceFlags(0x01)) ) ) - self.log_data_4 = SDKLogRecord( + self.log_data_4 = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_4, @@ -163,7 +163,7 @@ def setUp(self): ) ) ) - self.log_data_5 = SDKLogRecord( + self.log_data_5 = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx_log_data_5, diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index ef02bb45739..1a398bb5be0 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -33,7 +33,7 @@ from opentelemetry.exporter.otlp.proto.http._common import ( _is_retryable, ) -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs import ReadableLogRecord from opentelemetry.sdk._logs.export import ( LogExporter, LogExportResult, @@ -163,7 +163,7 @@ def _export( ) return resp - def export(self, batch: Sequence[SDKLogRecord]) -> LogExportResult: + def export(self, batch: Sequence[ReadableLogRecord]) -> LogExportResult: if self._shutdown: _logger.warning("Exporter already shutdown, ignoring batch") return LogExportResult.FAILURE diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index 5106b2957d0..fa95b14fb20 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -39,7 +39,7 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, ) -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs import ReadWriteLogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_CERTIFICATE, @@ -232,7 +232,7 @@ def test_exported_log_without_trace_id(self): ) ) ) - log = SDKLogRecord( + log = ReadWriteLogRecord( LogRecord( timestamp=1644650195189786182, context=ctx, @@ -268,7 +268,7 @@ def test_exported_log_without_span_id(self): ) ) - log = SDKLogRecord( + log = ReadWriteLogRecord( LogRecord( timestamp=1644650195189786360, context=ctx, @@ -293,7 +293,7 @@ def test_exported_log_without_span_id(self): self.fail("No log records found") @staticmethod - def _get_sdk_log_data() -> List[SDKLogRecord]: + def _get_sdk_log_data() -> List[ReadWriteLogRecord]: ctx_log1 = set_span_in_context( NonRecordingSpan( SpanContext( @@ -304,7 +304,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log1 = SDKLogRecord( + log1 = ReadWriteLogRecord( LogRecord( timestamp=1644650195189786880, context=ctx_log1, @@ -328,7 +328,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log2 = SDKLogRecord( + log2 = ReadWriteLogRecord( LogRecord( timestamp=1644650249738562048, context=ctx_log2, @@ -352,7 +352,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log3 = SDKLogRecord( + log3 = ReadWriteLogRecord( LogRecord( timestamp=1644650427658989056, context=ctx_log3, @@ -374,7 +374,7 @@ def _get_sdk_log_data() -> List[SDKLogRecord]: ) ) ) - log4 = SDKLogRecord( + log4 = ReadWriteLogRecord( LogRecord( timestamp=1644650584292683008, context=ctx_log4, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 8a09497cac0..99672a49d12 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -24,7 +24,7 @@ SeverityNumber, get_logger_provider, ) -from opentelemetry.sdk._logs import Logger, LoggerProvider, SDKLogRecord +from opentelemetry.sdk._logs import Logger, LoggerProvider, ReadWriteLogRecord from opentelemetry.util.types import _ExtendedAttributes _logger = logging.getLogger(__name__) @@ -53,7 +53,7 @@ def emit(self, event: Event) -> None: if isinstance(self._logger, NoOpLogger): # Do nothing if SDK is disabled return - log_record = SDKLogRecord( + log_record = ReadWriteLogRecord( LogRecord( timestamp=event.timestamp or time_ns(), observed_timestamp=None, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index ffcb02b1061..763655e553e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -20,7 +20,8 @@ LoggingHandler, LogLimits, LogRecordProcessor, - SDKLogRecord, + ReadableLogRecord, + ReadWriteLogRecord, ) __all__ = [ @@ -30,5 +31,6 @@ "LogLimits", "LogRecordProcessor", "LogDroppedAttributesWarning", - "SDKLogRecord", + "ReadableLogRecord", + "ReadWriteLogRecord", ] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 5507f035e43..e93be0cfca1 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -22,6 +22,7 @@ import threading import traceback import warnings +from dataclasses import dataclass from os import environ from threading import Lock from time import time_ns @@ -168,10 +169,20 @@ def _from_env_if_absent( ) -class SDKLogRecord: - """A SDKLogRecord instance represents an event being logged. +@dataclass(frozen=True) +class ReadableLogRecord: + """Readable LogRecord should be kept exactly in-sync with ReadWriteLogRecord, only difference is the frozen=True param.""" - SDKLogRecord instances are created and emitted via `Logger` + log_record: LogRecord + resource: Resource + instrumentation_scope: InstrumentationScope + + +@dataclass +class ReadWriteLogRecord: + """A ReadWriteLogRecord instance represents an event being logged. + + ReadWriteLogRecord instances are created and emitted via `Logger` every time something is logged. They contain all the information pertinent to the event being logged. """ @@ -212,7 +223,7 @@ def __init__( ) def __eq__(self, other: object) -> bool: - if not isinstance(other, SDKLogRecord): + if not isinstance(other, ReadWriteLogRecord): return NotImplemented return self.__dict__ == other.__dict__ @@ -268,8 +279,8 @@ class LogRecordProcessor(abc.ABC): """ @abc.abstractmethod - def on_emit(self, log_record: SDKLogRecord): - """Emits the `SDKLogRecord`""" + def on_emit(self, log_record: ReadWriteLogRecord): + """Emits the `ReadWriteLogRecord`""" @abc.abstractmethod def shutdown(self): @@ -312,7 +323,7 @@ def add_log_record_processor( with self._lock: self._log_record_processors += (log_record_processor,) - def on_emit(self, log_record: SDKLogRecord) -> None: + def on_emit(self, log_record: ReadWriteLogRecord) -> None: for lp in self._log_record_processors: lp.on_emit(log_record) @@ -386,7 +397,7 @@ def _submit_and_wait( for future in futures: future.result() - def on_emit(self, log_record: SDKLogRecord): + def on_emit(self, log_record: ReadWriteLogRecord): self._submit_and_wait(lambda lp: lp.on_emit, log_record) def shutdown(self): @@ -588,10 +599,10 @@ def resource(self): return self._resource def emit(self, record: LogRecord): - """Emits the :class:`SDKLogRecord` by setting instrumentation scope + """Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope and forwarding to the processor. """ - sdk_log_record = SDKLogRecord( + sdk_log_record = ReadWriteLogRecord( log_record=record, resource=self._resource, instrumentation_scope=self._instrumentation_scope, 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 4946d9b9162..da3f78ce060 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -26,7 +26,11 @@ detach, set_value, ) -from opentelemetry.sdk._logs import LogRecordProcessor, SDKLogRecord +from opentelemetry.sdk._logs._internal import ( + LogRecordProcessor, + ReadableLogRecord, + ReadWriteLogRecord, +) from opentelemetry.sdk._shared_internal import BatchProcessor from opentelemetry.sdk.environment_variables import ( OTEL_BLRP_EXPORT_TIMEOUT, @@ -59,10 +63,10 @@ class LogExporter(abc.ABC): """ @abc.abstractmethod - def export(self, batch: Sequence[SDKLogRecord]): + def export(self, batch: Sequence[ReadableLogRecord]) -> LogExportResult: """Exports a batch of logs. Args: - batch: The list of `SDKLogRecord` objects to be exported + batch: The list of `ReadableLogRecord` objects to be exported Returns: The result of the export """ @@ -87,13 +91,13 @@ def __init__( self, out: IO = sys.stdout, formatter: Callable[ - [SDKLogRecord], str + [ReadableLogRecord], str ] = lambda record: record.to_json() + linesep, ): self.out = out self.formatter = formatter - def export(self, batch: Sequence[SDKLogRecord]): + def export(self, batch: Sequence[ReadableLogRecord]): for log_record in batch: self.out.write(self.formatter(log_record)) self.out.flush() @@ -112,7 +116,7 @@ def __init__(self, exporter: LogExporter): self._exporter = exporter self._shutdown = False - def on_emit(self, log_record: SDKLogRecord): + def on_emit(self, log_record: ReadWriteLogRecord): if self._shutdown: _logger.warning("Processor is already shutdown, ignoring call") return @@ -185,7 +189,7 @@ def __init__( "Log", ) - def on_emit(self, log_record: SDKLogRecord) -> None: + def on_emit(self, log_record: ReadWriteLogRecord) -> None: return self._batch_processor.emit(log_record) def shutdown(self): diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py index c947995d045..0911a500909 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py @@ -15,7 +15,7 @@ import threading import typing -from opentelemetry.sdk._logs import SDKLogRecord +from opentelemetry.sdk._logs._internal import ReadableLogRecord from opentelemetry.sdk._logs.export import LogExporter, LogExportResult @@ -36,11 +36,13 @@ def clear(self) -> None: with self._lock: self._logs.clear() - def get_finished_logs(self) -> typing.Tuple[SDKLogRecord, ...]: + def get_finished_logs(self) -> typing.Tuple[ReadableLogRecord, ...]: with self._lock: return tuple(self._logs) - def export(self, batch: typing.Sequence[SDKLogRecord]) -> LogExportResult: + def export( + self, batch: typing.Sequence[ReadableLogRecord] + ) -> LogExportResult: if self._stopped: return LogExportResult.FAILURE with self._lock: diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index c21acb4c568..83e03fdb140 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -107,7 +107,7 @@ def test_event_logger(self, logger_mock): "name", "version", "schema_url", {"key": "value"} ) - @patch("opentelemetry.sdk._events.SDKLogRecord") + @patch("opentelemetry.sdk._events.ReadWriteLogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit(self, logger_mock, log_record_mock): logger_provider = LoggerProvider() @@ -145,7 +145,7 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): event_logger.emit(event) logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst) - @patch("opentelemetry.sdk._events.SDKLogRecord") + @patch("opentelemetry.sdk._events.ReadWriteLogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit_sdk_disabled( self, logger_mock, log_record_mock diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index e4ef5760b98..be9f2430dce 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -28,7 +28,7 @@ from opentelemetry.sdk._logs import ( LoggerProvider, LoggingHandler, - SDKLogRecord, + ReadWriteLogRecord, ) from opentelemetry.sdk._logs._internal.export import _logger from opentelemetry.sdk._logs.export import ( @@ -53,7 +53,7 @@ ) from opentelemetry.trace.span import INVALID_SPAN_CONTEXT -EMPTY_LOG = SDKLogRecord( +EMPTY_LOG = ReadWriteLogRecord( log_record=LogRecord(), instrumentation_scope=InstrumentationScope("example", "example"), ) @@ -628,7 +628,7 @@ def test_export(self): # pylint: disable=no-self-use ) ) ) - log_record = SDKLogRecord( + log_record = ReadWriteLogRecord( LogRecord( timestamp=int(time.time() * 1e9), context=ctx, diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 22e68dbe27a..67d250ef769 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -25,7 +25,7 @@ LoggerProvider, LoggingHandler, LogRecordProcessor, - SDKLogRecord, + ReadableLogRecord, ) from opentelemetry.semconv._incubating.attributes import code_attributes from opentelemetry.semconv.attributes import exception_attributes @@ -123,7 +123,7 @@ def test_log_record_observed_timestamp(self): self.assertIsNotNone(sdk_log_record.log_record.observed_timestamp) def test_log_record_user_attributes(self): - """Attributes can be injected into logs by adding them to the SDKLogRecord""" + """Attributes can be injected into logs by adding them to the ReadWriteLogRecord""" processor, logger = set_up_test_logging(logging.WARNING) # Assert emit gets called for warning message @@ -438,7 +438,7 @@ class FakeProcessor(LogRecordProcessor): def __init__(self): self.log_data_emitted = [] - def on_emit(self, log_record: SDKLogRecord): + def on_emit(self, log_record: ReadableLogRecord): self.log_data_emitted.append(log_record) def shutdown(self): diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index 7d360e4a751..852b2baaa1f 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -21,14 +21,14 @@ from opentelemetry.sdk._logs import ( LogDroppedAttributesWarning, LogLimits, - SDKLogRecord, + ReadWriteLogRecord, ) from opentelemetry.sdk.resources import Resource class TestLogRecord(unittest.TestCase): def test_log_record_to_json(self): - log_record = SDKLogRecord( + log_record = ReadWriteLogRecord( LogRecord( timestamp=0, observed_timestamp=0, @@ -50,7 +50,7 @@ def test_log_record_to_json(self): ) def test_log_record_to_json_serializes_severity_number_as_int(self): - actual = SDKLogRecord( + actual = ReadWriteLogRecord( LogRecord( timestamp=0, severity_number=SeverityNumber.WARN, @@ -66,7 +66,7 @@ def test_log_record_to_json_serializes_severity_number_as_int(self): def test_log_record_bounded_attributes(self): attr = {"key": "value"} - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord(timestamp=0, body="a log line", attributes=attr) ) @@ -77,7 +77,7 @@ def test_log_record_bounded_attributes(self): def test_log_record_dropped_attributes_empty_limits(self): attr = {"key": "value"} - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord(timestamp=0, body="a log line", attributes=attr) ) @@ -89,7 +89,7 @@ def test_log_record_dropped_attributes_set_limits_max_attribute(self): max_attributes=1, ) - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord(timestamp=0, body="a log line", attributes=attr), limits=limits, ) @@ -104,7 +104,7 @@ def test_log_record_dropped_attributes_set_limits_max_attribute_length( max_attribute_length=1, ) - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord( timestamp=0, body="a log line", @@ -123,7 +123,7 @@ def test_log_record_dropped_attributes_set_limits(self): max_attribute_length=1, ) - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord( timestamp=0, body="a log line", @@ -143,7 +143,7 @@ def test_log_record_dropped_attributes_set_limits_warning_once(self): with warnings.catch_warnings(record=True) as cw: for _ in range(10): - SDKLogRecord( + ReadWriteLogRecord( LogRecord( timestamp=0, body="a log line", @@ -162,7 +162,7 @@ def test_log_record_dropped_attributes_unset_limits(self): attr = {"key": "value", "key2": "value2"} limits = LogLimits() - result = SDKLogRecord( + result = ReadWriteLogRecord( LogRecord( timestamp=0, body="a log line", diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index 623ee03a73b..c35ba120e37 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -27,7 +27,7 @@ LoggerProvider, LoggingHandler, LogRecordProcessor, - SDKLogRecord, + ReadWriteLogRecord, SynchronousMultiLogRecordProcessor, ) @@ -38,11 +38,14 @@ def __init__(self, exporter, logs_list): self._log_list = logs_list self._closed = False - def on_emit(self, log_record: SDKLogRecord): + def on_emit(self, log_record: ReadWriteLogRecord): if self._closed: return self._log_list.append( - (log_record.log_record.body, log_record.log_record.severity_text) + ( + log_record.log_record.body, + log_record.log_record.severity_text, + ) ) def shutdown(self): @@ -105,7 +108,7 @@ def _get_multi_log_record_processor(self): pass def make_record(self): - return SDKLogRecord( + return ReadWriteLogRecord( LogRecord( timestamp=1622300111608942000, severity_text="WARN", diff --git a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py index 6bc7d1ea2cd..d5c0352abf9 100644 --- a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py +++ b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py @@ -30,7 +30,7 @@ LogRecord, ) from opentelemetry.sdk._logs import ( - SDKLogRecord, + ReadWriteLogRecord, ) from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, @@ -39,7 +39,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.util.instrumentation import InstrumentationScope -EMPTY_LOG = SDKLogRecord( +EMPTY_LOG = ReadWriteLogRecord( log_record=LogRecord(), instrumentation_scope=InstrumentationScope("example", "example"), ) From d1cd9e5fe0a9627988887b35b367a6d8204d73e9 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 12:01:35 -0700 Subject: [PATCH 13/28] Update --- .../sdk/_logs/_internal/export/__init__.py | 3 -- opentelemetry-sdk/tests/logs/test_logs.py | 36 ++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) 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 40a9e25d593..162ae9c3cde 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -26,10 +26,7 @@ detach, set_value, ) -from opentelemetry.sdk._shared_internal import BatchProcessor from opentelemetry.sdk._logs import ( - LogData, - LogRecord, LogRecordProcessor, ReadableLogRecord, ReadWriteLogRecord, diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 92daf4d40b3..8d456ca6e90 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -17,7 +17,8 @@ import unittest from unittest.mock import Mock, patch -from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry._logs import LogRecord, SeverityNumber +from opentelemetry.sdk._logs import LoggerProvider, ReadableLogRecord from opentelemetry.sdk._logs._internal import ( NoOpLogger, SynchronousMultiLogRecordProcessor, @@ -85,3 +86,36 @@ def test_logger_provider_init(self, resource_patch): ) ) self.assertIsNotNone(logger_provider._at_exit_handler) + + +class TestReadableLogRecord(unittest.TestCase): + def setUp(self): + self.log_record = LogRecord( + timestamp=1234567890, + observed_timestamp=1234567891, + body="Test log message", + attributes={"key": "value"}, + severity_number=SeverityNumber.INFO, + severity_text="INFO", + ) + self.resource = Resource({"service.name": "test-service"}) + self.readable_log_record = ReadableLogRecord( + log_record=self.log_record, + resource=self.resource, + instrumentation_scope=None, + ) + + def test_readable_log_record_is_frozen(self): + """Test that ReadableLogRecord is frozen and cannot be modified.""" + with self.assertRaises((AttributeError, TypeError)): + self.readable_log_record.log_record = LogRecord( + timestamp=999, + body="Modified" + ) + + def test_readable_log_record_can_read_attributes(self): + """Test that ReadableLogRecord provides read access to all fields.""" + self.assertEqual(self.readable_log_record.log_record.timestamp, 1234567890) + self.assertEqual(self.readable_log_record.log_record.body, "Test log message") + self.assertEqual(self.readable_log_record.log_record.attributes["key"], "value") + self.assertEqual(self.readable_log_record.resource.attributes["service.name"], "test-service") From 9387ff1f48612c1bc2b7018fd42c0e45a4fb2f56 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 12:41:48 -0700 Subject: [PATCH 14/28] Update tests --- .../common/_internal/_log_encoder/__init__.py | 24 +++++++++---------- opentelemetry-sdk/tests/logs/test_logs.py | 20 +++++++++++----- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index d584957efe4..aa777a7b117 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -42,29 +42,29 @@ def encode_logs( def _encode_log(readable_log_record: ReadableLogRecord) -> PB2LogRecord: span_id = ( None - if readable_log_record.span_id == 0 - else _encode_span_id(readable_log_record.span_id) + if readable_log_record.log_record.span_id == 0 + else _encode_span_id(readable_log_record.log_record.span_id) ) trace_id = ( None - if readable_log_record.trace_id == 0 - else _encode_trace_id(readable_log_record.trace_id) + if readable_log_record.log_record.trace_id == 0 + else _encode_trace_id(readable_log_record.log_record.trace_id) ) - body = readable_log_record.body + body = readable_log_record.log_record.body return PB2LogRecord( - time_unix_nano=readable_log_record.timestamp, - observed_time_unix_nano=readable_log_record.observed_timestamp, + time_unix_nano=readable_log_record.log_record.timestamp, + observed_time_unix_nano=readable_log_record.log_record.observed_timestamp, span_id=span_id, trace_id=trace_id, - flags=int(readable_log_record.trace_flags), + flags=int(readable_log_record.log_record.trace_flags), body=_encode_value(body, allow_null=True), - severity_text=readable_log_record.severity_text, + severity_text=readable_log_record.log_record.severity_text, attributes=_encode_attributes( - readable_log_record.attributes, allow_null=True + readable_log_record.log_record.attributes, allow_null=True ), dropped_attributes_count=readable_log_record.dropped_attributes, - severity_number=readable_log_record.severity_number.value, - event_name=readable_log_record.event_name, + severity_number=readable_log_record.log_record.severity_number.value, + event_name=readable_log_record.log_record.event_name, ) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 8d456ca6e90..0f9175f65fb 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -109,13 +109,21 @@ def test_readable_log_record_is_frozen(self): """Test that ReadableLogRecord is frozen and cannot be modified.""" with self.assertRaises((AttributeError, TypeError)): self.readable_log_record.log_record = LogRecord( - timestamp=999, - body="Modified" + timestamp=999, body="Modified" ) def test_readable_log_record_can_read_attributes(self): """Test that ReadableLogRecord provides read access to all fields.""" - self.assertEqual(self.readable_log_record.log_record.timestamp, 1234567890) - self.assertEqual(self.readable_log_record.log_record.body, "Test log message") - self.assertEqual(self.readable_log_record.log_record.attributes["key"], "value") - self.assertEqual(self.readable_log_record.resource.attributes["service.name"], "test-service") + self.assertEqual( + self.readable_log_record.log_record.timestamp, 1234567890 + ) + self.assertEqual( + self.readable_log_record.log_record.body, "Test log message" + ) + self.assertEqual( + self.readable_log_record.log_record.attributes["key"], "value" + ) + self.assertEqual( + self.readable_log_record.resource.attributes["service.name"], + "test-service", + ) From 940f642192627f0ab0028ff186db1affdc2786b1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 13:29:22 -0700 Subject: [PATCH 15/28] Add dropped_attributes in ReadableLogRecord, this is used to encode logs in OTLP --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index e93be0cfca1..71fbac65021 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -177,6 +177,12 @@ class ReadableLogRecord: resource: Resource instrumentation_scope: InstrumentationScope + @property + def dropped_attributes(self) -> int: + if isinstance(self.log_record.attributes, BoundedAttributes): + return self.log_record.attributes.dropped + return 0 + @dataclass class ReadWriteLogRecord: @@ -602,12 +608,12 @@ def emit(self, record: LogRecord): """Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope and forwarding to the processor. """ - sdk_log_record = ReadWriteLogRecord( + log_record = ReadWriteLogRecord( log_record=record, resource=self._resource, instrumentation_scope=self._instrumentation_scope, ) - self._multi_log_record_processor.on_emit(sdk_log_record) + self._multi_log_record_processor.on_emit(log_record) class LoggerProvider(APILoggerProvider): From dbef47bdac52238c407f8afaf95d5346a522d5fc Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 13:47:56 -0700 Subject: [PATCH 16/28] Convert to ReadWriteLogRecord before exporting --- .../sdk/_logs/_internal/__init__.py | 4 ++-- .../sdk/_logs/_internal/export/__init__.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 71fbac65021..328d9b6beb9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -175,7 +175,7 @@ class ReadableLogRecord: log_record: LogRecord resource: Resource - instrumentation_scope: InstrumentationScope + instrumentation_scope: InstrumentationScope | None = None @property def dropped_attributes(self) -> int: @@ -293,7 +293,7 @@ def shutdown(self): """Called when a :class:`opentelemetry.sdk._logs.Logger` is shutdown""" @abc.abstractmethod - def force_flush(self, timeout_millis: int = 30000): + def force_flush(self, timeout_millis: int = 30000) -> bool: """Export all the received logs to the configured Exporter that have not yet been exported. 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 162ae9c3cde..c8f4b5574ff 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -123,7 +123,13 @@ def on_emit(self, log_record: ReadWriteLogRecord): return token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True)) try: - self._exporter.export((log_record,)) + # Convert ReadWriteLogRecord to ReadableLogRecord before exporting + readable_log_record = ReadableLogRecord( + log_record=log_record.log_record, + resource=log_record.resource, + instrumentation_scope=log_record.instrumentation_scope, + ) + self._exporter.export((readable_log_record,)) except Exception: # pylint: disable=broad-exception-caught _logger.exception("Exception while exporting logs.") detach(token) @@ -191,7 +197,13 @@ def __init__( ) def on_emit(self, log_record: ReadWriteLogRecord) -> None: - return self._batch_processor.emit(log_record) + # Convert ReadWriteLogRecord to ReadableLogRecord before passing to BatchProcessor + readable_log_record = ReadableLogRecord( + log_record=log_record.log_record, + resource=log_record.resource, + instrumentation_scope=log_record.instrumentation_scope, + ) + return self._batch_processor.emit(readable_log_record) def shutdown(self): return self._batch_processor.shutdown() From 0e0b4513eaca53009a216a5b29243371953a1776 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 15:39:35 -0700 Subject: [PATCH 17/28] Update EventLogger --- .../src/opentelemetry/sdk/_events/__init__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 99672a49d12..4346fdb6694 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -24,7 +24,7 @@ SeverityNumber, get_logger_provider, ) -from opentelemetry.sdk._logs import Logger, LoggerProvider, ReadWriteLogRecord +from opentelemetry.sdk._logs import Logger, LoggerProvider from opentelemetry.util.types import _ExtendedAttributes _logger = logging.getLogger(__name__) @@ -53,18 +53,18 @@ def emit(self, event: Event) -> None: if isinstance(self._logger, NoOpLogger): # Do nothing if SDK is disabled return - log_record = ReadWriteLogRecord( - LogRecord( - timestamp=event.timestamp or time_ns(), - observed_timestamp=None, - severity_text=None, - severity_number=event.severity_number or SeverityNumber.INFO, - body=event.body, - attributes=event.attributes, - ), - resource=getattr(self._logger, "resource", None), + # Create an API LogRecord and pass it to the logger. The SDK Logger + # will wrap this into a ReadWriteLogRecord and attach resource and + # instrumentation scope. + api_log_record = LogRecord( + timestamp=event.timestamp or time_ns(), + observed_timestamp=None, + severity_text=None, + severity_number=event.severity_number or SeverityNumber.INFO, + body=event.body, + attributes=event.attributes, ) - self._logger.emit(log_record) + self._logger.emit(api_log_record) class EventLoggerProvider(APIEventLoggerProvider): From d7a467ad526375d0d4d701a7fd99a4de694f00e2 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 15:44:42 -0700 Subject: [PATCH 18/28] Update --- .../src/opentelemetry/sdk/_events/__init__.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 4346fdb6694..611d18da68a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -53,18 +53,10 @@ def emit(self, event: Event) -> None: if isinstance(self._logger, NoOpLogger): # Do nothing if SDK is disabled return - # Create an API LogRecord and pass it to the logger. The SDK Logger - # will wrap this into a ReadWriteLogRecord and attach resource and - # instrumentation scope. - api_log_record = LogRecord( - timestamp=event.timestamp or time_ns(), - observed_timestamp=None, - severity_text=None, - severity_number=event.severity_number or SeverityNumber.INFO, - body=event.body, - attributes=event.attributes, - ) - self._logger.emit(api_log_record) + # The Event is already an API LogRecord (it holds trace/span + # context and the event name). Forward it directly to the SDK + # logger so the original trace/span relationship is preserved. + self._logger.emit(event) class EventLoggerProvider(APIEventLoggerProvider): From 59554659527bff0627b412602915eb6cfbe98c0b Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 16:18:44 -0700 Subject: [PATCH 19/28] Fix events tests --- .../src/opentelemetry/sdk/_events/__init__.py | 19 ++++++++++++++---- opentelemetry-sdk/tests/events/test_events.py | 20 +++++++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 611d18da68a..83100c02ac2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -15,6 +15,7 @@ from time import time_ns from typing import Optional +from opentelemetry import trace from opentelemetry._events import Event from opentelemetry._events import EventLogger as APIEventLogger from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider @@ -53,10 +54,20 @@ def emit(self, event: Event) -> None: if isinstance(self._logger, NoOpLogger): # Do nothing if SDK is disabled return - # The Event is already an API LogRecord (it holds trace/span - # context and the event name). Forward it directly to the SDK - # logger so the original trace/span relationship is preserved. - self._logger.emit(event) + span_context = trace.get_current_span().get_span_context() + log_record = LogRecord( + timestamp=event.timestamp or time_ns(), + observed_timestamp=None, + trace_id=event.trace_id or span_context.trace_id, + span_id=event.span_id or span_context.span_id, + trace_flags=event.trace_flags or span_context.trace_flags, + severity_text=None, + severity_number=event.severity_number or SeverityNumber.INFO, + body=event.body, + resource=getattr(self._logger, "resource", None), + attributes=event.attributes, + ) + self._logger.emit(log_record) class EventLoggerProvider(APIEventLoggerProvider): diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index 83e03fdb140..7b8d42ff316 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -107,7 +107,7 @@ def test_event_logger(self, logger_mock): "name", "version", "schema_url", {"key": "value"} ) - @patch("opentelemetry.sdk._events.ReadWriteLogRecord") + @patch("opentelemetry.sdk._events.LogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit(self, logger_mock, log_record_mock): logger_provider = LoggerProvider() @@ -143,9 +143,25 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): log_record_mock_inst = Mock() log_record_mock.return_value = log_record_mock_inst event_logger.emit(event) + log_record_mock.assert_called_once_with( + timestamp=now, + observed_timestamp=None, + trace_id=trace_id, + span_id=span_id, + trace_flags=trace_flags, + severity_text=None, + severity_number=SeverityNumber.ERROR, + body="test body", + resource=event_logger._logger.resource, + attributes={ + "key": "val", + "foo": "bar", + "event.name": "test_event", + }, + ) logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst) - @patch("opentelemetry.sdk._events.ReadWriteLogRecord") + @patch("opentelemetry.sdk._events.LogRecord") @patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger") def test_event_logger_emit_sdk_disabled( self, logger_mock, log_record_mock From 9da84bcba17525d4c4a4e7e2da8b44d078ad04b1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 16:21:37 -0700 Subject: [PATCH 20/28] Update --- opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 83100c02ac2..7d1766d9b80 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -64,7 +64,6 @@ def emit(self, event: Event) -> None: severity_text=None, severity_number=event.severity_number or SeverityNumber.INFO, body=event.body, - resource=getattr(self._logger, "resource", None), attributes=event.attributes, ) self._logger.emit(log_record) From 34d2a1b0f918bc02ceea4422d0bb78133224c1b9 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Tue, 9 Sep 2025 16:27:58 -0700 Subject: [PATCH 21/28] Update event test --- opentelemetry-sdk/tests/events/test_events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opentelemetry-sdk/tests/events/test_events.py b/opentelemetry-sdk/tests/events/test_events.py index 7b8d42ff316..df770109b8c 100644 --- a/opentelemetry-sdk/tests/events/test_events.py +++ b/opentelemetry-sdk/tests/events/test_events.py @@ -152,7 +152,6 @@ def test_event_logger_emit(self, logger_mock, log_record_mock): severity_text=None, severity_number=SeverityNumber.ERROR, body="test body", - resource=event_logger._logger.resource, attributes={ "key": "val", "foo": "bar", From d7f58f0b2140a07078b196a1efb87cef2a999f1d Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Wed, 10 Sep 2025 11:06:54 -0700 Subject: [PATCH 22/28] Update after merge --- .../common/_internal/_log_encoder/__init__.py | 1 - .../sdk/_logs/_internal/__init__.py | 43 ++++++------- .../tests/logs/test_log_record.py | 63 +++++++------------ opentelemetry-sdk/tests/logs/test_logs.py | 6 +- 4 files changed, 44 insertions(+), 69 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index 913b672e69a..e04ee95fd38 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -66,7 +66,6 @@ def _encode_log(readable_log_record: ReadableLogRecord) -> PB2LogRecord: severity_number=getattr( readable_log_record.log_record.severity_number, "value", None ), - severity_number=readable_log_record.log_record.severity_number.value, event_name=readable_log_record.log_record.event_name, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 8749837475d..7714f7c1eff 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -247,7 +247,7 @@ def to_json(self, indent: int | None = 4) -> str: else None ), "dropped_attributes": self.dropped_attributes, - "timestamp": ns_to_iso_str(self.log_record.timestamp), + "timestamp": ns_to_iso_str(self.log_record.timestamp) if self.log_record.timestamp is not None else None, "observed_timestamp": ns_to_iso_str( @@ -265,7 +265,9 @@ def to_json(self, indent: int | None = 4) -> str: ), "trace_flags": self.log_record.trace_flags, "resource": json.loads(self.resource.to_json()), - "event_name": self.log_record.event_name or "", + "event_name": self.log_record.event_name + if self.log_record.event_name + else "", }, indent=indent, cls=BytesEncoder, @@ -279,21 +281,16 @@ def dropped_attributes(self) -> int: @classmethod def _from_api_log_record( - cls, *, record: APILogRecord, resource: Resource - ) -> LogRecord: + cls, + *, + record: LogRecord, + resource: Resource, + instrumentation_scope: InstrumentationScope | None = None, + ) -> ReadWriteLogRecord: return cls( - timestamp=record.timestamp, - observed_timestamp=record.observed_timestamp, - context=record.context, - trace_id=record.trace_id, - span_id=record.span_id, - trace_flags=record.trace_flags, - severity_text=record.severity_text, - severity_number=record.severity_number, - body=record.body, - attributes=record.attributes, - event_name=record.event_name, + log_record=record, resource=resource, + instrumentation_scope=instrumentation_scope, ) @@ -631,16 +628,12 @@ def emit(self, record: LogRecord): """ if not isinstance(record, ReadWriteLogRecord): # pylint:disable=protected-access - record = LogRecord._from_api_log_record( - record=record, resource=self._resource - ) - - log_record = ReadWriteLogRecord( - log_record=record, - resource=self._resource, - instrumentation_scope=self._instrumentation_scope, - ) - self._multi_log_record_processor.on_emit(log_record) + record = ReadWriteLogRecord._from_api_log_record( + record=record, + resource=self._resource, + instrumentation_scope=self._instrumentation_scope, + ) + self._multi_log_record_processor.on_emit(record) class LoggerProvider(APILoggerProvider): diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index d4cf5679a87..4bfe24d769f 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -18,12 +18,14 @@ from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.attributes import BoundedAttributes +from opentelemetry.context import get_current from opentelemetry.sdk._logs import ( LogDroppedAttributesWarning, LogLimits, ReadWriteLogRecord, ) from opentelemetry.sdk.resources import Resource +from opentelemetry.trace.span import TraceFlags class TestLogRecord(unittest.TestCase): @@ -64,9 +66,11 @@ def test_log_record_to_json_serializes_severity_number_as_int(self): self.assertEqual(SeverityNumber.WARN.value, decoded["severity_number"]) def test_log_record_to_json_serializes_null_severity_number(self): - actual = LogRecord( - observed_timestamp=0, - body="a log line", + actual = ReadWriteLogRecord( + LogRecord( + observed_timestamp=0, + body="a log line", + ), resource=Resource({"service.name": "foo"}), ) @@ -183,34 +187,9 @@ def test_log_record_dropped_attributes_unset_limits(self): self.assertTrue(result.dropped_attributes == 0) self.assertEqual(attr, result.log_record.attributes) - def test_log_record_deprecated_init_warning(self): - test_cases = [ - {"trace_id": 123}, - {"span_id": 123}, - {"trace_flags": TraceFlags(0x01)}, - ] - - for params in test_cases: - with self.subTest(params=params): - with warnings.catch_warnings(record=True) as cw: - for _ in range(10): - LogRecord(**params) - - self.assertEqual(len(cw), 1) - self.assertIsInstance(cw[-1].message, LogDeprecatedInitWarning) - self.assertIn( - "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead.", - str(cw[-1].message), - ) - - with warnings.catch_warnings(record=True) as cw: - for _ in range(10): - LogRecord(context=get_current()) - self.assertEqual(len(cw), 0) - # pylint:disable=protected-access def test_log_record_from_api_log_record(self): - api_log_record = APILogRecord( + api_log_record = LogRecord( timestamp=1, observed_timestamp=2, context=get_current(), @@ -225,19 +204,21 @@ def test_log_record_from_api_log_record(self): ) resource = Resource.create({}) - record = LogRecord._from_api_log_record( + record = ReadWriteLogRecord._from_api_log_record( record=api_log_record, resource=resource ) - self.assertEqual(record.timestamp, 1) - self.assertEqual(record.observed_timestamp, 2) - self.assertEqual(record.context, get_current()) - self.assertEqual(record.trace_id, 123) - self.assertEqual(record.span_id, 456) - self.assertEqual(record.trace_flags, TraceFlags(0x01)) - self.assertEqual(record.severity_text, "WARN") - self.assertEqual(record.severity_number, SeverityNumber.WARN) - self.assertEqual(record.body, "a log line") - self.assertEqual(record.attributes, {"a": "b"}) - self.assertEqual(record.event_name, "an.event") + self.assertEqual(record.log_record.timestamp, 1) + self.assertEqual(record.log_record.observed_timestamp, 2) + self.assertEqual(record.log_record.context, get_current()) + self.assertEqual(record.log_record.trace_id, 123) + self.assertEqual(record.log_record.span_id, 456) + self.assertEqual(record.log_record.trace_flags, TraceFlags(0x01)) + self.assertEqual(record.log_record.severity_text, "WARN") + self.assertEqual( + record.log_record.severity_number, SeverityNumber.WARN + ) + self.assertEqual(record.log_record.body, "a log line") + self.assertEqual(record.log_record.attributes, {"a": "b"}) + self.assertEqual(record.log_record.event_name, "an.event") self.assertEqual(record.resource, resource) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 6719b17e670..a97cc515efd 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -18,7 +18,7 @@ from unittest.mock import Mock, patch from opentelemetry._logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs import LoggerProvider, ReadableLogRecord +from opentelemetry.sdk._logs import Logger, LoggerProvider, ReadableLogRecord from opentelemetry.sdk._logs._internal import ( NoOpLogger, SynchronousMultiLogRecordProcessor, @@ -88,6 +88,7 @@ def test_logger_provider_init(self, resource_patch): ) self.assertIsNotNone(logger_provider._at_exit_handler) + class TestReadableLogRecord(unittest.TestCase): def setUp(self): self.log_record = LogRecord( @@ -128,6 +129,7 @@ def test_readable_log_record_can_read_attributes(self): "test-service", ) + class TestLogger(unittest.TestCase): @staticmethod def _get_logger(): @@ -158,7 +160,7 @@ def test_can_emit_logrecord(self): def test_can_emit_api_logrecord(self): logger, log_record_processor_mock = self._get_logger() - api_log_record = APILogRecord( + api_log_record = LogRecord( observed_timestamp=0, body="a log line", ) From 4ca4db899b813e07fd9e32439401865662d468f6 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Wed, 10 Sep 2025 11:24:51 -0700 Subject: [PATCH 23/28] Update otlp common test --- .../tests/test_log_encoder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 4a589c36aa5..06ff06a9e02 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -315,13 +315,13 @@ def _get_sdk_log_data() -> List[ReadWriteLogRecord]: ) ) ) - log9 = LogData( - log_record=SDKLogRecord( + log9 = ReadWriteLogRecord( + LogRecord( # these are otherwise set by default observed_timestamp=1644650584292683045, context=ctx_log9, - resource=SDKResource({}), ), + resource=SDKResource({}), instrumentation_scope=InstrumentationScope( "empty_log_record_name", "empty_log_record_version" ), From 7eb9f58d554cf672034a329e3deb954e90f89be1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Thu, 11 Sep 2025 14:07:14 -0700 Subject: [PATCH 24/28] Update --- .../sdk/_logs/_internal/__init__.py | 38 ++-- opentelemetry-sdk/tests/logs/test_handler.py | 175 ++++++++---------- 2 files changed, 87 insertions(+), 126 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 76add9b99f3..ef7ee226879 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -158,12 +158,6 @@ def _from_env_if_absent( return value -_UnsetLogLimits = LogLimits( - max_attributes=LogLimits.UNSET, - max_attribute_length=LogLimits.UNSET, -) - - @dataclass(frozen=True) class ReadableLogRecord: """Readable LogRecord should be kept exactly in-sync with ReadWriteLogRecord, only difference is the frozen=True param.""" @@ -188,35 +182,23 @@ class ReadWriteLogRecord: pertinent to the event being logged. """ - @overload - def __init__( - self, - log_record: LogRecord, - resource: Resource | None = None, - - limits: LogLimits | None = _UnsetLogLimits, - ): ... + log_record: LogRecord + resource: Resource | None = Resource.create({}) + instrumentation_scope: InstrumentationScope | None = None + limits: LogLimits | None = None - def __init__( - self, - log_record: LogRecord, - resource: Resource | None = None, - limits: LogLimits | None = _UnsetLogLimits, - instrumentation_scope: InstrumentationScope | None = None, - ): - self.log_record = log_record + def __post_init__(self): + if self.limits is None: + self.limits = LogLimits() self.log_record.attributes = BoundedAttributes( - maxlen=limits.max_attributes, + maxlen=self.limits.max_attributes, attributes=self.log_record.attributes if self.log_record.attributes else None, immutable=False, - max_value_len=limits.max_attribute_length, + max_value_len=self.limits.max_attribute_length, extended_attributes=True, ) - - self.resource = resource if resource else Resource.create({}) - self.instrumentation_scope = instrumentation_scope if self.dropped_attributes > 0: warnings.warn( "Log record attributes were dropped due to limits", @@ -282,11 +264,13 @@ def _from_api_log_record( record: LogRecord, resource: Resource, instrumentation_scope: InstrumentationScope | None = None, + limits: LogLimits | None = None, ) -> ReadWriteLogRecord: return cls( log_record=record, resource=resource, instrumentation_scope=instrumentation_scope, + limits=limits, ) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 46644244d35..c5db462bb9d 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -101,17 +101,17 @@ def test_log_record_no_span_context(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) + self.assertIsNotNone(record) self.assertEqual( - sdk_log_record.log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id + record.log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id ) self.assertEqual( - sdk_log_record.log_record.span_id, INVALID_SPAN_CONTEXT.span_id + record.log_record.span_id, INVALID_SPAN_CONTEXT.span_id ) self.assertEqual( - sdk_log_record.log_record.trace_flags, + record.log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags, ) @@ -121,8 +121,8 @@ def test_log_record_observed_timestamp(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message") - sdk_log_record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record.log_record.observed_timestamp) + record = processor.get_log_record(0) + self.assertIsNotNone(record.log_record.observed_timestamp) def test_log_record_user_attributes(self): """Attributes can be injected into logs by adding them to the ReadWriteLogRecord""" @@ -132,32 +132,27 @@ def test_log_record_user_attributes(self): with self.assertLogs(level=logging.WARNING): logger.warning("Warning message", extra={"http.status_code": 200}) - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) - self.assertEqual(len(sdk_log_record.log_record.attributes), 4) - self.assertEqual( - sdk_log_record.log_record.attributes["http.status_code"], 200 - ) + self.assertIsNotNone(record) + self.assertEqual(len(record.log_record.attributes), 4) + self.assertEqual(record.log_record.attributes["http.status_code"], 200) self.assertTrue( - sdk_log_record.log_record.attributes[ + record.log_record.attributes[ code_attributes.CODE_FILE_PATH ].endswith("test_handler.py") ) self.assertEqual( - sdk_log_record.log_record.attributes[ - code_attributes.CODE_FUNCTION_NAME - ], + record.log_record.attributes[code_attributes.CODE_FUNCTION_NAME], "test_log_record_user_attributes", ) # The line of the log statement is not a constant (changing tests may change that), # so only check that the attribute is present. self.assertTrue( - code_attributes.CODE_LINE_NUMBER - in sdk_log_record.log_record.attributes + code_attributes.CODE_LINE_NUMBER in record.log_record.attributes ) self.assertTrue( - isinstance(sdk_log_record.log_record.attributes, BoundedAttributes) + isinstance(record.log_record.attributes, BoundedAttributes) ) def test_log_record_exception(self): @@ -170,24 +165,22 @@ def test_log_record_exception(self): with self.assertLogs(level=logging.ERROR): logger.exception("Zero Division Error") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) - self.assertTrue(isinstance(sdk_log_record.log_record.body, str)) - self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") + self.assertIsNotNone(record) + self.assertTrue(isinstance(record.log_record.body, str)) + self.assertEqual(record.log_record.body, "Zero Division Error") self.assertEqual( - sdk_log_record.log_record.attributes[ - exception_attributes.EXCEPTION_TYPE - ], + record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], ZeroDivisionError.__name__, ) self.assertEqual( - sdk_log_record.log_record.attributes[ + record.log_record.attributes[ exception_attributes.EXCEPTION_MESSAGE ], "division by zero", ) - stack_trace = sdk_log_record.log_record.attributes[ + stack_trace = record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -208,23 +201,21 @@ def test_log_record_recursive_exception(self): with self.assertLogs(level=logging.ERROR): logger.exception("Zero Division Error") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) - self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") + self.assertIsNotNone(record) + self.assertEqual(record.log_record.body, "Zero Division Error") self.assertEqual( - sdk_log_record.log_record.attributes[ - exception_attributes.EXCEPTION_TYPE - ], + record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], ZeroDivisionError.__name__, ) self.assertEqual( - sdk_log_record.log_record.attributes[ + record.log_record.attributes[ exception_attributes.EXCEPTION_MESSAGE ], "division by zero", ) - stack_trace = sdk_log_record.log_record.attributes[ + stack_trace = record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -243,21 +234,21 @@ def test_log_exc_info_false(self): with self.assertLogs(level=logging.ERROR): logger.error("Zero Division Error", exc_info=False) - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) - self.assertEqual(sdk_log_record.log_record.body, "Zero Division Error") + self.assertIsNotNone(record) + self.assertEqual(record.log_record.body, "Zero Division Error") self.assertNotIn( exception_attributes.EXCEPTION_TYPE, - sdk_log_record.log_record.attributes, + record.log_record.attributes, ) self.assertNotIn( exception_attributes.EXCEPTION_MESSAGE, - sdk_log_record.log_record.attributes, + record.log_record.attributes, ) self.assertNotIn( exception_attributes.EXCEPTION_STACKTRACE, - sdk_log_record.log_record.attributes, + record.log_record.attributes, ) def test_log_record_exception_with_object_payload(self): @@ -273,26 +264,22 @@ def __str__(self): with self.assertLogs(level=logging.ERROR): logger.exception(exception) - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) - self.assertIsNotNone(sdk_log_record) - self.assertTrue(isinstance(sdk_log_record.log_record.body, str)) + self.assertIsNotNone(record) + self.assertTrue(isinstance(record.log_record.body, str)) + self.assertEqual(record.log_record.body, "CustomException stringified") self.assertEqual( - sdk_log_record.log_record.body, "CustomException stringified" - ) - self.assertEqual( - sdk_log_record.log_record.attributes[ - exception_attributes.EXCEPTION_TYPE - ], + record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], CustomException.__name__, ) self.assertEqual( - sdk_log_record.log_record.attributes[ + record.log_record.attributes[ exception_attributes.EXCEPTION_MESSAGE ], "CustomException message", ) - stack_trace = sdk_log_record.log_record.attributes[ + stack_trace = record.log_record.attributes[ exception_attributes.EXCEPTION_STACKTRACE ] self.assertIsInstance(stack_trace, str) @@ -314,31 +301,27 @@ def test_log_record_trace_correlation(self): with self.assertLogs(level=logging.CRITICAL): logger.critical("Critical message within span") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) self.assertEqual( - sdk_log_record.log_record.body, + record.log_record.body, "Critical message within span", ) + self.assertEqual(record.log_record.severity_text, "CRITICAL") self.assertEqual( - sdk_log_record.log_record.severity_text, "CRITICAL" - ) - self.assertEqual( - sdk_log_record.log_record.severity_number, + record.log_record.severity_number, SeverityNumber.FATAL, ) - self.assertEqual( - sdk_log_record.log_record.context, mock_context - ) + self.assertEqual(record.log_record.context, mock_context) span_context = span.get_span_context() self.assertEqual( - sdk_log_record.log_record.trace_id, span_context.trace_id + record.log_record.trace_id, span_context.trace_id ) self.assertEqual( - sdk_log_record.log_record.span_id, span_context.span_id + record.log_record.span_id, span_context.span_id ) self.assertEqual( - sdk_log_record.log_record.trace_flags, + record.log_record.trace_flags, span_context.trace_flags, ) @@ -350,41 +333,35 @@ def test_log_record_trace_correlation_deprecated(self): with self.assertLogs(level=logging.CRITICAL): logger.critical("Critical message within span") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) self.assertEqual( - sdk_log_record.log_record.body, "Critical message within span" - ) - self.assertEqual( - sdk_log_record.log_record.severity_text, "CRITICAL" + record.log_record.body, "Critical message within span" ) + self.assertEqual(record.log_record.severity_text, "CRITICAL") self.assertEqual( - sdk_log_record.log_record.severity_number, SeverityNumber.FATAL + record.log_record.severity_number, SeverityNumber.FATAL ) span_context = span.get_span_context() + self.assertEqual(record.log_record.trace_id, span_context.trace_id) + self.assertEqual(record.log_record.span_id, span_context.span_id) self.assertEqual( - sdk_log_record.log_record.trace_id, span_context.trace_id - ) - self.assertEqual( - sdk_log_record.log_record.span_id, span_context.span_id - ) - self.assertEqual( - sdk_log_record.log_record.trace_flags, span_context.trace_flags + record.log_record.trace_flags, span_context.trace_flags ) def test_warning_without_formatter(self): processor, logger = set_up_test_logging(logging.WARNING) logger.warning("Test message") - sdk_log_record = processor.get_log_record(0) - self.assertEqual(sdk_log_record.log_record.body, "Test message") + record = processor.get_log_record(0) + self.assertEqual(record.log_record.body, "Test message") def test_exception_without_formatter(self): processor, logger = set_up_test_logging(logging.WARNING) logger.exception("Test exception") - sdk_log_record = processor.get_log_record(0) - self.assertEqual(sdk_log_record.log_record.body, "Test exception") + record = processor.get_log_record(0) + self.assertEqual(record.log_record.body, "Test exception") def test_warning_with_formatter(self): processor, logger = set_up_test_logging( @@ -395,9 +372,9 @@ def test_warning_with_formatter(self): ) logger.warning("Test message") - sdk_log_record = processor.get_log_record(0) + record = processor.get_log_record(0) self.assertEqual( - sdk_log_record.log_record.body, "foo - WARNING - Test message" + record.log_record.body, "foo - WARNING - Test message" ) def test_log_body_is_always_string_with_formatter(self): @@ -409,8 +386,8 @@ def test_log_body_is_always_string_with_formatter(self): ) logger.warning(["something", "of", "note"]) - sdk_log_record = processor.get_log_record(0) - self.assertIsInstance(sdk_log_record.log_record.body, str) + record = processor.get_log_record(0) + self.assertIsInstance(record.log_record.body, str) @patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"}) def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error( @@ -445,10 +422,10 @@ def test_otel_attribute_count_limit_respected_in_logging_handler(self): "Test message with many attributes", extra=extra_attrs ) - log_record = processor.get_log_record(0) + record = processor.get_log_record(0) # With OTEL_ATTRIBUTE_COUNT_LIMIT=3, should have exactly 3 attributes - total_attrs = len(log_record.attributes) + total_attrs = len(record.log_record.attributes) self.assertEqual( total_attrs, 3, @@ -457,9 +434,9 @@ def test_otel_attribute_count_limit_respected_in_logging_handler(self): # Should have 10 dropped attributes (10 custom + 3 code - 3 kept = 10 dropped) self.assertEqual( - log_record.dropped_attributes, + record.dropped_attributes, 10, - f"Should have 10 dropped attributes, got {log_record.dropped_attributes}", + f"Should have 10 dropped attributes, got {record.dropped_attributes}", ) @patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "5"}) @@ -482,10 +459,10 @@ def test_otel_attribute_count_limit_includes_code_attributes(self): with self.assertLogs(level=logging.WARNING): logger.warning("Test message", extra=extra_attrs) - log_record = processor.get_log_record(0) + record = processor.get_log_record(0) # With OTEL_ATTRIBUTE_COUNT_LIMIT=5, should have exactly 5 attributes - total_attrs = len(log_record.attributes) + total_attrs = len(record.log_record.attributes) self.assertEqual( total_attrs, 5, @@ -494,9 +471,9 @@ def test_otel_attribute_count_limit_includes_code_attributes(self): # Should have 6 dropped attributes (8 user + 3 code - 5 kept = 6 dropped) self.assertEqual( - log_record.dropped_attributes, + record.dropped_attributes, 6, - f"Should have 6 dropped attributes, got {log_record.dropped_attributes}", + f"Should have 6 dropped attributes, got {record.dropped_attributes}", ) def test_logging_handler_without_env_var_uses_default_limit(self): @@ -511,10 +488,10 @@ def test_logging_handler_without_env_var_uses_default_limit(self): "Test message with many attributes", extra=extra_attrs ) - log_record = processor.get_log_record(0) + record = processor.get_log_record(0) # Should be limited to default limit (128) total attributes - total_attrs = len(log_record.attributes) + total_attrs = len(record.log_record.attributes) self.assertEqual( total_attrs, 128, @@ -523,9 +500,9 @@ def test_logging_handler_without_env_var_uses_default_limit(self): # Should have 25 dropped attributes (150 user + 3 code - 128 kept = 25 dropped) self.assertEqual( - log_record.dropped_attributes, + record.dropped_attributes, 25, - f"Should have 25 dropped attributes, got {log_record.dropped_attributes}", + f"Should have 25 dropped attributes, got {record.dropped_attributes}", ) From 53ecfa52d958be46acd8235586a78f546c51a39f Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Fri, 12 Sep 2025 13:15:47 -0700 Subject: [PATCH 25/28] Address comments --- .../src/opentelemetry/sdk/_logs/_internal/__init__.py | 9 +++------ .../opentelemetry/sdk/_logs/_internal/export/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index ef7ee226879..2667d2e464b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -22,7 +22,7 @@ import threading import traceback import warnings -from dataclasses import dataclass +from dataclasses import dataclass, field from os import environ from threading import Lock from time import time_ns @@ -165,6 +165,7 @@ class ReadableLogRecord: log_record: LogRecord resource: Resource instrumentation_scope: InstrumentationScope | None = None + limits: LogLimits | None = None @property def dropped_attributes(self) -> int: @@ -185,11 +186,9 @@ class ReadWriteLogRecord: log_record: LogRecord resource: Resource | None = Resource.create({}) instrumentation_scope: InstrumentationScope | None = None - limits: LogLimits | None = None + limits: LogLimits = field(default_factory=LogLimits) def __post_init__(self): - if self.limits is None: - self.limits = LogLimits() self.log_record.attributes = BoundedAttributes( maxlen=self.limits.max_attributes, attributes=self.log_record.attributes @@ -264,13 +263,11 @@ def _from_api_log_record( record: LogRecord, resource: Resource, instrumentation_scope: InstrumentationScope | None = None, - limits: LogLimits | None = None, ) -> ReadWriteLogRecord: return cls( log_record=record, resource=resource, instrumentation_scope=instrumentation_scope, - limits=limits, ) 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 c8f4b5574ff..bb0ffcafd88 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -128,6 +128,7 @@ def on_emit(self, log_record: ReadWriteLogRecord): log_record=log_record.log_record, resource=log_record.resource, instrumentation_scope=log_record.instrumentation_scope, + limits=log_record.limits, ) self._exporter.export((readable_log_record,)) except Exception: # pylint: disable=broad-exception-caught @@ -202,6 +203,7 @@ def on_emit(self, log_record: ReadWriteLogRecord) -> None: log_record=log_record.log_record, resource=log_record.resource, instrumentation_scope=log_record.instrumentation_scope, + limits=log_record.limits, ) return self._batch_processor.emit(readable_log_record) From 8b6efb86f7f98770fc490551709386a60016bbba Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Fri, 17 Oct 2025 16:19:22 -0700 Subject: [PATCH 26/28] Update --- CHANGELOG.md | 4 +- .../src/opentelemetry/sdk/_events/__init__.py | 28 +++++-------- .../src/opentelemetry/sdk/_logs/__init__.py | 5 ++- .../sdk/_logs/_internal/__init__.py | 36 ++++++---------- .../sdk/_logs/_internal/export/__init__.py | 2 +- .../tests/logs/test_log_record.py | 41 ++----------------- opentelemetry-sdk/tests/logs/test_logs.py | 34 +++++++-------- 7 files changed, 52 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6341a400f78..a5d201f663a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - docs: Added sqlcommenter example ([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734)) +- [BREAKING] Remove LogData and extend SDK LogRecord to have instrumentation scope + ([#4676](https://github.com/open-telemetry/opentelemetry-python/pull/4676)) ## Version 1.38.0/0.59b0 (2025-10-16) @@ -95,8 +97,6 @@ can cause a deadlock to occur over `logging._lock` in some cases ([#4636](https: ([#4669](https://github.com/open-telemetry/opentelemetry-python/pull/4669)) - Set expected User-Agent in HTTP headers for grpc OTLP exporter ([#4658](https://github.com/open-telemetry/opentelemetry-python/pull/4658)) -- Remove LogData and extend SDK LogRecord to have instrumentation scope - ([#4676](https://github.com/open-telemetry/opentelemetry-python/pull/4676)) ## Version 1.34.0/0.55b0 (2025-06-04) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 0a88936ea11..0c643f28982 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -23,7 +23,6 @@ from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider from opentelemetry.sdk._logs import ( - LogDeprecatedInitWarning, Logger, LoggerProvider, LogRecord, @@ -58,22 +57,17 @@ def emit(self, event: Event) -> None: return span_context = trace.get_current_span().get_span_context() - # silence deprecation warnings from internal users - with warnings.catch_warnings(): - warnings.simplefilter("ignore", category=LogDeprecatedInitWarning) - - log_record = LogRecord( - timestamp=event.timestamp or time_ns(), - observed_timestamp=None, - trace_id=event.trace_id or span_context.trace_id, - span_id=event.span_id or span_context.span_id, - trace_flags=event.trace_flags or span_context.trace_flags, - severity_text=None, - severity_number=event.severity_number or SeverityNumber.INFO, - body=event.body, - resource=getattr(self._logger, "resource", None), - attributes=event.attributes, - ) + log_record = LogRecord( + timestamp=event.timestamp or time_ns(), + observed_timestamp=None, + trace_id=event.trace_id or span_context.trace_id, + span_id=event.span_id or span_context.span_id, + trace_flags=event.trace_flags or span_context.trace_flags, + severity_text=None, + severity_number=event.severity_number or SeverityNumber.INFO, + body=event.body, + attributes=event.attributes, + ) self._logger.emit(log_record) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index 763655e553e..c06dd69200d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. - +from opentelemetry._logs import LogRecord from opentelemetry.sdk._logs._internal import ( LogDroppedAttributesWarning, Logger, @@ -25,12 +25,13 @@ ) __all__ = [ + "LogDroppedAttributesWarning", "Logger", "LoggerProvider", "LoggingHandler", "LogLimits", + "LogRecord", "LogRecordProcessor", - "LogDroppedAttributesWarning", "ReadableLogRecord", "ReadWriteLogRecord", ] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index e1c23b2e7e9..a12de66f460 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -78,18 +78,6 @@ class LogDroppedAttributesWarning(UserWarning): warnings.simplefilter("once", LogDroppedAttributesWarning) -class LogDeprecatedInitWarning(UserWarning): - """Custom warning to indicate that deprecated and soon to be deprecated Log classes was used. - - This class is used to filter and handle these specific warnings separately - from other warnings, ensuring that they are only shown once without - interfering with default user warnings. - """ - - -warnings.simplefilter("once", LogDeprecatedInitWarning) - - class LogLimits: """This class is based on a SpanLimits class in the Tracing module. @@ -281,6 +269,7 @@ def _from_api_log_record( instrumentation_scope=instrumentation_scope, ) + class LogRecordProcessor(abc.ABC): """Interface to hook the log record emitting action. @@ -518,7 +507,7 @@ def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes: ) return attributes - def _translate(self, record: logging.LogRecord) -> dict: + def _translate(self, record: logging.LogRecord) -> LogRecord: timestamp = int(record.created * 1e9) observered_timestamp = time_ns() attributes = self._get_attributes(record) @@ -552,15 +541,15 @@ def _translate(self, record: logging.LogRecord) -> dict: "WARN" if record.levelname == "WARNING" else record.levelname ) - return { - "timestamp": timestamp, - "observed_timestamp": observered_timestamp, - "context": get_current() or None, - "severity_text": level_name, - "severity_number": severity_number, - "body": body, - "attributes": attributes, - } + return LogRecord( + timestamp=timestamp, + observed_timestamp=observered_timestamp, + context=get_current() or None, + severity_text=level_name, + severity_number=severity_number, + body=body, + attributes=attributes, + ) def emit(self, record: logging.LogRecord) -> None: """ @@ -570,7 +559,7 @@ def emit(self, record: logging.LogRecord) -> None: """ logger = get_logger(record.name, logger_provider=self._logger_provider) if not isinstance(logger, NoOpLogger): - logger.emit(**self._translate(record)) + logger.emit(self._translate(record)) def flush(self) -> None: """ @@ -609,6 +598,7 @@ def __init__( def resource(self): return self._resource + # pylint: disable=arguments-differ def emit(self, record: LogRecord): """Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope and forwarding to the processor. 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 d42db7d71c4..1367e879465 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -27,7 +27,7 @@ set_value, ) from opentelemetry.sdk._logs import ( - LogRecord + LogRecord, LogRecordProcessor, ReadableLogRecord, ReadWriteLogRecord, diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index 0ff86423b05..fa5a4f85204 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -25,7 +25,6 @@ ReadWriteLogRecord, ) from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace.span import TraceFlags @@ -205,9 +204,6 @@ def test_log_record_from_api_log_record(self): timestamp=1, observed_timestamp=2, context=get_current(), - trace_id=123, - span_id=456, - trace_flags=TraceFlags(0x01), severity_text="WARN", severity_number=SeverityNumber.WARN, body="a log line", @@ -223,9 +219,10 @@ 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()) - self.assertEqual(record.log_record.trace_id, 123) - self.assertEqual(record.log_record.span_id, 456) - self.assertEqual(record.log_record.trace_flags, TraceFlags(0x01)) + # 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) + self.assertEqual(record.log_record.trace_flags, TraceFlags(0x00)) self.assertEqual(record.log_record.severity_text, "WARN") self.assertEqual( record.log_record.severity_number, SeverityNumber.WARN @@ -234,33 +231,3 @@ def test_log_record_from_api_log_record(self): self.assertEqual(record.log_record.attributes, {"a": "b"}) self.assertEqual(record.log_record.event_name, "an.event") self.assertEqual(record.resource, resource) - - -class TestLogData(unittest.TestCase): - def test_init_deprecated_warning(self): - """Test that LogData initialization emits a LogDeprecatedInitWarning.""" - log_record = LogRecord() - - with warnings.catch_warnings(record=True) as cw: - warnings.simplefilter("always") - LogData( - log_record=log_record, - instrumentation_scope=InstrumentationScope("foo", "bar"), - ) - - # Check that at least one LogDeprecatedInitWarning was emitted - init_warnings = [ - w for w in cw if isinstance(w.message, LogDeprecatedInitWarning) - ] - self.assertGreater( - len(init_warnings), - 0, - "Expected at least one LogDeprecatedInitWarning", - ) - - # Check the message content of the LogDeprecatedInitWarning - warning_message = str(init_warnings[0].message) - self.assertIn( - "LogData will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord", - warning_message, - ) diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index 582bfe07ec8..70811260ae4 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -17,8 +17,7 @@ import unittest from unittest.mock import Mock, patch -from opentelemetry._logs import LogRecord as APILogRecord -from opentelemetry._logs import SeverityNumber +from opentelemetry._logs import LogRecord, SeverityNumber from opentelemetry.context import get_current from opentelemetry.sdk._logs import ( Logger, @@ -178,18 +177,18 @@ 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.assertEqual(log_record.context, {}) + 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") self.assertEqual(log_record.attributes, {}) self.assertEqual(log_record.event_name, None) - self.assertEqual(log_record.resource, logger.resource) + self.assertEqual(log_data.resource, logger.resource) def test_can_emit_with_keywords_arguments(self): logger, log_record_processor_mock = self._get_logger() - logger.emit( + log_record = LogRecord( timestamp=100, observed_timestamp=101, context=get_current(), @@ -199,16 +198,19 @@ def test_can_emit_with_keywords_arguments(self): attributes={"some": "attributes"}, event_name="event_name", ) + logger.emit(log_record) log_record_processor_mock.on_emit.assert_called_once() log_data = log_record_processor_mock.on_emit.call_args.args[0] - log_record = log_data.log_record - self.assertTrue(isinstance(log_record, LogRecord)) - self.assertEqual(log_record.timestamp, 100) - self.assertEqual(log_record.observed_timestamp, 101) - self.assertEqual(log_record.context, {}) - self.assertEqual(log_record.severity_number, SeverityNumber.WARN) - self.assertEqual(log_record.severity_text, "warn") - self.assertEqual(log_record.body, "a body") - self.assertEqual(log_record.attributes, {"some": "attributes"}) - self.assertEqual(log_record.event_name, "event_name") - self.assertEqual(log_record.resource, logger.resource) + result_log_record = log_data.log_record + 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 + ) + self.assertEqual(result_log_record.severity_text, "warn") + self.assertEqual(result_log_record.body, "a body") + self.assertEqual(result_log_record.attributes, {"some": "attributes"}) + self.assertEqual(result_log_record.event_name, "event_name") + self.assertEqual(log_data.resource, logger.resource) From 7ff66d142e0817df34ace1900c44a46d86865852 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Fri, 17 Oct 2025 16:35:23 -0700 Subject: [PATCH 27/28] Update --- .../src/opentelemetry/exporter/otlp/proto/grpc/exporter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py index 47e99f72291..90b0b7a284b 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py @@ -80,7 +80,7 @@ KeyValue, ) from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401 -from opentelemetry.sdk._logs import LogData +from opentelemetry.sdk._logs import LogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( @@ -118,7 +118,7 @@ logger.addFilter(DuplicateFilter()) SDKDataT = TypeVar( "SDKDataT", - TypingSequence[LogData], + TypingSequence[LogRecord], MetricsData, TypingSequence[ReadableSpan], ) From 49540d26de6dfaffb52f71a169450ec2ada9fd77 Mon Sep 17 00:00:00 2001 From: Hector Hernandez Date: Fri, 17 Oct 2025 16:45:25 -0700 Subject: [PATCH 28/28] Update --- .../opentelemetry/exporter/otlp/proto/grpc/exporter.py | 4 ++-- .../src/opentelemetry/sdk/_events/__init__.py | 10 +++++----- .../src/opentelemetry/sdk/_logs/__init__.py | 2 -- .../sdk/_logs/_internal/export/__init__.py | 1 - 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py index 90b0b7a284b..461ea0aee74 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py @@ -80,7 +80,7 @@ KeyValue, ) from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401 -from opentelemetry.sdk._logs import LogRecord +from opentelemetry.sdk._logs import ReadableLogRecord from opentelemetry.sdk._logs.export import LogExportResult from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( @@ -118,7 +118,7 @@ logger.addFilter(DuplicateFilter()) SDKDataT = TypeVar( "SDKDataT", - TypingSequence[LogRecord], + TypingSequence[ReadableLogRecord], MetricsData, TypingSequence[ReadableSpan], ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py index 0c643f28982..12ddb48c757 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py @@ -13,7 +13,6 @@ # limitations under the License. import logging -import warnings from time import time_ns from typing import Optional @@ -21,12 +20,13 @@ from opentelemetry._events import Event from opentelemetry._events import EventLogger as APIEventLogger from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider -from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider -from opentelemetry.sdk._logs import ( - Logger, - LoggerProvider, +from opentelemetry._logs import ( LogRecord, + NoOpLogger, + SeverityNumber, + get_logger_provider, ) +from opentelemetry.sdk._logs import Logger, LoggerProvider from opentelemetry.util.types import _ExtendedAttributes _logger = logging.getLogger(__name__) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index c06dd69200d..db15568842d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from opentelemetry._logs import LogRecord from opentelemetry.sdk._logs._internal import ( LogDroppedAttributesWarning, Logger, @@ -30,7 +29,6 @@ "LoggerProvider", "LoggingHandler", "LogLimits", - "LogRecord", "LogRecordProcessor", "ReadableLogRecord", "ReadWriteLogRecord", 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 1367e879465..bb0ffcafd88 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py @@ -27,7 +27,6 @@ set_value, ) from opentelemetry.sdk._logs import ( - LogRecord, LogRecordProcessor, ReadableLogRecord, ReadWriteLogRecord,