Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- `opentelemetry-instrumentation-celery`: Coerce non-string values to strings in `CeleryGetter.get()` to prevent `TypeError` in `TraceState.from_header()` when Celery request attributes contain ints
([#4360](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4360))
- `opentelemetry-docker-tests`: Replace deprecated `SpanAttributes` from `opentelemetry.semconv.trace` with `opentelemetry.semconv._incubating.attributes`
([#4339](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4339))
- `opentelemetry-instrumentation-confluent-kafka`: Skip `recv` span creation when `poll()` returns no message or `consume()` returns an empty list, avoiding empty spans on idle polls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,18 @@ def get(self, carrier, key):
value = getattr(carrier, key, None)
if value is None:
return None
if isinstance(value, str) or not isinstance(value, Iterable):
# Celery's Context copies all message properties as instance
# attributes, including non-string values like timelimit (tuple
# of ints). The TextMapPropagator contract requires string
# values, so coerce anything that isn't already a string.
if isinstance(value, str):
value = (value,)
elif isinstance(value, Iterable):
value = tuple(
str(v) if not isinstance(v, str) else v for v in value
)
else:
value = (str(value),)
return value

def keys(self, carrier):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,40 @@ def test_get_iter(self):
getter = CeleryGetter()
mock_obj.test = ["val"]
val = getter.get(mock_obj, "test")
self.assertEqual(val, ["val"])
self.assertEqual(val, ("val",))

def test_get_int(self):
"""Non-string scalar values should be coerced to strings.

Celery's Context stores some attributes as ints (e.g. priority).
The TextMapPropagator contract requires string values; passing
an int to re.split() in TraceState.from_header() causes a
TypeError.
"""
mock_obj = mock.Mock()
getter = CeleryGetter()
mock_obj.test = 42
val = getter.get(mock_obj, "test")
self.assertEqual(val, ("42",))

def test_get_iter_with_non_string_elements(self):
"""Iterable values containing non-strings should be coerced.

Celery's timelimit attribute is a tuple of ints, e.g. (300, 60).
"""
mock_obj = mock.Mock()
getter = CeleryGetter()
mock_obj.test = (300, 60)
val = getter.get(mock_obj, "test")
self.assertEqual(val, ("300", "60"))

def test_get_iter_with_mixed_types(self):
"""Iterables with a mix of strings and non-strings."""
mock_obj = mock.Mock()
getter = CeleryGetter()
mock_obj.test = ["val", 123]
val = getter.get(mock_obj, "test")
self.assertEqual(val, ("val", "123"))

def test_keys(self):
getter = CeleryGetter()
Expand Down
Loading