Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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 @@ -50,6 +50,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 @@ -127,19 +129,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 @@ -149,14 +159,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,
Comment thread
herin049 marked this conversation as resolved.
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 @@ -262,12 +290,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 @@ -191,6 +191,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):
Comment thread
herin049 marked this conversation as resolved.
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