From aa5c4e398796c94cc155f82526698858cd2df07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20V=C3=A1squez?= Date: Wed, 4 Mar 2020 07:59:01 -0500 Subject: [PATCH 1/3] sdk: Change attribute validation logic 4fca8c985769 ("Add runtime validation in setAttribute (#348)") added a robust attribute validation using numbers.Number to validate numeric types. Although the approach is correct, it presents some complications because Complex, Fraction and Decimal are accepted because they are Numbers. This presents a problem to the exporters because they will have to consider all these different cases when converting attributes to the underlying exporter representation. This commit simplifies the logic by accepting only int and float as numeric values. --- opentelemetry-api/src/opentelemetry/util/types.py | 3 +-- .../src/opentelemetry/sdk/trace/__init__.py | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/util/types.py b/opentelemetry-api/src/opentelemetry/util/types.py index 5ce93d84b25..1ecd3c5f4b0 100644 --- a/opentelemetry-api/src/opentelemetry/util/types.py +++ b/opentelemetry-api/src/opentelemetry/util/types.py @@ -22,7 +22,6 @@ float, Sequence[str], Sequence[bool], - Sequence[int], - Sequence[float], + Sequence[Union[int, float]], ] Attributes = Optional[Dict[str, AttributeValue]] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 7f305dcc0d9..32e13658552 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -18,7 +18,6 @@ import random import threading from contextlib import contextmanager -from numbers import Number from types import TracebackType from typing import Iterator, Optional, Sequence, Tuple, Type @@ -238,7 +237,7 @@ def set_attribute(self, key: str, value: types.AttributeValue) -> None: if error_message is not None: logger.warning("%s in attribute value sequence", error_message) return - elif not isinstance(value, (bool, str, Number, Sequence)): + elif not isinstance(value, (bool, str, int, float)): logger.warning("invalid type for attribute value") return @@ -254,12 +253,13 @@ def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]: first_element_type = type(sequence[0]) - if issubclass(first_element_type, Number): - first_element_type = Number - - if first_element_type not in (bool, str, Number): + if first_element_type not in (bool, str, int, float): return "invalid type" + # int and float are both numeric types, allow mixed arrays of those + if first_element_type in (int, float): + first_element_type = (int, float) + for element in sequence: if not isinstance(element, first_element_type): return "different type" From b0f858a1f8a29185cf97a757352730674dfd2559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20V=C3=A1squez?= Date: Wed, 4 Mar 2020 08:32:52 -0500 Subject: [PATCH 2/3] sdk: Check for empty or null attibute key --- opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py | 4 ++++ opentelemetry-sdk/tests/trace/test_trace.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 32e13658552..27cfbd1e368 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -232,6 +232,10 @@ def set_attribute(self, key: str, value: types.AttributeValue) -> None: logger.warning("Setting attribute on ended span.") return + if not key: + logger.warning("invalid key (empty or null)") + return + if isinstance(value, Sequence): error_message = self._check_attribute_value_sequence(value) if error_message is not None: diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 188c019acc1..287567c5c51 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -440,6 +440,9 @@ def test_invalid_attribute_values(self): "list-with-non-primitive-data-type", [dict(), 123] ) + root.set_attribute("", 123) + root.set_attribute(None, 123) + self.assertEqual(len(root.attributes), 0) def test_check_sequence_helper(self): From 4597838026867f3e7d6df5f50a119f196ccee250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20V=C3=A1squez?= Date: Mon, 9 Mar 2020 20:18:48 -0500 Subject: [PATCH 3/3] avoid mixed int/float sequence attributes --- .../src/opentelemetry/util/types.py | 3 ++- .../src/opentelemetry/sdk/trace/__init__.py | 4 ---- opentelemetry-sdk/tests/trace/test_trace.py | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/util/types.py b/opentelemetry-api/src/opentelemetry/util/types.py index 1ecd3c5f4b0..5ce93d84b25 100644 --- a/opentelemetry-api/src/opentelemetry/util/types.py +++ b/opentelemetry-api/src/opentelemetry/util/types.py @@ -22,6 +22,7 @@ float, Sequence[str], Sequence[bool], - Sequence[Union[int, float]], + Sequence[int], + Sequence[float], ] Attributes = Optional[Dict[str, AttributeValue]] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 27cfbd1e368..d2f1816cb8a 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -260,10 +260,6 @@ def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]: if first_element_type not in (bool, str, int, float): return "invalid type" - # int and float are both numeric types, allow mixed arrays of those - if first_element_type in (int, float): - first_element_type = (int, float) - for element in sequence: if not isinstance(element, first_element_type): return "different type" diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 287567c5c51..2a50f88c258 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -391,7 +391,7 @@ def test_attributes(self): root.set_attribute("empty-list", []) root.set_attribute("list-of-bools", [True, True, False]) - root.set_attribute("list-of-numerics", [123, 3.14, 0]) + root.set_attribute("list-of-numerics", [123, 314, 0]) self.assertEqual(len(root.attributes), 10) self.assertEqual(root.attributes["component"], "http") @@ -409,7 +409,7 @@ def test_attributes(self): root.attributes["list-of-bools"], [True, True, False] ) self.assertEqual( - root.attributes["list-of-numerics"], [123, 3.14, 0] + root.attributes["list-of-numerics"], [123, 314, 0] ) attributes = { @@ -461,8 +461,18 @@ def test_check_sequence_helper(self): ), "different type", ) + self.assertEqual( + trace.Span._check_attribute_value_sequence([1, 2, 3.4, 5]), + "different type", + ) + self.assertIsNone( + trace.Span._check_attribute_value_sequence([1, 2, 3, 5]) + ) + self.assertIsNone( + trace.Span._check_attribute_value_sequence([1.2, 2.3, 3.4, 4.5]) + ) self.assertIsNone( - trace.Span._check_attribute_value_sequence([1, 2, 3.4, 5]) + trace.Span._check_attribute_value_sequence([True, False]) ) self.assertIsNone( trace.Span._check_attribute_value_sequence(["ss", "dw", "fw"])