Skip to content
This repository has been archived by the owner on Oct 19, 2023. It is now read-only.

Commit

Permalink
Add runtime validation in setAttribute (open-telemetry#348)
Browse files Browse the repository at this point in the history
Validate attribute value data types before adding to span
Add lists as an accepted data type.

By adding validation during the setAttribute phase, this
allows allows exporters to avoid redundant code to validate
attributes.
  • Loading branch information
jakemalachowski authored and toumorokoshi committed Jan 20, 2020
1 parent 3883e0a commit 4fca8c9
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
32 changes: 32 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import random
import threading
from contextlib import contextmanager
from numbers import Number
from types import TracebackType
from typing import Iterator, Optional, Sequence, Tuple, Type

Expand Down Expand Up @@ -216,8 +217,39 @@ def set_attribute(self, key: str, value: types.AttributeValue) -> None:
if has_ended:
logger.warning("Setting attribute on ended span.")
return

if isinstance(value, Sequence):
error_message = self._check_attribute_value_sequence(value)
if error_message is not None:
logger.warning("%s in attribute value sequence", error_message)
return
elif not isinstance(value, (bool, str, Number, Sequence)):
logger.warning("invalid type for attribute value")
return

self.attributes[key] = value

@staticmethod
def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]:
"""
Checks if sequence items are valid and are of the same type
"""
if len(sequence) == 0:
return None

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):
return "invalid type"

for element in sequence:
if not isinstance(element, first_element_type):
return "different type"
return None

def add_event(
self,
name: str,
Expand Down
53 changes: 52 additions & 1 deletion opentelemetry-sdk/tests/trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,11 @@ def test_attributes(self):
root.set_attribute("attr-key", "attr-value1")
root.set_attribute("attr-key", "attr-value2")

self.assertEqual(len(root.attributes), 7)
root.set_attribute("empty-list", [])
root.set_attribute("list-of-bools", [True, True, False])
root.set_attribute("list-of-numerics", [123, 3.14, 0])

self.assertEqual(len(root.attributes), 10)
self.assertEqual(root.attributes["component"], "http")
self.assertEqual(root.attributes["http.method"], "GET")
self.assertEqual(
Expand All @@ -379,6 +383,13 @@ def test_attributes(self):
self.assertEqual(root.attributes["http.status_text"], "OK")
self.assertEqual(root.attributes["misc.pi"], 3.14)
self.assertEqual(root.attributes["attr-key"], "attr-value2")
self.assertEqual(root.attributes["empty-list"], [])
self.assertEqual(
root.attributes["list-of-bools"], [True, True, False]
)
self.assertEqual(
root.attributes["list-of-numerics"], [123, 3.14, 0]
)

attributes = {
"attr-key": "val",
Expand All @@ -393,6 +404,46 @@ def test_attributes(self):
self.assertEqual(root.attributes["attr-key2"], "val2")
self.assertEqual(root.attributes["attr-in-both"], "span-attr")

def test_invalid_attribute_values(self):
with self.tracer.start_as_current_span("root") as root:
root.set_attribute("non-primitive-data-type", dict())
root.set_attribute(
"list-of-mixed-data-types-numeric-first",
[123, False, "string"],
)
root.set_attribute(
"list-of-mixed-data-types-non-numeric-first",
[False, 123, "string"],
)
root.set_attribute(
"list-with-non-primitive-data-type", [dict(), 123]
)

self.assertEqual(len(root.attributes), 0)

def test_check_sequence_helper(self):
# pylint: disable=protected-access
self.assertEqual(
trace.Span._check_attribute_value_sequence([1, 2, 3.4, "ss", 4]),
"different type",
)
self.assertEqual(
trace.Span._check_attribute_value_sequence([dict(), 1, 2, 3.4, 4]),
"invalid type",
)
self.assertEqual(
trace.Span._check_attribute_value_sequence(
["sw", "lf", 3.4, "ss"]
),
"different type",
)
self.assertIsNone(
trace.Span._check_attribute_value_sequence([1, 2, 3.4, 5])
)
self.assertIsNone(
trace.Span._check_attribute_value_sequence(["ss", "dw", "fw"])
)

def test_sampling_attributes(self):
decision_attributes = {
"sampler-attr": "sample-val",
Expand Down

0 comments on commit 4fca8c9

Please sign in to comment.