Skip to content

Commit e10dc80

Browse files
Skip and warn if invalid attr for Labeler
1 parent c86a538 commit e10dc80

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

opentelemetry-instrumentation/src/opentelemetry/instrumentation/_labeler/_internal/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import contextvars
16+
import logging
1617
import threading
1718
from types import MappingProxyType
1819
from typing import Any, Dict, Optional, Union
@@ -22,6 +23,8 @@
2223
contextvars.ContextVar("otel_labeler", default=None)
2324
)
2425

26+
_logger = logging.getLogger(__name__)
27+
2528

2629
class Labeler:
2730
"""
@@ -58,6 +61,14 @@ def add(self, key: str, value: Union[str, int, float, bool]) -> None:
5861
key: attribute key
5962
value: attribute value, must be a primitive type: str, int, float, or bool
6063
"""
64+
if not isinstance(value, (str, int, float, bool)):
65+
_logger.warning(
66+
"Skipping attribute '%s': value must be str, int, float, or bool, got %s",
67+
key,
68+
type(value).__name__,
69+
)
70+
return
71+
6172
with self._lock:
6273
if (
6374
len(self._attributes) >= self._max_custom_attrs
@@ -87,6 +98,14 @@ def add_attributes(
8798
"""
8899
with self._lock:
89100
for key, value in attributes.items():
101+
if not isinstance(value, (str, int, float, bool)):
102+
_logger.warning(
103+
"Skipping attribute '%s': value must be str, int, float, or bool, got %s",
104+
key,
105+
type(value).__name__,
106+
)
107+
continue
108+
90109
if (
91110
len(self._attributes) >= self._max_custom_attrs
92111
and key not in self._attributes

opentelemetry-instrumentation/tests/test_labeler.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import contextvars
1717
import threading
1818
import unittest
19+
from unittest.mock import patch
1920

2021
from opentelemetry.instrumentation._labeler import (
2122
Labeler,
@@ -76,6 +77,123 @@ def test_clear_attributes(self):
7677
self.assertEqual(labeler.get_attributes(), {})
7778
self.assertEqual(len(labeler), 0)
7879

80+
def test_add_valid_types(self):
81+
labeler = Labeler()
82+
labeler.add("str_key", "string_value")
83+
labeler.add("int_key", 42)
84+
labeler.add("float_key", 3.14)
85+
labeler.add("bool_true_key", True)
86+
labeler.add("bool_false_key", False)
87+
88+
attributes = labeler.get_attributes()
89+
expected = {
90+
"str_key": "string_value",
91+
"int_key": 42,
92+
"float_key": 3.14,
93+
"bool_true_key": True,
94+
"bool_false_key": False,
95+
}
96+
self.assertEqual(attributes, expected)
97+
self.assertEqual(len(labeler), 5)
98+
99+
def test_add_invalid_types_logs_warning_and_skips(self):
100+
labeler = Labeler()
101+
102+
with patch(
103+
"opentelemetry.instrumentation._labeler._internal._logger.warning"
104+
) as mock_warning:
105+
labeler.add("valid", "value")
106+
107+
labeler.add("dict_key", {"nested": "dict"})
108+
labeler.add("list_key", [1, 2, 3])
109+
labeler.add("none_key", None)
110+
labeler.add("tuple_key", (1, 2))
111+
labeler.add("set_key", {1, 2, 3})
112+
113+
labeler.add("another_valid", 123)
114+
115+
self.assertEqual(mock_warning.call_count, 5)
116+
warning_calls = [call[0] for call in mock_warning.call_args_list]
117+
self.assertIn("dict_key", str(warning_calls[0]))
118+
self.assertIn("dict", str(warning_calls[0]))
119+
self.assertIn("list_key", str(warning_calls[1]))
120+
self.assertIn("list", str(warning_calls[1]))
121+
self.assertIn("none_key", str(warning_calls[2]))
122+
self.assertIn("NoneType", str(warning_calls[2]))
123+
124+
attributes = labeler.get_attributes()
125+
expected = {"valid": "value", "another_valid": 123}
126+
self.assertEqual(attributes, expected)
127+
self.assertEqual(len(labeler), 2)
128+
129+
def test_add_attributes_valid_types(self):
130+
labeler = Labeler()
131+
attrs = {
132+
"str_key": "string_value",
133+
"int_key": 42,
134+
"float_key": 3.14,
135+
"bool_true_key": True,
136+
"bool_false_key": False,
137+
}
138+
labeler.add_attributes(attrs)
139+
attributes = labeler.get_attributes()
140+
self.assertEqual(attributes, attrs)
141+
self.assertEqual(len(labeler), 5)
142+
143+
def test_add_attributes_invalid_types_logs_and_skips(self):
144+
labeler = Labeler()
145+
146+
with patch(
147+
"opentelemetry.instrumentation._labeler._internal._logger.warning"
148+
) as mock_warning:
149+
mixed_attrs = {
150+
"valid_str": "value",
151+
"invalid_dict": {"nested": "dict"},
152+
"valid_int": 42,
153+
"invalid_list": [1, 2, 3],
154+
"valid_bool": True,
155+
"invalid_none": None,
156+
}
157+
labeler.add_attributes(mixed_attrs)
158+
159+
self.assertEqual(mock_warning.call_count, 3)
160+
warning_calls = [str(call) for call in mock_warning.call_args_list]
161+
self.assertTrue(any("invalid_dict" in call for call in warning_calls))
162+
self.assertTrue(any("invalid_list" in call for call in warning_calls))
163+
self.assertTrue(any("invalid_none" in call for call in warning_calls))
164+
attributes = labeler.get_attributes()
165+
expected = {
166+
"valid_str": "value",
167+
"valid_int": 42,
168+
"valid_bool": True,
169+
}
170+
self.assertEqual(attributes, expected)
171+
self.assertEqual(len(labeler), 3)
172+
173+
def test_add_attributes_all_invalid_types(self):
174+
"""Test add_attributes when all types are invalid"""
175+
labeler = Labeler()
176+
177+
with patch(
178+
"opentelemetry.instrumentation._labeler._internal._logger.warning"
179+
) as mock_warning:
180+
invalid_attrs = {
181+
"dict_key": {"nested": "dict"},
182+
"list_key": [1, 2, 3],
183+
"none_key": None,
184+
"custom_obj": object(),
185+
}
186+
187+
labeler.add_attributes(invalid_attrs)
188+
189+
# Should have logged warnings for all 4 invalid attributes
190+
self.assertEqual(mock_warning.call_count, 4)
191+
192+
# No attributes should be stored
193+
attributes = labeler.get_attributes()
194+
self.assertEqual(attributes, {})
195+
self.assertEqual(len(labeler), 0)
196+
79197
def test_thread_safety(self):
80198
labeler = Labeler(max_custom_attrs=1100) # 11 * 100
81199
num_threads = 10

0 commit comments

Comments
 (0)