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
Description
CeleryGetter.get()can return non-string values (e.g.int) from Celery task request attributes, which causesTraceState.from_header()to crash with:Stack trace
Root cause
CeleryGetter.get()usesgetattr(carrier, key, None)againsttask.request, which is a CeleryContextobject. Celery'sContext.__init__copies all message properties as instance attributes viaself.__dict__.update(), including numeric values liketimelimit.When the value is an
int, the existing type check wraps it in a tuple but doesn't coerce to string:The
TextMapPropagatorcontract expects string values, andTraceState.from_header()passes each value directly tore.split()without type-checking.Suggested fix
Coerce non-string values to strings in
CeleryGetter.get():Alternatively,
TraceState.from_header()could validate input types before callingre.split().Environment
Related
soft_time_limitortime_limit) leads to warning: "Attribute 'celery.timelimit' mixes types str and int in attribute value sequence" #2022 (timelimit str/int mixing in span attributes — same root cause, different code path)