From cc197af984c427589f5d82ca5268bfa887c8f195 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 29 Apr 2026 12:26:33 -0400 Subject: [PATCH 1/9] fix(baggage): Enforce W3C size limits on outbound baggage propagation Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../baggage/propagation/__init__.py | 93 ++++++++++---- .../propagators/test_w3cbaggagepropagator.py | 114 ++++++++++++++++-- 2 files changed, 173 insertions(+), 34 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 49fb378eabd..1ce71c8c102 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -14,7 +14,7 @@ # from logging import getLogger from re import split -from typing import Iterable, List, Mapping, Optional, Set +from typing import Generator, Iterable, Mapping, Optional, Set from urllib.parse import quote_plus, unquote_plus from opentelemetry.baggage import _is_valid_pair, get_all, set_baggage @@ -26,6 +26,50 @@ _logger = getLogger(__name__) +def _apply_baggage_limits( + entries: Iterable[str], + max_pairs: int, + max_pair_length: int, + max_header_length: int, +) -> Generator[str, None, None]: + """Apply W3C Baggage size limits to a sequence of baggage entries. + + Yields entries that fit within the W3C specification limits. + Logs warnings when entries are dropped. + """ + count = 0 + total_length = 0 + + for entry in entries: + if not entry: + continue + + if len(entry) > max_pair_length: + _logger.warning( + "Baggage entry `%s` exceeded the maximum number of bytes per list-member", + entry, + ) + continue + + if count >= max_pairs: + _logger.warning( + "Baggage exceeded the maximum number of list-members", + ) + break + + # Account for comma separator between entries + added_length = len(entry) + (1 if count > 0 else 0) + if total_length + added_length > max_header_length: + _logger.warning( + "Baggage exceeded the maximum number of bytes per baggage-string", + ) + break + + count += 1 + total_length += added_length + yield entry + + class W3CBaggagePropagator(textmap.TextMapPropagator): """Extracts and injects Baggage which is used to annotate telemetry.""" @@ -63,8 +107,7 @@ def extract( ) return context - baggage_entries: List[str] = split(_DELIMITER_PATTERN, header) - total_baggage_entries = self._MAX_PAIRS + baggage_entries = split(_DELIMITER_PATTERN, header) if len(baggage_entries) > self._MAX_PAIRS: _logger.warning( @@ -72,15 +115,12 @@ def extract( header, ) - for entry in baggage_entries: - if len(entry) > self._MAX_PAIR_LENGTH: - _logger.warning( - "Baggage entry `%s` exceeded the maximum number of bytes per list-member", - entry, - ) - continue - if not entry: # empty string - continue + for entry in _apply_baggage_limits( + baggage_entries, + max_pairs=self._MAX_PAIRS, + max_pair_length=self._MAX_PAIR_LENGTH, + max_header_length=self._MAX_HEADER_LENGTH, + ): try: name, value = entry.split("=", 1) except Exception: # pylint: disable=broad-exception-caught @@ -101,9 +141,6 @@ def extract( value, context=context, ) - total_baggage_entries -= 1 - if total_baggage_entries == 0: - break return context @@ -122,8 +159,17 @@ def inject( if not baggage_entries: return - baggage_string = _format_baggage(baggage_entries) - setter.set(carrier, self._BAGGAGE_HEADER_NAME, baggage_string) + baggage_string = ",".join( + _apply_baggage_limits( + _encode_baggage_pairs(baggage_entries), + max_pairs=self._MAX_PAIRS, + max_pair_length=self._MAX_PAIR_LENGTH, + max_header_length=self._MAX_HEADER_LENGTH, + ) + ) + + if baggage_string: + setter.set(carrier, self._BAGGAGE_HEADER_NAME, baggage_string) @property def fields(self) -> Set[str]: @@ -132,10 +178,15 @@ def fields(self) -> Set[str]: def _format_baggage(baggage_entries: Mapping[str, object]) -> str: - return ",".join( - quote_plus(str(key)) + "=" + quote_plus(str(value)) - for key, value in baggage_entries.items() - ) + return ",".join(_encode_baggage_pairs(baggage_entries)) + + +def _encode_baggage_pairs( + baggage_entries: Mapping[str, object], +) -> Generator[str, None, None]: + """Yield URL-encoded 'key=value' pairs from baggage entries.""" + for key, value in baggage_entries.items(): + yield quote_plus(str(key)) + "=" + quote_plus(str(value)) def _extract_first_element( diff --git a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py index 46db45f4d34..7c52c59cf12 100644 --- a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py +++ b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py @@ -99,15 +99,21 @@ def test_header_too_long(self): long_value = "s" * (W3CBaggagePropagator._MAX_HEADER_LENGTH + 1) header = f"key1={long_value}" expected = {} - self.assertEqual(self._extract(header), expected) + with self.assertLogs(level=WARNING) as warning: + self.assertEqual(self._extract(header), expected) + self.assertIn( + "exceeded the maximum number of bytes per baggage-string", + warning.output[0], + ) def test_header_contains_too_many_entries(self): header = ",".join( [f"key{k}=val" for k in range(W3CBaggagePropagator._MAX_PAIRS + 1)] ) - self.assertEqual( - len(self._extract(header)), W3CBaggagePropagator._MAX_PAIRS - ) + with self.assertLogs(level=WARNING): + self.assertEqual( + len(self._extract(header)), W3CBaggagePropagator._MAX_PAIRS + ) def test_header_contains_pair_too_long(self): long_value = "s" * (W3CBaggagePropagator._MAX_PAIR_LENGTH + 1) @@ -130,6 +136,7 @@ def test_extract_unquote_plus(self): ) def test_header_max_entries_skip_invalid_entry(self): + # 181 entries where index 2 is too long: skipping it leaves exactly 180 valid entries with self.assertLogs(level=WARNING) as warning: self.assertEqual( self._extract( @@ -155,11 +162,17 @@ def test_header_max_entries_skip_invalid_entry(self): if index != 2 }, ) - self.assertIn( - "exceeded the maximum number of list-members", - warning.output[0], + self.assertTrue( + any( + "exceeded the maximum number of bytes per list-member" + in msg + for msg in warning.output + ) ) + # 181 entries where index 2 is malformed (no '='): _apply_baggage_limits + # accepts the first 180 entries (indices 0-179), then the malformed entry + # at index 2 is skipped during parsing with self.assertLogs(level=WARNING) as warning: self.assertEqual( self._extract( @@ -178,13 +191,20 @@ def test_header_max_entries_skip_invalid_entry(self): ), { f"key{index}": f"value{index}" - for index in range(W3CBaggagePropagator._MAX_PAIRS + 1) + for index in range(W3CBaggagePropagator._MAX_PAIRS) if index != 2 }, ) - self.assertIn( - "exceeded the maximum number of list-members", - warning.output[0], + self.assertTrue( + any( + "exceeded the maximum number of list-members" in msg + for msg in warning.output + ) + ) + self.assertTrue( + any( + "doesn't match the format" in msg for msg in warning.output + ) ) def test_inject_no_baggage_entries(self): @@ -224,8 +244,8 @@ def test_inject_non_string_values(self): self.assertIn("key3=123.567", output) @patch("opentelemetry.baggage.propagation.get_all") - @patch("opentelemetry.baggage.propagation._format_baggage") - def test_fields(self, mock_format_baggage, mock_baggage): + def test_fields(self, mock_baggage): + mock_baggage.return_value = {"key": "value"} mock_setter = Mock() self.propagator.inject({}, setter=mock_setter) @@ -246,6 +266,74 @@ def test__format_baggage(self): "key%2Fkey=value%2Fvalue", ) + def test_inject_too_many_entries(self): + """Inject should drop entries exceeding _MAX_PAIRS.""" + values = { + f"key{i}": f"val{i}" + for i in range(self.propagator._MAX_PAIRS + 10) + } + ctx = get_current() + for k, v in values.items(): + ctx = set_baggage(k, v, context=ctx) + output = {} + with self.assertLogs(level=WARNING) as warning: + self.propagator.inject(output, context=ctx) + self.assertIn( + "exceeded the maximum number of list-members", + warning.output[0], + ) + + def test_inject_entry_too_long(self): + """Inject should skip individual entries that exceed _MAX_PAIR_LENGTH.""" + long_value = "x" * self.propagator._MAX_PAIR_LENGTH + values = {"key1": "val1", "big": long_value, "key3": "val3"} + ctx = get_current() + for k, v in values.items(): + ctx = set_baggage(k, v, context=ctx) + output = {} + with self.assertLogs(level=WARNING) as warning: + self.propagator.inject(output, context=ctx) + self.assertIn( + "exceeded the maximum number of bytes per list-member", + warning.output[0], + ) + baggage_str = output.get("baggage", "") + self.assertIn("key1=val1", baggage_str) + self.assertIn("key3=val3", baggage_str) + self.assertNotIn("big=", baggage_str) + + def test_inject_total_header_too_long(self): + """Inject should stop adding entries when total header would exceed _MAX_HEADER_LENGTH.""" + # Create entries that individually fit but collectively exceed the max header length + # Each entry "kNNN=" with value ~200 chars; 50 of these > 8192 + value = "v" * 200 + values = {f"k{i:03d}": value for i in range(50)} + ctx = get_current() + for k, v in values.items(): + ctx = set_baggage(k, v, context=ctx) + output = {} + with self.assertLogs(level=WARNING) as warning: + self.propagator.inject(output, context=ctx) + self.assertIn( + "exceeded the maximum number of bytes per baggage-string", + warning.output[0], + ) + baggage_str = output.get("baggage", "") + self.assertLessEqual( + len(baggage_str), self.propagator._MAX_HEADER_LENGTH + ) + + def test_inject_empty_after_all_dropped(self): + """If all entries are too long, nothing should be injected.""" + long_value = "x" * self.propagator._MAX_PAIR_LENGTH + values = {"big1": long_value, "big2": long_value} + ctx = get_current() + for k, v in values.items(): + ctx = set_baggage(k, v, context=ctx) + output = {} + self.propagator.inject(output, context=ctx) + self.assertNotIn("baggage", output) + @patch("opentelemetry.baggage._BAGGAGE_KEY", new="abc") def test_inject_extract(self): carrier = {} From 540f5bd413e8e3460d8252f5f6c541b59cf88f39 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 29 Apr 2026 12:39:10 -0400 Subject: [PATCH 2/9] fix(baggage): Enforce W3C size limits on outbound baggage propagation Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- opentelemetry-api/tests/propagators/test__envcarrier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/tests/propagators/test__envcarrier.py b/opentelemetry-api/tests/propagators/test__envcarrier.py index eb2b99c39ca..3b020a39f4b 100644 --- a/opentelemetry-api/tests/propagators/test__envcarrier.py +++ b/opentelemetry-api/tests/propagators/test__envcarrier.py @@ -470,9 +470,9 @@ def test_roundtrip_baggage(self): self.assertEqual(baggage1, baggage2) @patch("opentelemetry.baggage.propagation.get_all") - @patch("opentelemetry.baggage.propagation._format_baggage") - def test_fields(self, mock_format_baggage, mock_get_all): + def test_fields(self, mock_get_all): """Test that propagator.fields matches injected keys.""" + mock_get_all.return_value = {"key": "value"} mock_setter = Mock() self.propagator.inject({}, setter=mock_setter) From d16f8a134c3eba762c0e80f4e6276e891fffd576 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 29 Apr 2026 12:48:15 -0400 Subject: [PATCH 3/9] Add changelog entry for #5163 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941551d8a99..360e088026f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-api`: Enforce W3C Baggage size limits on outbound propagation in `W3CBaggagePropagator.inject()`. Previously only inbound extraction enforced limits; now inject also caps entries at 180, individual pairs at 4096 bytes, and total header at 8192 bytes per the W3C Baggage spec. The extract path max_pairs limit now counts all size-valid entries rather than only successfully parsed ones. + ([#5163](https://github.com/open-telemetry/opentelemetry-python/pull/5163)) - `opentelemetry-sdk`: add `additional_properties` support to generated config models via custom `datamodel-codegen` template, enabling plugin/custom component names to flow through typed dataclasses ([#5131](https://github.com/open-telemetry/opentelemetry-python/pull/5131)) - Fix incorrect code example in `create_tracer()` docstring From 03f4a89fbf2978d8d2ec1d157c05cb794f07fdba Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 29 Apr 2026 13:18:58 -0400 Subject: [PATCH 4/9] fix lint: rename single-char variables in tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../propagators/test_w3cbaggagepropagator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py index 7c52c59cf12..972a91dbf62 100644 --- a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py +++ b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py @@ -273,8 +273,8 @@ def test_inject_too_many_entries(self): for i in range(self.propagator._MAX_PAIRS + 10) } ctx = get_current() - for k, v in values.items(): - ctx = set_baggage(k, v, context=ctx) + for key, val in values.items(): + ctx = set_baggage(key, val, context=ctx) output = {} with self.assertLogs(level=WARNING) as warning: self.propagator.inject(output, context=ctx) @@ -288,8 +288,8 @@ def test_inject_entry_too_long(self): long_value = "x" * self.propagator._MAX_PAIR_LENGTH values = {"key1": "val1", "big": long_value, "key3": "val3"} ctx = get_current() - for k, v in values.items(): - ctx = set_baggage(k, v, context=ctx) + for key, val in values.items(): + ctx = set_baggage(key, val, context=ctx) output = {} with self.assertLogs(level=WARNING) as warning: self.propagator.inject(output, context=ctx) @@ -309,8 +309,8 @@ def test_inject_total_header_too_long(self): value = "v" * 200 values = {f"k{i:03d}": value for i in range(50)} ctx = get_current() - for k, v in values.items(): - ctx = set_baggage(k, v, context=ctx) + for key, val in values.items(): + ctx = set_baggage(key, val, context=ctx) output = {} with self.assertLogs(level=WARNING) as warning: self.propagator.inject(output, context=ctx) @@ -328,8 +328,8 @@ def test_inject_empty_after_all_dropped(self): long_value = "x" * self.propagator._MAX_PAIR_LENGTH values = {"big1": long_value, "big2": long_value} ctx = get_current() - for k, v in values.items(): - ctx = set_baggage(k, v, context=ctx) + for key, val in values.items(): + ctx = set_baggage(key, val, context=ctx) output = {} self.propagator.inject(output, context=ctx) self.assertNotIn("baggage", output) From 24dd6249fb6ea44b8de7c8b866f6db26e988b5b9 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 29 Apr 2026 15:37:30 -0400 Subject: [PATCH 5/9] Remove redundant max_pairs pre-check in extract The _apply_baggage_limits helper already logs a warning when the maximum number of list-members is exceeded, making the early check redundant. Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/opentelemetry/baggage/propagation/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 1ce71c8c102..a3ab2ca3b70 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -109,12 +109,6 @@ def extract( baggage_entries = split(_DELIMITER_PATTERN, header) - if len(baggage_entries) > self._MAX_PAIRS: - _logger.warning( - "Baggage header `%s` exceeded the maximum number of list-members", - header, - ) - for entry in _apply_baggage_limits( baggage_entries, max_pairs=self._MAX_PAIRS, From d2f1cc0ef039ab45fdf0fb529e04b46e221cdbd6 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 30 Apr 2026 10:00:50 -0500 Subject: [PATCH 6/9] Update opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com> --- .../src/opentelemetry/baggage/propagation/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index a3ab2ca3b70..33268112428 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -31,7 +31,7 @@ def _apply_baggage_limits( max_pairs: int, max_pair_length: int, max_header_length: int, -) -> Generator[str, None, None]: +) -> Iterator[str]: """Apply W3C Baggage size limits to a sequence of baggage entries. Yields entries that fit within the W3C specification limits. From e5b34f1b987f1340fe1c7d0d261516389d7adcbf Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 30 Apr 2026 11:35:39 -0400 Subject: [PATCH 7/9] Add outbound size limits to W3C Baggage propagator Enforce header value and total size limits when injecting baggage entries per the W3C Baggage specification. Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../baggage/propagation/__init__.py | 63 +++++++++++-------- .../propagators/test_w3cbaggagepropagator.py | 31 +++++++++ 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 33268112428..2b5d34ea922 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -14,7 +14,7 @@ # from logging import getLogger from re import split -from typing import Generator, Iterable, Mapping, Optional, Set +from typing import Generator, Iterable, Iterator, Mapping, Optional, Set from urllib.parse import quote_plus, unquote_plus from opentelemetry.baggage import _is_valid_pair, get_all, set_baggage @@ -26,47 +26,56 @@ _logger = getLogger(__name__) -def _apply_baggage_limits( +def _filter_valid_entries( entries: Iterable[str], - max_pairs: int, max_pair_length: int, - max_header_length: int, ) -> Iterator[str]: - """Apply W3C Baggage size limits to a sequence of baggage entries. - - Yields entries that fit within the W3C specification limits. - Logs warnings when entries are dropped. - """ - count = 0 - total_length = 0 - for entry in entries: if not entry: continue - + if not entry.isascii(): + _logger.warning( + "Baggage entry with key `%s` contains non-ASCII characters", + entry.split("=", 1)[0], + ) + continue if len(entry) > max_pair_length: _logger.warning( - "Baggage entry `%s` exceeded the maximum number of bytes per list-member", - entry, + "Baggage entry with key `%s` exceeded the maximum number of bytes per list-member with length %d", + entry.split("=", 1)[0], + len(entry), ) continue + yield entry + - if count >= max_pairs: +def _apply_baggage_limits( + entries: Iterable[str], + max_pairs: int, + max_pair_length: int, + max_header_length: int, +) -> Iterator[str]: + """Apply W3C Baggage size limits to a sequence of baggage entries. + + Yields entries that fit within the W3C specification limits. + Logs warnings when entries are dropped. + """ + length = 0 + for index, entry in enumerate( + _filter_valid_entries(entries, max_pair_length) + ): + if index >= max_pairs: _logger.warning( - "Baggage exceeded the maximum number of list-members", + "Baggage exceeded the maximum number of list-members" ) - break + return - # Account for comma separator between entries - added_length = len(entry) + (1 if count > 0 else 0) - if total_length + added_length > max_header_length: + length += (1 if index > 0 else 0) + len(entry) + if length > max_header_length: _logger.warning( - "Baggage exceeded the maximum number of bytes per baggage-string", + "Baggage exceeded the maximum number of bytes per baggage-string" ) - break - - count += 1 - total_length += added_length + return yield entry @@ -100,7 +109,7 @@ def extract( if not header: return context - if len(header) > self._MAX_HEADER_LENGTH: + if len(header.encode()) > self._MAX_HEADER_LENGTH: _logger.warning( "Baggage header `%s` exceeded the maximum number of bytes per baggage-string", header, diff --git a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py index 972a91dbf62..c3bf575a83e 100644 --- a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py +++ b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py @@ -106,6 +106,30 @@ def test_header_too_long(self): warning.output[0], ) + def test_extract_non_ascii_header(self): + """Extract should skip non-ASCII entries but keep valid ones.""" + header = "key1=val1,key2=caf\u00e9" + with self.assertLogs(level=WARNING) as warning: + result = self._extract(header) + self.assertEqual(result, {"key1": "val1"}) + self.assertIn( + "contains non-ASCII characters", + warning.output[0], + ) + + def test_extract_non_ascii_header_exceeds_byte_limit(self): + """A header under the char limit but over the byte limit when encoded should be rejected.""" + # Each \u00e9 encodes to 2 bytes in UTF-8, so we need fewer chars to exceed the byte limit + non_ascii_value = "\u00e9" * (W3CBaggagePropagator._MAX_HEADER_LENGTH) + header = f"key1={non_ascii_value}" + with self.assertLogs(level=WARNING) as warning: + result = self._extract(header) + self.assertEqual(result, {}) + self.assertIn( + "exceeded the maximum number of bytes per baggage-string", + warning.output[0], + ) + def test_header_contains_too_many_entries(self): header = ",".join( [f"key{k}=val" for k in range(W3CBaggagePropagator._MAX_PAIRS + 1)] @@ -282,6 +306,13 @@ def test_inject_too_many_entries(self): "exceeded the maximum number of list-members", warning.output[0], ) + baggage_str = output.get("baggage", "") + pairs = baggage_str.split(",") + self.assertEqual(len(pairs), self.propagator._MAX_PAIRS) + self.assertNotIn( + f"key{self.propagator._MAX_PAIRS}=", + baggage_str, + ) def test_inject_entry_too_long(self): """Inject should skip individual entries that exceed _MAX_PAIR_LENGTH.""" From 779ca2aa7d11df6360e38c37050d22693d89d450 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 30 Apr 2026 11:49:21 -0400 Subject: [PATCH 8/9] Update non-ASCII test case value Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/propagators/test_w3cbaggagepropagator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py index c3bf575a83e..888d2caedd1 100644 --- a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py +++ b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py @@ -108,7 +108,7 @@ def test_header_too_long(self): def test_extract_non_ascii_header(self): """Extract should skip non-ASCII entries but keep valid ones.""" - header = "key1=val1,key2=caf\u00e9" + header = "key1=val1,key2=test\u00e9" with self.assertLogs(level=WARNING) as warning: result = self._extract(header) self.assertEqual(result, {"key1": "val1"}) From c863d644455c9a150f8eb9c56cad4738a8275e1f Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 30 Apr 2026 13:05:50 -0400 Subject: [PATCH 9/9] Refine baggage propagator limits and update tests Assisted-by: Claude Opus 4.6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../opentelemetry/baggage/propagation/__init__.py | 8 ++------ .../tests/propagators/test_w3cbaggagepropagator.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py index 2b5d34ea922..43419ad2f77 100644 --- a/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/baggage/propagation/__init__.py @@ -14,7 +14,7 @@ # from logging import getLogger from re import split -from typing import Generator, Iterable, Iterator, Mapping, Optional, Set +from typing import Iterable, Iterator, Mapping, Optional, Set from urllib.parse import quote_plus, unquote_plus from opentelemetry.baggage import _is_valid_pair, get_all, set_baggage @@ -180,13 +180,9 @@ def fields(self) -> Set[str]: return {self._BAGGAGE_HEADER_NAME} -def _format_baggage(baggage_entries: Mapping[str, object]) -> str: - return ",".join(_encode_baggage_pairs(baggage_entries)) - - def _encode_baggage_pairs( baggage_entries: Mapping[str, object], -) -> Generator[str, None, None]: +) -> Iterator[str]: """Yield URL-encoded 'key=value' pairs from baggage entries.""" for key, value in baggage_entries.items(): yield quote_plus(str(key)) + "=" + quote_plus(str(value)) diff --git a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py index 888d2caedd1..e85b6adf518 100644 --- a/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py +++ b/opentelemetry-api/tests/propagators/test_w3cbaggagepropagator.py @@ -17,12 +17,10 @@ from logging import WARNING from unittest import TestCase from unittest.mock import Mock, patch +from urllib.parse import quote_plus from opentelemetry.baggage import get_all, set_baggage -from opentelemetry.baggage.propagation import ( - W3CBaggagePropagator, - _format_baggage, -) +from opentelemetry.baggage.propagation import W3CBaggagePropagator from opentelemetry.context import get_current @@ -281,7 +279,13 @@ def test_fields(self, mock_baggage): self.assertEqual(inject_fields, self.propagator.fields) - def test__format_baggage(self): + def test_encode_baggage_pairs(self): + def _format_baggage(entries): + return ",".join( + quote_plus(str(k)) + "=" + quote_plus(str(v)) + for k, v in entries.items() + ) + self.assertEqual( _format_baggage({"key key": "value value"}), "key+key=value+value" )