Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ inject a `requests.Session` or `grpc.ChannelCredentials` object into OTLP export
([#4735](https://github.com/open-telemetry/opentelemetry-python/pull/4735))
- opentelemetry-sdk: fix calling Logger.emit with an API LogRecord instance
([#4741](https://github.com/open-telemetry/opentelemetry-python/pull/4741))
- docs: Added sqlcommenter example
([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734))

## Version 1.36.0/0.57b0 (2025-07-29)

Expand Down
124 changes: 124 additions & 0 deletions docs/examples/sqlcommenter/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
sqlcommenter
============

This is an example of how to use OpenTelemetry Python instrumention with
sqlcommenter to enrich database query statements with contextual information.
For more information on sqlcommenter concepts, see:

* `Semantic Conventions - Database Spans <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#sql-commenter>`_
* `sqlcommenter <https://google.github.io/sqlcommenter/>`_

The source files of this example are available `here <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/sqlcommenter/>`_.
This example uses Docker to manage a database server and OpenTelemetry collector.

Run MySQL server
----------------

A running MySQL server with general logs enabled will store query statements with context resulting from the sqlcommenter feature enabled in this example.

.. code-block:: sh

cd books_database
docker build -t books-db .
docker run -d --name books-db -p 3366:3306 books-db
cd ..

Check that the run is working and the general log is available:

.. code-block:: sh

docker exec -it books-db tail -f /var/log/general.log

Run OpenTelemetry Collector
---------------------------

Running the OpenTelemetry collector will show the MySQL instrumentor's
comment-in-span-attribute feature, which this example has also enabled.

.. code-block:: sh

docker run \
-p 4317:4317 \
-v $(pwd)/collector-config.yaml:/etc/otel/config.yaml \
otel/opentelemetry-collector-contrib:latest

Run the sqlcommenter example
----------------------------

Set up and activate a Python virtual environment. Install these
dependencies of the sqlcommenter example:

.. code-block:: sh

pip install opentelemetry-sdk \
opentelemetry-exporter-otlp-proto-grpc \
opentelemetry-instrumentation-mysql \
mysql-connector-python

Then, run this script, which instruments all mysql-connector calls with
two sqlcommenter features opted in.

.. code-block:: sh

python instrumented_query.py

Note that OpenTelemetry instrumentation with sqlcommenter is also
available for other Python database client drivers/object relation
mappers (ORMs). See full list at `instrumentation`_.

.. _instrumentation: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation

Check MySQL server general log and spans for sqlcomment
-------------------------------------------------------

After running the query script, check the MySQL general log contents:

.. code-block:: sh

docker exec -it books-db tail -f /var/log/general.log

For each instrumented ``SELECT`` call, a query was made and logged with
a sqlcomment appended. For example:

.. code::

2025-09-02T18:49:06.981980Z 186 Query SELECT * FROM authors WHERE id = 1 /*db_driver='mysql.connector%%3A9.4.0',dbapi_level='2.0',dbapi_threadsafety=1,driver_paramstyle='pyformat',mysql_client_version='9.4.0',traceparent='00-2c45248f2beefdd9688b0a94eb4ac9ee-4f3af9a825aae9b1-01'*/

In the running OpenTelemetry collector, you'll also see one span per
``SELECT`` call. Each of those span's trace ID and span ID will
correspond to a query log sqlcomment. With the comment-in-attribute
feature enabled, the span's ``db.statement`` attribute will also contain
the sqlcomment. For example:

.. code::

ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.11.0
InstrumentationScope opentelemetry.instrumentation.mysql 0.57b0
Span #0
Trace ID : 2c45248f2beefdd9688b0a94eb4ac9ee
Parent ID :
ID : 4f3af9a825aae9b1
Name : SELECT
Kind : Client
Start time : 2025-09-02 18:49:06.982341 +0000 UTC
End time : 2025-09-02 18:49:06.98463 +0000 UTC
Status code : Unset
Status message :
Attributes:
-> db.system: Str(mysql)
-> db.name: Str(books)
-> db.statement: Str(SELECT * FROM authors WHERE id = %s /*db_driver='mysql.connector%%3A9.4.0',dbapi_level='2.0',dbapi_threadsafety=1,driver_paramstyle='pyformat',mysql_client_version='9.4.0',traceparent='00-2c45248f2beefdd9688b0a94eb4ac9ee-4f3af9a825aae9b1-01'*/)
-> db.user: Str(books)
-> net.peer.name: Str(localhost)
-> net.peer.port: Int(3366)


References
----------

* `OpenTelemetry Project <https://opentelemetry.io/>`_
* `OpenTelemetry Collector <https://github.com/open-telemetry/opentelemetry-collector>`_
* `OpenTelemetry MySQL instrumentation <https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-mysql>`_
* `Semantic Conventions - Database Spans <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#sql-commenter>`_
* `sqlcommenter <https://google.github.io/sqlcommenter/>`_
23 changes: 23 additions & 0 deletions docs/examples/sqlcommenter/books_database/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM mysql:8.0

ENV MYSQL_ROOT_PASSWORD=root
ENV MYSQL_DATABASE=books

ADD books.sql /docker-entrypoint-initdb.d/

RUN echo "CREATE USER IF NOT EXISTS 'books'@'%' IDENTIFIED WITH mysql_native_password BY 'books123';" > /docker-entrypoint-initdb.d/01-create-user.sql && \
echo "GRANT ALL PRIVILEGES ON books.* TO 'books'@'%';" >> /docker-entrypoint-initdb.d/01-create-user.sql && \
echo "FLUSH PRIVILEGES;" >> /docker-entrypoint-initdb.d/01-create-user.sql

# Prepare general logs
RUN mkdir -p /var/log && \
touch /var/log/general.log && \
chown mysql:mysql /var/log/general.log

EXPOSE 3306

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD mysqladmin ping -p${MYSQL_ROOT_PASSWORD} || exit 1

# Start MySQL with general logging enabled and compatible authentication
CMD ["mysqld", "--general-log=1", "--general-log-file=/var/log/general.log"]
72 changes: 72 additions & 0 deletions docs/examples/sqlcommenter/books_database/books.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
-- MySQL dump for Books Database
-- Database: books_db
-- Generated on: 2025-08-29

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";

-- Database: `books`
CREATE DATABASE IF NOT EXISTS `books` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `books`;

-- --------------------------------------------------------

-- Table structure for table `authors`

DROP TABLE IF EXISTS `authors`;
CREATE TABLE `authors` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`home_town` varchar(255) DEFAULT NULL,
`birthdate` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dumping data for table `authors`

INSERT INTO `authors` (`id`, `name`, `home_town`, `birthdate`) VALUES
(1, 'Frank Herbert', 'Tacoma, Washington', '1920-10-08'),
(2, 'Isaac Asimov', 'Petrovichi, Russia', '1920-01-02'),
(3, 'Terry Pratchett', 'Beaconsfield, England', '1948-04-28');

-- --------------------------------------------------------

-- Table structure for table `books`

DROP TABLE IF EXISTS `books`;
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`author_id` int(11) NOT NULL,
`year_published` int(4) DEFAULT NULL,
`genre` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_author` (`author_id`),
CONSTRAINT `fk_author` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dumping data for table `books`

INSERT INTO `books` (`id`, `title`, `author_id`, `year_published`, `genre`) VALUES
(1, 'Dune', 1, 1965, 'Science Fiction'),
(2, 'Foundation', 2, 1951, 'Science Fiction'),
(3, 'The Colour of Magic', 3, 1983, 'Fantasy Comedy');

-- --------------------------------------------------------

-- Additional books to show the many-to-one relationship

INSERT INTO `books` (`id`, `title`, `author_id`, `year_published`, `genre`) VALUES
(4, 'Dune Messiah', 1, 1969, 'Science Fiction'),
(5, 'I, Robot', 2, 1950, 'Science Fiction'),
(6, 'Good Omens', 3, 1990, 'Fantasy Comedy');

-- --------------------------------------------------------

-- Auto increment values

ALTER TABLE `authors` AUTO_INCREMENT = 4;
ALTER TABLE `books` AUTO_INCREMENT = 7;

COMMIT;
19 changes: 19 additions & 0 deletions docs/examples/sqlcommenter/collector-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317

exporters:
debug:
verbosity: detailed

processors:
batch:

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
65 changes: 65 additions & 0 deletions docs/examples/sqlcommenter/instrumented_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from mysql.connector import connect

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

resource = Resource.create(
attributes={
"service.name": "sqlcommenter-example",
}
)
trace.set_tracer_provider(TracerProvider(resource=resource))
span_processor = BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://localhost:4317")
)
trace.get_tracer_provider().add_span_processor(span_processor)

cnx = connect(
host="localhost",
port=3366,
user="books",
password="books123",
database="books",
)

# Instruments MySQL queries with sqlcommenter enabled
# and comment-in-span-attribute enabled.
# Returns wrapped connection to generate traces.
cnx = MySQLInstrumentor().instrument_connection(
connection=cnx,
enable_commenter=True,
enable_attribute_commenter=True,
)

cursor = cnx.cursor()
statement = "SELECT * FROM authors WHERE id = %s"

# Each SELECT query generates one mysql log with sqlcomment
# and one OTel span with `db.statement` attribute that also
# includes sqlcomment.
for cid in range(1, 4):
cursor.execute(statement, (cid,))
rows = cursor.fetchall()
print(f"Found author: {rows[0]}")

print("Done.")
Loading