Skip to content

Self-referential attribute value crashes the instrumented application with RecursionError #5564

Description

@dwin-gharibi

Describe your environment

Labels: bug, api, sdk, crash
Affected packages: opentelemetry-api, opentelemetry-sdk
Found on: main @ 0a5d76b6
Environment: CPython 3.12

What happened?

_clean_attribute_value walks nested Sequence and Mapping attribute values recursively with no cycle detection and no depth bound. A self-referential list or dict recurses until the interpreter stack is exhausted, and the resulting RecursionError propagates out of the public telemetry API into the calling application.

This became reachable when AnyValue was widened to accept arbitrarily nested values. Previously such an object would simply have been stringified.

Steps to Reproduce

from opentelemetry.sdk.trace import TracerProvider

cyclic = [1, 2]
cyclic.append(cyclic)          # a list that contains itself

tracer = TracerProvider().get_tracer(__name__)
with tracer.start_as_current_span("span") as span:
    span.set_attribute("k", cyclic)

Expected Result

The unusable value is dropped and recorded as None with a warning, the surrounding attributes are preserved, and no exception reaches the caller.

Actual Result

RecursionError: maximum recursion depth exceeded

# every public entry point is affected
span.set_attribute('k', cyclic_list)          RecursionError ESCAPES into caller
span.set_attribute('k', cyclic_dict)          RecursionError ESCAPES into caller
span.set_attributes({'k': cyclic_list})       RecursionError ESCAPES into caller
span.add_event('e', {'k': cyclic_list})       RecursionError ESCAPES into caller
Resource.create({'k': cyclic_list})           RecursionError ESCAPES into caller
counter.add(1, {'k': cyclic_list})            RecursionError ESCAPES into caller
logger.emit(attributes={'k': cyclic_list})    RecursionError ESCAPES into caller

# a deeply nested but acyclic value hits the same wall
span.set_attribute('k', 2000-deep nested list) RecursionError ESCAPES into caller

Additional context

An observability library can take down the process it is observing. The value does not have to be deliberately cyclic: any object graph with a back-reference - a parent pointer, a memoisation cache, a linked structure - will do, and AnyValue now invites callers to pass exactly those. Because the exception surfaces at the instrumentation call site rather than in an exporter thread, it is not caught by the SDK's usual broad exception handling.

Would you like to implement a fix?

Yes

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions