OpenTelemetry DBAPI Instrumentation
The `opentelemetry-instrumentation-dbapi` library provides OpenTelemetry tracing for Python applications interacting with databases via libraries that adhere to the Python Database API Specification v2.0 (PEP 249). It's part of the `opentelemetry-python-contrib` project, which typically follows a monthly release cadence. The current version, `0.61b0`, signifies that it is still in beta, and while functional, its API or behavior may be subject to change. This instrumentation offers core functionality for database tracing, and while users often prefer framework or ORM-specific instrumentations, it can be used directly when those are not available.
Warnings
- breaking OpenTelemetry Python contrib packages, including `opentelemetry-instrumentation-dbapi`, are often in beta (indicated by `b0` suffix). This means API changes and breaking modifications can occur between minor versions without strict adherence to semantic versioning until a stable `1.0.0` release. Semantic conventions, which define attribute names and structures, also evolve and may require updates to instrumentation configurations. For example, the `db.statement` attribute's inclusion of sqlcomment became opt-in in previous versions.
- gotcha Incorrectly identifying the `connect` method name for your DBAPI driver is a common mistake. For instance, `pyodbc`'s connection function is `connect`, not `Connection`. Using the wrong method name will result in instrumentation failing silently or partially.
- gotcha The `sqlcommenter` feature, which enriches SQL queries with OpenTelemetry context for better database-side observability, is disabled by default. If not explicitly enabled, trace context will not be appended to SQL queries, hindering end-to-end trace correlation if the database logs are consumed by an observability backend.
- gotcha When using pre-forking servers (e.g., Gunicorn with multiple workers), OpenTelemetry automatic instrumentation, especially for metrics, can lead to inconsistencies. The forking process can create issues with background threads and locks in SDK components like `PeriodicExportingMetricReader`, potentially causing missing or incorrect metrics.
- gotcha In some older versions (e.g., `0.53b1`), the `opentelemetry-instrumentation-dbapi` might not correctly respect the `suppress_instrumentation` context manager. This can lead to spans being generated even when you intend to suppress them.
Install
-
pip install opentelemetry-instrumentation-dbapi opentelemetry-sdk mysql-connector-python
Imports
- trace_integration
from opentelemetry.instrumentation.dbapi import trace_integration
Quickstart
import os
import mysql.connector
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
from opentelemetry.instrumentation.dbapi import trace_integration
# Configure OpenTelemetry SDK
resource = {"service.name": os.environ.get('OTEL_SERVICE_NAME', 'dbapi-example')}
tracer_provider = TracerProvider.from_resource_attributes(resource)
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(tracer_provider)
# Get a tracer
tracer = trace.get_tracer(__name__)
# Instrument the database connector (e.g., mysql.connector)
# Pass the module, the name of its connect method, and the database system identifier
trace_integration(mysql.connector, "connect", "mysql")
try:
# Establish a connection using the instrumented module
connection = mysql.connector.connect(
host=os.environ.get('MYSQL_HOST', 'localhost'),
user=os.environ.get('MYSQL_USER', 'root'),
password=os.environ.get('MYSQL_PASSWORD', 'password'),
database=os.environ.get('MYSQL_DATABASE', 'testdb')
)
with tracer.start_as_current_span("db-operations"):
cursor = connection.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))")
cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
connection.commit()
cursor.execute("SELECT * FROM users")
result = cursor.fetchall()
print(f"Fetched result: {result}")
cursor.close()
except Exception as e:
print(f"An error occurred: {e}")
finally:
if 'connection' in locals() and connection.is_connected():
connection.close()
print("Application finished.")