Skip to content

CeleryGetter.get() returns non-string values, crashes TraceContext propagator #4359

@DrMeers

Description

@DrMeers

Description

CeleryGetter.get() can return non-string values (e.g. int) from Celery task request attributes, which causes TraceState.from_header() to crash with:

TypeError: expected string or bytes-like object, got 'int'

Stack trace

celery/utils/dispatch/signal.py:280 in send
opentelemetry/instrumentation/celery/__init__.py:174 in _trace_prerun
opentelemetry/propagate/__init__.py:101 in extract
opentelemetry/propagators/composite.py:52 in extract
opentelemetry/trace/propagation/tracecontext.py:76 in extract
opentelemetry/trace/span.py:386 in from_header
re/__init__.py:207 in split
TypeError: expected string or bytes-like object, got 'int'

Root cause

CeleryGetter.get() uses getattr(carrier, key, None) against task.request, which is a Celery Context object. Celery's Context.__init__ copies all message properties as instance attributes via self.__dict__.update(), including numeric values like timelimit.

When the value is an int, the existing type check wraps it in a tuple but doesn't coerce to string:

class CeleryGetter(Getter):
    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):
            value = (value,)  # wraps int in tuple, but doesn't convert to str
        return value

The TextMapPropagator contract expects string values, and TraceState.from_header() passes each value directly to re.split() without type-checking.

Suggested fix

Coerce non-string values to strings in CeleryGetter.get():

def get(self, carrier, key):
    value = getattr(carrier, key, None)
    if value is None:
        return None
    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

Alternatively, TraceState.from_header() could validate input types before calling re.split().

Environment

  • opentelemetry-instrumentation-celery 0.61b0
  • opentelemetry-sdk 1.x
  • celery 5.x
  • Python 3.12

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions