Skip to content

Commit 1685cb0

Browse files
committed
Allow Passing Limit
1 parent 43341d7 commit 1685cb0

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ def __init__(
556556
) -> None:
557557
super().__init__(level=level)
558558
self._logger_provider = logger_provider or get_logger_provider()
559+
self._log_limits = LogLimits()
559560

560561
@staticmethod
561562
def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes:
@@ -629,6 +630,7 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
629630
body=body,
630631
resource=logger.resource,
631632
attributes=attributes,
633+
limits=self._log_limits,
632634
)
633635

634636
def emit(self, record: logging.LogRecord) -> None:

opentelemetry-sdk/tests/logs/test_handler.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
LoggingHandler,
2828
LogRecordProcessor,
2929
)
30+
from opentelemetry.sdk.environment_variables import OTEL_ATTRIBUTE_COUNT_LIMIT
3031
from opentelemetry.semconv._incubating.attributes import code_attributes
3132
from opentelemetry.semconv.attributes import exception_attributes
3233
from opentelemetry.trace import (
@@ -367,6 +368,152 @@ def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error(
367368

368369
self.assertEqual(processor.emit_count(), 0)
369370

371+
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "3"})
372+
def test_otel_attribute_count_limit_respected_in_logging_handler(self):
373+
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT is properly respected by LoggingHandler."""
374+
processor, logger = set_up_test_logging(logging.WARNING)
375+
376+
# Create a log record with many extra attributes
377+
extra_attrs = {f"custom_attr_{i}": f"value_{i}" for i in range(10)}
378+
379+
with self.assertLogs(level=logging.WARNING):
380+
logger.warning(
381+
"Test message with many attributes", extra=extra_attrs
382+
)
383+
384+
log_record = processor.get_log_record(0)
385+
386+
# With OTEL_ATTRIBUTE_COUNT_LIMIT=3, should have exactly 3 attributes
387+
total_attrs = len(log_record.attributes)
388+
self.assertEqual(
389+
total_attrs,
390+
3,
391+
f"Should have exactly 3 attributes due to limit, got {total_attrs}",
392+
)
393+
394+
# Should have 10 dropped attributes (10 custom + 3 code - 3 kept = 10 dropped)
395+
self.assertEqual(
396+
log_record.dropped_attributes,
397+
10,
398+
f"Should have 10 dropped attributes, got {log_record.dropped_attributes}",
399+
)
400+
401+
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "5"})
402+
def test_otel_attribute_count_limit_includes_code_attributes(self):
403+
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT applies to all attributes including code attributes."""
404+
processor, logger = set_up_test_logging(logging.WARNING)
405+
406+
# Create a log record with some extra attributes
407+
extra_attrs = {f"user_attr_{i}": f"value_{i}" for i in range(8)}
408+
409+
with self.assertLogs(level=logging.WARNING):
410+
logger.warning("Test message", extra=extra_attrs)
411+
412+
log_record = processor.get_log_record(0)
413+
414+
# With OTEL_ATTRIBUTE_COUNT_LIMIT=5, should have exactly 5 attributes
415+
total_attrs = len(log_record.attributes)
416+
self.assertEqual(
417+
total_attrs,
418+
5,
419+
f"Should have exactly 5 attributes due to limit, got {total_attrs}",
420+
)
421+
422+
# Should have 6 dropped attributes (8 user + 3 code - 5 kept = 6 dropped)
423+
self.assertEqual(
424+
log_record.dropped_attributes,
425+
6,
426+
f"Should have 6 dropped attributes, got {log_record.dropped_attributes}",
427+
)
428+
429+
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "0"})
430+
def test_otel_attribute_count_limit_zero_prevents_all_attributes(self):
431+
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT=0 prevents all attributes."""
432+
processor, logger = set_up_test_logging(logging.WARNING)
433+
434+
# Create a log record with extra attributes
435+
extra_attrs = {"user_attr": "value", "another_attr": "another_value"}
436+
437+
with self.assertLogs(level=logging.WARNING):
438+
logger.warning("Test message", extra=extra_attrs)
439+
440+
log_record = processor.get_log_record(0)
441+
442+
# With OTEL_ATTRIBUTE_COUNT_LIMIT=0, should have 0 attributes
443+
self.assertEqual(
444+
len(log_record.attributes),
445+
0,
446+
"With OTEL_ATTRIBUTE_COUNT_LIMIT=0, should have no attributes",
447+
)
448+
449+
# Should have 5 dropped attributes (2 user + 3 code = 5 dropped)
450+
self.assertEqual(
451+
log_record.dropped_attributes,
452+
5,
453+
f"Should have 5 dropped attributes, got {log_record.dropped_attributes}",
454+
)
455+
456+
def test_logging_handler_without_env_var_uses_default_limit(self):
457+
"""Test that without OTEL_ATTRIBUTE_COUNT_LIMIT, default limit (128) should apply."""
458+
processor, logger = set_up_test_logging(logging.WARNING)
459+
460+
# Create a log record with many attributes (more than default limit of 128)
461+
extra_attrs = {f"attr_{i}": f"value_{i}" for i in range(150)}
462+
463+
with self.assertLogs(level=logging.WARNING):
464+
logger.warning(
465+
"Test message with many attributes", extra=extra_attrs
466+
)
467+
468+
log_record = processor.get_log_record(0)
469+
470+
# Should be limited to default limit (128) total attributes
471+
total_attrs = len(log_record.attributes)
472+
self.assertEqual(
473+
total_attrs,
474+
128,
475+
f"Should have exactly 128 attributes (default limit), got {total_attrs}",
476+
)
477+
478+
# Should have 25 dropped attributes (150 user + 3 code - 128 kept = 25 dropped)
479+
self.assertEqual(
480+
log_record.dropped_attributes,
481+
25,
482+
f"Should have 25 dropped attributes, got {log_record.dropped_attributes}",
483+
)
484+
485+
@patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "2"})
486+
def test_otel_attribute_count_limit_with_exception_attributes(self):
487+
"""Test that OTEL_ATTRIBUTE_COUNT_LIMIT applies even with exception attributes."""
488+
processor, logger = set_up_test_logging(logging.ERROR)
489+
490+
try:
491+
raise ValueError("test exception")
492+
except ValueError:
493+
with self.assertLogs(level=logging.ERROR):
494+
logger.exception(
495+
"Exception occurred", extra={"custom_attr": "value"}
496+
)
497+
498+
log_record = processor.get_log_record(0)
499+
500+
# With OTEL_ATTRIBUTE_COUNT_LIMIT=2, should have exactly 2 attributes
501+
# Total available: 3 code + 3 exception + 1 custom = 7 attributes
502+
# But limited to 2, so 5 should be dropped
503+
total_attrs = len(log_record.attributes)
504+
self.assertEqual(
505+
total_attrs,
506+
2,
507+
f"Should have exactly 2 attributes due to limit, got {total_attrs}",
508+
)
509+
510+
# Should have 5 dropped attributes (7 total - 2 kept = 5 dropped)
511+
self.assertEqual(
512+
log_record.dropped_attributes,
513+
5,
514+
f"Should have 5 dropped attributes, got {log_record.dropped_attributes}",
515+
)
516+
370517

371518
def set_up_test_logging(level, formatter=None, root_logger=False):
372519
logger_provider = LoggerProvider()

0 commit comments

Comments
 (0)