Skip to content

Commit 1d21f9b

Browse files
committed
fix(celery): coerce non-string values in CeleryGetter.get()
Celery's Context copies all message properties as instance attributes via __dict__.update(), including non-string values like timelimit (tuple of ints) and priority (int). The TextMapPropagator contract requires string values from Getter.get(), but CeleryGetter returned these non-string values as-is, causing TraceState.from_header() to crash with TypeError when calling re.split() on an int. Coerce all non-string values to strings before returning them. See #4359
1 parent 822cd77 commit 1d21f9b

2 files changed

Lines changed: 43 additions & 2 deletions

File tree

instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,16 @@ def get(self, carrier, key):
104104
value = getattr(carrier, key, None)
105105
if value is None:
106106
return None
107-
if isinstance(value, str) or not isinstance(value, Iterable):
107+
# Celery's Context copies all message properties as instance
108+
# attributes, including non-string values like timelimit (tuple
109+
# of ints). The TextMapPropagator contract requires string
110+
# values, so coerce anything that isn't already a string.
111+
if isinstance(value, str):
108112
value = (value,)
113+
elif isinstance(value, Iterable):
114+
value = tuple(str(v) if not isinstance(v, str) else v for v in value)
115+
else:
116+
value = (str(value),)
109117
return value
110118

111119
def keys(self, carrier):

instrumentation/opentelemetry-instrumentation-celery/tests/test_getter.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,40 @@ def test_get_iter(self):
3636
getter = CeleryGetter()
3737
mock_obj.test = ["val"]
3838
val = getter.get(mock_obj, "test")
39-
self.assertEqual(val, ["val"])
39+
self.assertEqual(val, ("val",))
40+
41+
def test_get_int(self):
42+
"""Non-string scalar values should be coerced to strings.
43+
44+
Celery's Context stores some attributes as ints (e.g. priority).
45+
The TextMapPropagator contract requires string values; passing
46+
an int to re.split() in TraceState.from_header() causes a
47+
TypeError.
48+
"""
49+
mock_obj = mock.Mock()
50+
getter = CeleryGetter()
51+
mock_obj.test = 42
52+
val = getter.get(mock_obj, "test")
53+
self.assertEqual(val, ("42",))
54+
55+
def test_get_iter_with_non_string_elements(self):
56+
"""Iterable values containing non-strings should be coerced.
57+
58+
Celery's timelimit attribute is a tuple of ints, e.g. (300, 60).
59+
"""
60+
mock_obj = mock.Mock()
61+
getter = CeleryGetter()
62+
mock_obj.test = (300, 60)
63+
val = getter.get(mock_obj, "test")
64+
self.assertEqual(val, ("300", "60"))
65+
66+
def test_get_iter_with_mixed_types(self):
67+
"""Iterables with a mix of strings and non-strings."""
68+
mock_obj = mock.Mock()
69+
getter = CeleryGetter()
70+
mock_obj.test = ["val", 123]
71+
val = getter.get(mock_obj, "test")
72+
self.assertEqual(val, ("val", "123"))
4073

4174
def test_keys(self):
4275
getter = CeleryGetter()

0 commit comments

Comments
 (0)