diff --git a/CHANGELOG.md b/CHANGELOG.md index 332ec653c12..745b248832e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Allow bytes as attributes + ([#3605](https://github.com/open-telemetry/opentelemetry-python/pull/3605)) - Use Attribute rather than boundattribute in logrecord ([#3567](https://github.com/open-telemetry/opentelemetry-python/pull/3567)) - Fix flush error when no LoggerProvider configured for LoggingHandler diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py index 6593d89fd87..3cc542d3916 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/__init__.py @@ -75,6 +75,8 @@ def _encode_value(value: Any) -> PB2AnyValue: return PB2AnyValue(int_value=value) if isinstance(value, float): return PB2AnyValue(double_value=value) + if isinstance(value, bytes): + return PB2AnyValue(bytes_value=value) if isinstance(value, Sequence): return PB2AnyValue( array_value=PB2ArrayValue(values=[_encode_value(v) for v in value]) diff --git a/opentelemetry-api/src/opentelemetry/attributes/__init__.py b/opentelemetry-api/src/opentelemetry/attributes/__init__.py index a3c3c31197b..4142b0fd026 100644 --- a/opentelemetry-api/src/opentelemetry/attributes/__init__.py +++ b/opentelemetry-api/src/opentelemetry/attributes/__init__.py @@ -21,8 +21,6 @@ from opentelemetry.util import types -# bytes are accepted as a user supplied value for attributes but -# decoded to strings internally. _VALID_ATTR_VALUE_TYPES = (bool, str, bytes, int, float) @@ -44,7 +42,6 @@ def _clean_attribute( An attribute needs cleansing if: - Its length is greater than the maximum allowed length. - - It needs to be encoded/decoded e.g, bytes to strings. """ if not (key and isinstance(key, str)): @@ -114,13 +111,6 @@ def _clean_attribute_value( if value is None: return None - if isinstance(value, bytes): - try: - value = value.decode() - except UnicodeDecodeError: - _logger.warning("Byte attribute could not be decoded.") - return None - if limit is not None and isinstance(value, str): value = value[:limit] return value diff --git a/opentelemetry-api/tests/attributes/test_attributes.py b/opentelemetry-api/tests/attributes/test_attributes.py index ad2f741fb1f..ece56662afb 100644 --- a/opentelemetry-api/tests/attributes/test_attributes.py +++ b/opentelemetry-api/tests/attributes/test_attributes.py @@ -78,10 +78,10 @@ def test_sequence_attr_decode(self): ] expected = [ None, - "Content-Disposition", - "Content-Type", - None, - "Keep-Alive", + b"Content-Disposition", + b"Content-Type", + b"\x81", + b"Keep-Alive", ] self.assertEqual( _clean_attribute("headers", seq, None), tuple(expected) diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index da3f9469617..854898c7468 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -238,9 +238,6 @@ def test_invalid_resource_attribute_values(self): { SERVICE_NAME: "test", "non-primitive-data-type": {}, - "invalid-byte-type-attribute": ( - b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1" - ), "": "empty-key-value", None: "null-key-value", "another-non-primitive": uuid.uuid4(), diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 4150d60d104..3f0323ff03a 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -736,19 +736,12 @@ def test_invalid_attribute_values(self): def test_byte_type_attribute_value(self): with self.tracer.start_as_current_span("root") as root: - with self.assertLogs(level=WARNING): - root.set_attribute( - "invalid-byte-type-attribute", - b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1", - ) - self.assertFalse( - "invalid-byte-type-attribute" in root.attributes - ) - - root.set_attribute("valid-byte-type-attribute", b"valid byte") - self.assertTrue( - isinstance(root.attributes["valid-byte-type-attribute"], str) - ) + for key, value in ( + ("arbitrary", b"\xd8\xe1\xb7\xeb\xa8\xe5 \xd2\xb7\xe1"), + ("encodable", b"valid byte"), + ): + root.set_attribute(key, b"valid byte") + self.assertTrue(isinstance(root.attributes[key], bytes)) def test_sampling_attributes(self): sampling_attributes = {