|
27 | 27 | LoggingHandler,
|
28 | 28 | LogRecordProcessor,
|
29 | 29 | )
|
| 30 | +from opentelemetry.sdk.environment_variables import OTEL_ATTRIBUTE_COUNT_LIMIT |
30 | 31 | from opentelemetry.semconv._incubating.attributes import code_attributes
|
31 | 32 | from opentelemetry.semconv.attributes import exception_attributes
|
32 | 33 | from opentelemetry.trace import (
|
@@ -367,6 +368,152 @@ def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error(
|
367 | 368 |
|
368 | 369 | self.assertEqual(processor.emit_count(), 0)
|
369 | 370 |
|
| 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 | + |
370 | 517 |
|
371 | 518 | def set_up_test_logging(level, formatter=None, root_logger=False):
|
372 | 519 | logger_provider = LoggerProvider()
|
|
0 commit comments