Describe your environment
OS: Ubuntu
Python version: Python 3.11.8
Package version: v0.63b1
What happened?
PyramidInstrumentor registers duplicate _before_traversal event subscribers when any Pyramid library
calls config.commit() during its includeme. This causes OpenTelemetry context corruption — child spans
(DB, cache, HTTP) become orphaned as separate traces instead of parenting to the request span.
_traced_init wraps Configurator.init and unconditionally calls
instance.include("opentelemetry.instrumentation.pyramid.callbacks"). Pyramid's processSpec deduplication
(via action_state._seen_files) is supposed to prevent double-registration, but config.commit() replaces
action_state with a fresh instance, clearing _seen_files.
After any commit(), the next config.include() creates a child Configurator → triggers _traced_init →
processSpec passes on the new empty _seen_files → _before_traversal is registered again.
Steps to Reproduce
"""
Demonstrates the opentelemetry-instrumentation-pyramid double subscriber bug.
When any library calls config.commit() during configuration, the OTEL pyramid
instrumentation registers its _before_traversal subscriber a second time. This
causes child spans (DB, cache, HTTP calls) to be orphaned as separate traces.
Requirements:
pip install pyramid opentelemetry-instrumentation-pyramid
Run:
python otel_pyramid_double_subscriber.py
Expected output:
Without commit: 1 _before_traversal subscriber(s) - OK
With commit: 2 _before_traversal subscriber(s) - BUG: double registration!
"""
from opentelemetry.instrumentation.pyramid import PyramidInstrumentor
PyramidInstrumentor().instrument()
from pyramid.config import Configurator
def library_that_commits(config):
"""Simulates any library that calls config.commit() in its includeme.
This is what tomb_routes does on every add_simple_route call."""
config.add_route("example", "/example")
config.commit()
def another_library(config):
"""Any subsequent config.include() after the commit triggers the bug."""
pass
registrations = []
from pyramid.config.adapters import AdaptersConfiguratorMixin
_orig_add_subscriber = AdaptersConfiguratorMixin.add_subscriber
def _counting_add_subscriber(self, subscriber, iface=None, **kw):
if "before_traversal" in getattr(subscriber, "__name__", ""):
registrations.append(subscriber)
return _orig_add_subscriber(self, subscriber, iface=iface, **kw)
AdaptersConfiguratorMixin.add_subscriber = _counting_add_subscriber
# --- Without commit: works correctly ---
registrations.clear()
config1 = Configurator(settings={})
config1.include(another_library)
count1 = len(registrations)
print(f"Without commit: {count1} _before_traversal subscriber(s) - {'OK' if count1 == 1 else 'BUG'}")
# --- With commit: triggers the bug ---
registrations.clear()
config2 = Configurator(settings={})
config2.include(library_that_commits)
config2.include(another_library)
count2 = len(registrations)
print(f"With commit: {count2} _before_traversal subscriber(s) - {'OK' if count2 == 1 else 'BUG: double registration!'}")
PyramidInstrumentor().uninstrument()
Expected Result
Only one BeforeTraversal subscriber should be registered.
Actual Result
When any library calls config.commit() the action state is cleared causing the traced init to subscribe the BeforeTraversal subscriber a second time on the next config.include()
Additional context
No response
Would you like to implement a fix?
No
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.
Describe your environment
OS: Ubuntu
Python version: Python 3.11.8
Package version: v0.63b1
What happened?
PyramidInstrumentor registers duplicate _before_traversal event subscribers when any Pyramid library
calls config.commit() during its includeme. This causes OpenTelemetry context corruption — child spans
(DB, cache, HTTP) become orphaned as separate traces instead of parenting to the request span.
_traced_init wraps Configurator.init and unconditionally calls
instance.include("opentelemetry.instrumentation.pyramid.callbacks"). Pyramid's processSpec deduplication
(via action_state._seen_files) is supposed to prevent double-registration, but config.commit() replaces
action_state with a fresh instance, clearing _seen_files.
After any commit(), the next config.include() creates a child Configurator → triggers _traced_init →
processSpec passes on the new empty _seen_files → _before_traversal is registered again.
Steps to Reproduce
Expected Result
Only one BeforeTraversal subscriber should be registered.
Actual Result
When any library calls config.commit() the action state is cleared causing the traced init to subscribe the BeforeTraversal subscriber a second time on the next config.include()
Additional context
No response
Would you like to implement a fix?
No
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it. Learn more here.