diff --git a/CHANGELOG.md b/CHANGELOG.md index 68b9a47006b..d3a34c300e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +- Log a warning when a `LogRecord` in `sdk/log` has dropped attributes + due to reaching limits + ([#3946](https://github.com/open-telemetry/opentelemetry-python/pull/3946)) - Fix RandomIdGenerator can generate invalid Span/Trace Ids ([#3949](https://github.com/open-telemetry/opentelemetry-python/pull/3949)) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index 881bb9a4b27..0254c135e84 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -15,6 +15,7 @@ from opentelemetry.sdk._logs._internal import ( LogData, + LogDroppedAttributesWarning, Logger, LoggerProvider, LoggingHandler, @@ -31,4 +32,5 @@ "LogLimits", "LogRecord", "LogRecordProcessor", + "LogDroppedAttributesWarning", ] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 08835942971..c094e42c958 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -19,6 +19,7 @@ import logging import threading import traceback +import warnings from os import environ from time import time_ns from typing import Any, Callable, Optional, Tuple, Union # noqa @@ -57,6 +58,18 @@ _ENV_VALUE_UNSET = "" +class LogDroppedAttributesWarning(UserWarning): + """Custom warning to indicate dropped log attributes due to limits. + + 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", LogDroppedAttributesWarning) + + class LogLimits: """This class is based on a SpanLimits class in the Tracing module. @@ -190,6 +203,12 @@ def __init__( } ) self.resource = resource + if self.dropped_attributes > 0: + warnings.warn( + "Log record attributes were dropped due to limits", + LogDroppedAttributesWarning, + stacklevel=2, + ) def __eq__(self, other: object) -> bool: if not isinstance(other, LogRecord): diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index 6a33db101c8..5a65f71ba8a 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -14,9 +14,14 @@ import json import unittest +import warnings from opentelemetry.attributes import BoundedAttributes -from opentelemetry.sdk._logs import LogLimits, LogRecord +from opentelemetry.sdk._logs import ( + LogDroppedAttributesWarning, + LogLimits, + LogRecord, +) class TestLogRecord(unittest.TestCase): @@ -98,6 +103,28 @@ def test_log_record_dropped_attributes_set_limits(self): self.assertTrue(result.dropped_attributes == 1) self.assertEqual(expected, result.attributes) + def test_log_record_dropped_attributes_set_limits_warning_once(self): + attr = {"key1": "value1", "key2": "value2"} + limits = LogLimits( + max_attributes=1, + max_attribute_length=1, + ) + + with warnings.catch_warnings(record=True) as cw: + for _ in range(10): + LogRecord( + timestamp=0, + body="a log line", + attributes=attr, + limits=limits, + ) + self.assertEqual(len(cw), 1) + self.assertIsInstance(cw[-1].message, LogDroppedAttributesWarning) + self.assertIn( + "Log record attributes were dropped due to limits", + str(cw[-1].message), + ) + def test_log_record_dropped_attributes_unset_limits(self): attr = {"key": "value", "key2": "value2"} limits = LogLimits()