diff --git a/.changelog/5300.added b/.changelog/5300.added new file mode 100644 index 00000000000..60c16fd8210 --- /dev/null +++ b/.changelog/5300.added @@ -0,0 +1 @@ +`opentelemetry-sdk`: add log record limits environment variables diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index b6b1ce88f69..ef570e9e080 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -53,6 +53,8 @@ from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, OTEL_SDK_DISABLED, ) @@ -130,19 +132,27 @@ class LogRecordLimits: - Else if the global limit has a default value, the default value will be used. Args: - max_attributes: Maximum number of attributes that can be added to a span, event, and link. + max_attributes: Maximum number of attributes that can be added to a log record (global fallback). Environment variable: ``OTEL_ATTRIBUTE_COUNT_LIMIT`` Default: {_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT} - max_attribute_length: Maximum length an attribute value can have. Values longer than - the specified length will be truncated. + max_attribute_length: Maximum length an attribute value can have (global fallback). Values + longer than the specified length will be truncated. + max_log_record_attributes: Maximum number of attributes that can be added to a log record. + Environment variable: ``OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT`` + Falls back to ``max_attributes`` when unset. + max_log_record_attribute_length: Maximum length a log record attribute value can have. + Environment variable: ``OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT`` + Falls back to ``max_attribute_length`` when unset. """ def __init__( self, max_attributes: int | None = None, max_attribute_length: int | None = None, + max_log_record_attributes: int | None = None, + max_log_record_attribute_length: int | None = None, ): - # attribute count + # global attribute count global_max_attributes = self._from_env_if_absent( max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT ) @@ -152,14 +162,32 @@ def __init__( else _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT ) - # attribute length + # global attribute length self.max_attribute_length = self._from_env_if_absent( max_attribute_length, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, ) + self.max_log_record_attributes = self._from_env_if_absent( + max_log_record_attributes, + OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT, + default=self.max_attributes, + ) + + self.max_log_record_attribute_length = self._from_env_if_absent( + max_log_record_attribute_length, + OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT, + default=self.max_attribute_length, + ) + def __repr__(self): - return f"{type(self).__name__}(max_attributes={self.max_attributes}, max_attribute_length={self.max_attribute_length})" + return ( + f"{type(self).__name__}(" + f"max_attributes={self.max_attributes}, " + f"max_attribute_length={self.max_attribute_length}, " + f"max_log_record_attributes={self.max_log_record_attributes}, " + f"max_log_record_attribute_length={self.max_log_record_attribute_length})" + ) @classmethod def _from_env_if_absent( @@ -265,12 +293,12 @@ class ReadWriteLogRecord: def __post_init__(self): self.log_record.attributes = BoundedAttributes( - maxlen=self.limits.max_attributes, + maxlen=self.limits.max_log_record_attributes, attributes=self.log_record.attributes if self.log_record.attributes else None, immutable=False, - max_value_len=self.limits.max_attribute_length, + max_value_len=self.limits.max_log_record_attribute_length, extended_attributes=True, ) if self.dropped_attributes > 0: diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 94738ca4697..fca608a5134 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -178,6 +178,25 @@ span attribute values can have. This takes precedence over :envvar:`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`. """ +OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT = "OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT" +""" +.. envvar:: OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT + +The :envvar:`OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT` represents the maximum allowed log record +attribute count. This takes precedence over :envvar:`OTEL_ATTRIBUTE_COUNT_LIMIT`. +""" + +OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT = ( + "OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT" +) +""" +.. envvar:: OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT + +The :envvar:`OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT` represents the maximum allowed length +log record attribute values can have. This takes precedence over +:envvar:`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`. +""" + OTEL_SPAN_EVENT_COUNT_LIMIT = "OTEL_SPAN_EVENT_COUNT_LIMIT" """ .. envvar:: OTEL_SPAN_EVENT_COUNT_LIMIT diff --git a/opentelemetry-sdk/tests/logs/test_log_limits.py b/opentelemetry-sdk/tests/logs/test_log_limits.py index daaba3ad36b..3d1f69dc498 100644 --- a/opentelemetry-sdk/tests/logs/test_log_limits.py +++ b/opentelemetry-sdk/tests/logs/test_log_limits.py @@ -11,12 +11,20 @@ from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT, ) class TestLogLimits(unittest.TestCase): def test_log_limits_repr_unset(self): - expected = f"LogRecordLimits(max_attributes={_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT}, max_attribute_length=None)" + expected = ( + f"LogRecordLimits(" + f"max_attributes={_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT}, " + f"max_attribute_length=None, " + f"max_log_record_attributes={_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT}, " + f"max_log_record_attribute_length=None)" + ) limits = str(LogRecordLimits()) self.assertEqual(expected, limits) @@ -33,10 +41,82 @@ def test_log_limits_max_attribute_length(self): self.assertEqual(expected, limits.max_attribute_length) + def test_log_limits_max_log_record_attributes(self): + limits = LogRecordLimits(max_log_record_attributes=5) + + self.assertEqual(5, limits.max_log_record_attributes) + + def test_log_limits_max_log_record_attribute_length(self): + limits = LogRecordLimits(max_log_record_attribute_length=10) + + self.assertEqual(10, limits.max_log_record_attribute_length) + + @patch.dict("os.environ", {OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT: "7"}) + def test_logrecord_count_env_var(self): + limits = LogRecordLimits() + + self.assertEqual(7, limits.max_log_record_attributes) + self.assertEqual( + _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT, limits.max_attributes + ) + + @patch.dict( + "os.environ", {OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT: "20"} + ) + def test_logrecord_length_env_var(self): + limits = LogRecordLimits() + + self.assertEqual(20, limits.max_log_record_attribute_length) + self.assertIsNone(limits.max_attribute_length) + + @patch.dict( + "os.environ", + { + OTEL_ATTRIBUTE_COUNT_LIMIT: "50", + OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT: "3", + }, + ) + def test_logrecord_count_env_takes_precedence(self): + limits = LogRecordLimits() + + self.assertEqual(50, limits.max_attributes) + self.assertEqual(3, limits.max_log_record_attributes) + + @patch.dict( + "os.environ", + { + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "100", + OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT: "25", + }, + ) + def test_logrecord_length_env_takes_precedence(self): + limits = LogRecordLimits() + + self.assertEqual(100, limits.max_attribute_length) + self.assertEqual(25, limits.max_log_record_attribute_length) + + @patch.dict("os.environ", {OTEL_ATTRIBUTE_COUNT_LIMIT: "42"}, clear=True) + def test_global_count_env_applies_as_fallback(self): + limits = LogRecordLimits() + + self.assertEqual(42, limits.max_attributes) + self.assertEqual(42, limits.max_log_record_attributes) + + @patch.dict( + "os.environ", {OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "60"}, clear=True + ) + def test_global_length_env_applies_as_fallback(self): + limits = LogRecordLimits() + + self.assertEqual(60, limits.max_attribute_length) + self.assertEqual(60, limits.max_log_record_attribute_length) + def test_invalid_env_vars_raise(self): env_vars = [ OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT, + OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT, ] bad_values = ["bad", "-1"]