diff --git a/CHANGELOG.md b/CHANGELOG.md index b2155602a2f..8e0dbfd4d61 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`: Convert objects of any type other than AnyValue which fixes invalid type at WSGI request 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 @@ -1719,7 +1721,7 @@ can cause a deadlock to occur over `logging._lock` in some cases ([#4636](https: - Add reset for the global configuration object, for testing purposes ([#636](https://github.com/open-telemetry/opentelemetry-python/pull/636)) - Add support for programmatic instrumentation - ([#579](https://github.com/open-telemetry/opentelemetry-python/pull/569)) + ([#569](https://github.com/open-telemetry/opentelemetry-python/pull/569)) ### Changed diff --git a/opentelemetry-api/src/opentelemetry/attributes/__init__.py b/opentelemetry-api/src/opentelemetry/attributes/__init__.py index fc3d494631a..857bd5c7737 100644 --- a/opentelemetry-api/src/opentelemetry/attributes/__init__.py +++ b/opentelemetry-api/src/opentelemetry/attributes/__init__.py @@ -118,7 +118,7 @@ def _clean_attribute( return None -def _clean_extended_attribute_value( +def _clean_extended_attribute_value( # pylint: disable=too-many-branches value: types.AnyValue, max_len: Optional[int] ) -> types.AnyValue: # for primitive types just return the value and eventually shorten the string length @@ -180,11 +180,14 @@ 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", - ) + try: + return str(value) + except Exception: + 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", + ) def _clean_extended_attribute( @@ -279,6 +282,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..b35adc260b8 100644 --- a/opentelemetry-api/tests/attributes/test_attributes.py +++ b/opentelemetry-api/tests/attributes/test_attributes.py @@ -301,3 +301,22 @@ 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() + + with unittest.mock.patch( + "opentelemetry.attributes._clean_extended_attribute", + return_value="stringified_request", + ): + bdict["request"] = wsgi_request + + # Verify that the request stored in the bounded dict matches the cleaned value + self.assertEqual(bdict["request"], "stringified_request")