Description
Describe your environment
MacOS
python 3.12
opentelemetry-instrumentation-sqlalchemy v0.53b1
opentelemetry-api v1.32.1
opentelemetry-instrumentation v0.53b1
opentelemetry-semantic-conventions v0.53b1
What happened?
We have some healthcheck functions that produce spans from sqlalchemy instrumentation we would like to suppress.
In order to do that, I guess it is best practice to use the suppress_instrumentation
method from opentelemetry.instrumentations.util
module. However, the SQLAlchemy instrumentation does not respect this and still produces spans and metrics.
Steps to Reproduce
using uv you can simply run this as is with uv run reproduce.py
reproduce.py:
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "opentelemetry-api",
# "opentelemetry-instrumentation-sqlalchemy",
# "opentelemetry-sdk",
# "sqlalchemy",
# ]
# ///
from opentelemetry import trace
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from opentelemetry.instrumentation.utils import suppress_instrumentation
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from sqlalchemy import create_engine
exporter = ConsoleSpanExporter()
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
engine = create_engine("sqlite:///:memory:")
SQLAlchemyInstrumentor().instrument(engine=engine)
with engine.connect(): # produces spans
pass
with suppress_instrumentation(), engine.connect(): # should not produce spans but does
pass
Expected Result
OTEL signals should not be produced when suppress_instrumentation is used.
Actual Result
OTEL signals are produced when suppress_instrumentation is used
Additional context
A fix can be implemented by using the is_instrumentation_enabled
from opentelemetry.instrumentation.utils
module everywhere in the engine.py module, i.e.:
engine.py
def _wrap_connect(tracer):
# pylint: disable=unused-argument
def _wrap_connect_internal(func, module, args, kwargs):
if not is_instrumentation_enabled(): # this is new
return func(*args, **kwargs)
with tracer.start_as_current_span(
"connect", kind=trace.SpanKind.CLIENT
) as span:
if span.is_recording():
attrs, _ = _get_attributes_from_url(module.url)
span.set_attributes(attrs)
span.set_attribute(
SpanAttributes.DB_SYSTEM, _normalize_vendor(module.name)
)
return func(*args, **kwargs)
return _wrap_connect_internal
If i use the code above and the reproduction script, I get the expected result where only 1 span is produced.
Would you like to implement a fix?
Yes