diff --git a/CHANGELOG.md b/CHANGELOG.md index b2155602a2f..5c28654cd3b 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-sdk: Added request filter which fixes invalid type at WSGI request headers and attributes + ([#4808](https://github.com/open-telemetry/opentelemetry-python/pull/4808)) - docs: Added sqlcommenter example ([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734)) - build: bump ruff to 0.14.1 diff --git a/opentelemetry-api/src/opentelemetry/attributes/__init__.py b/opentelemetry-api/src/opentelemetry/attributes/__init__.py index fc3d494631a..04fec92c136 100644 --- a/opentelemetry-api/src/opentelemetry/attributes/__init__.py +++ b/opentelemetry-api/src/opentelemetry/attributes/__init__.py @@ -180,11 +180,7 @@ def _clean_extended_attribute_value( # Freeze mutable sequences defensively return tuple(cleaned_seq) - raise TypeError( - f"Invalid type {type(value).__name__} for attribute value. " - f"Expected one of {[valid_type.__name__ for valid_type in _VALID_ANY_VALUE_TYPES]} or a " - "sequence of those types", - ) + return value def _clean_extended_attribute( @@ -279,6 +275,9 @@ def __setitem__(self, key: str, value: types.AnyValue) -> None: return if self._extended_attributes: + # Convert types other than AnyValue to strings before cleaning + if not isinstance(value, _VALID_ANY_VALUE_TYPES): + value = str(value) value = _clean_extended_attribute( key, value, self.max_value_len ) diff --git a/opentelemetry-api/tests/attributes/test_attributes.py b/opentelemetry-api/tests/attributes/test_attributes.py index 8a653387254..5237deb9dbc 100644 --- a/opentelemetry-api/tests/attributes/test_attributes.py +++ b/opentelemetry-api/tests/attributes/test_attributes.py @@ -301,3 +301,34 @@ def test_extended_attributes(self): bdict["key"] = "value" clean_extended_attribute_mock.assert_called_once() + + def test_wsgi_request_conversion_to_string(self): + """Test that WSGI request objects are converted to strings before calling _clean_extended_attribute.""" + + class DummyWSGIRequest: + def __str__(self): + return "" + + bdict = BoundedAttributes(extended_attributes=True, immutable=False) + wsgi_request = DummyWSGIRequest() + original_request = wsgi_request # Keep reference to original object + + with unittest.mock.patch( + "opentelemetry.attributes._clean_extended_attribute", + return_value="stringified_request", + ) as clean_extended_attribute_mock: + bdict["request"] = wsgi_request + + # Verify that _clean_extended_attribute was called + clean_extended_attribute_mock.assert_called_once() + + # Verify that the value passed to _clean_extended_attribute is a string, not the original object + call_args = clean_extended_attribute_mock.call_args + passed_value = call_args[0][1] # Second argument is the value + self.assertIsInstance(passed_value, str) + self.assertNotEqual( + passed_value, original_request + ) # Should be stringified, not the original object + self.assertIn( + "DummyWSGIRequest", passed_value + ) # String representation includes class name