Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/5300.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: add log record limits environment variables
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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
)
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 81 additions & 1 deletion opentelemetry-sdk/tests/logs/test_log_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"]
Expand Down
Loading