From 3aa48b3fe590e3ac2fab43fbe90df470645cfa53 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Fri, 29 Aug 2025 20:01:32 -0700 Subject: [PATCH 1/8] Add sqlcommenter docs/example --- docs/examples/sqlcommenter/README.rst | 73 +++++++++++++++++++ .../sqlcommenter/books_database/Dockerfile | 21 ++++++ .../sqlcommenter/books_database/books.sql | 72 ++++++++++++++++++ .../sqlcommenter/collector-config.yaml | 19 +++++ .../sqlcommenter/instrumented_query.py | 68 +++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 docs/examples/sqlcommenter/README.rst create mode 100644 docs/examples/sqlcommenter/books_database/Dockerfile create mode 100644 docs/examples/sqlcommenter/books_database/books.sql create mode 100644 docs/examples/sqlcommenter/collector-config.yaml create mode 100644 docs/examples/sqlcommenter/instrumented_query.py diff --git a/docs/examples/sqlcommenter/README.rst b/docs/examples/sqlcommenter/README.rst new file mode 100644 index 00000000000..47fc8053de6 --- /dev/null +++ b/docs/examples/sqlcommenter/README.rst @@ -0,0 +1,73 @@ +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 `_ +* `sqlcommenter `_ + +The source files of this example are available `here `_. +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 3306:3306 books-db + cd .. + +Check that the run worked 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 that the example has also been 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 + pip install mysql-connector-python + +Then, run this script, which instruments all mysql-connector calls with +two sqlcommenter features opted in. For each ``SELECT`` call, a query is +made with a sqlcomment appended and one OTel span with ``db.statement`` +attribute is also generated with sqlcomment. + +.. code-block:: sh + + python instrumented_query.py + + +References +---------- + +* `OpenTelemetry Project `_ +* `OpenTelemetry Collector `_ +* `OpenTelemetry MySQL instrumentation `_ \ No newline at end of file diff --git a/docs/examples/sqlcommenter/books_database/Dockerfile b/docs/examples/sqlcommenter/books_database/Dockerfile new file mode 100644 index 00000000000..ce35028e03d --- /dev/null +++ b/docs/examples/sqlcommenter/books_database/Dockerfile @@ -0,0 +1,21 @@ +FROM mysql:8.0 + +ENV MYSQL_ROOT_PASSWORD=root +ENV MYSQL_DATABASE=books +ENV MYSQL_USER=books +ENV MYSQL_PASSWORD=books123 + +ADD books.sql /docker-entrypoint-initdb.d/ + +# 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", "--default-authentication-plugin=mysql_native_password"] \ No newline at end of file diff --git a/docs/examples/sqlcommenter/books_database/books.sql b/docs/examples/sqlcommenter/books_database/books.sql new file mode 100644 index 00000000000..845bc458bc3 --- /dev/null +++ b/docs/examples/sqlcommenter/books_database/books.sql @@ -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; \ No newline at end of file diff --git a/docs/examples/sqlcommenter/collector-config.yaml b/docs/examples/sqlcommenter/collector-config.yaml new file mode 100644 index 00000000000..389d0814c9a --- /dev/null +++ b/docs/examples/sqlcommenter/collector-config.yaml @@ -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] diff --git a/docs/examples/sqlcommenter/instrumented_query.py b/docs/examples/sqlcommenter/instrumented_query.py new file mode 100644 index 00000000000..e4835f8405c --- /dev/null +++ b/docs/examples/sqlcommenter/instrumented_query.py @@ -0,0 +1,68 @@ +# 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) + +# Instrument MySQL queries with sqlcommenter enabled +# and comment-in-span-attribute enabled +MySQLInstrumentor().instrument( + enable_commenter=True, + enable_attribute_commenter=True, +) + +cnx = connect( + host="127.0.0.1", + port=3306, + user="books", + password="books123", + database="books", +) + +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, 3): + cursor.execute(statement, (cid,)) + rows = cursor.fetchall() + print(f"Found author: {rows[0]}") + +print("Done.") From 1e3cc9bab3c19f283398217f7cebf08ef613a918 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Fri, 29 Aug 2025 20:42:59 -0700 Subject: [PATCH 2/8] Change port --- docs/examples/sqlcommenter/README.rst | 2 +- docs/examples/sqlcommenter/instrumented_query.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples/sqlcommenter/README.rst b/docs/examples/sqlcommenter/README.rst index 47fc8053de6..d880a59b3c7 100644 --- a/docs/examples/sqlcommenter/README.rst +++ b/docs/examples/sqlcommenter/README.rst @@ -20,7 +20,7 @@ A running MySQL server with general logs enabled will store query statements wit cd books_database docker build -t books-db . - docker run -d --name books-db -p 3306:3306 books-db + docker run -d --name books-db -p 3366:3306 books-db cd .. Check that the run worked and the general log is available: diff --git a/docs/examples/sqlcommenter/instrumented_query.py b/docs/examples/sqlcommenter/instrumented_query.py index e4835f8405c..1bfc6a903c4 100644 --- a/docs/examples/sqlcommenter/instrumented_query.py +++ b/docs/examples/sqlcommenter/instrumented_query.py @@ -47,8 +47,8 @@ ) cnx = connect( - host="127.0.0.1", - port=3306, + host="localhost", + port=3366, user="books", password="books123", database="books", From 1777bc52a6c8cf4e236ccd0e21b688bbe44d639a Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Fri, 29 Aug 2025 20:46:06 -0700 Subject: [PATCH 3/8] User creation --- docs/examples/sqlcommenter/books_database/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/examples/sqlcommenter/books_database/Dockerfile b/docs/examples/sqlcommenter/books_database/Dockerfile index ce35028e03d..0739fb2ad6b 100644 --- a/docs/examples/sqlcommenter/books_database/Dockerfile +++ b/docs/examples/sqlcommenter/books_database/Dockerfile @@ -7,6 +7,10 @@ ENV MYSQL_PASSWORD=books123 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 && \ @@ -18,4 +22,4 @@ 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", "--default-authentication-plugin=mysql_native_password"] \ No newline at end of file +CMD ["mysqld", "--general-log=1", "--general-log-file=/var/log/general.log"] \ No newline at end of file From 4d509d3e3e12d9205c4ea8f5043c346ba2ad26a8 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Fri, 29 Aug 2025 21:01:22 -0700 Subject: [PATCH 4/8] Rm unneeded vars --- docs/examples/sqlcommenter/books_database/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/examples/sqlcommenter/books_database/Dockerfile b/docs/examples/sqlcommenter/books_database/Dockerfile index 0739fb2ad6b..cb683dba086 100644 --- a/docs/examples/sqlcommenter/books_database/Dockerfile +++ b/docs/examples/sqlcommenter/books_database/Dockerfile @@ -2,8 +2,6 @@ FROM mysql:8.0 ENV MYSQL_ROOT_PASSWORD=root ENV MYSQL_DATABASE=books -ENV MYSQL_USER=books -ENV MYSQL_PASSWORD=books123 ADD books.sql /docker-entrypoint-initdb.d/ From 8ebd151b063763ded92bc987600f2a92c672762c Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 10:37:14 -0700 Subject: [PATCH 5/8] Fix query script --- .../sqlcommenter/instrumented_query.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/examples/sqlcommenter/instrumented_query.py b/docs/examples/sqlcommenter/instrumented_query.py index 1bfc6a903c4..123d0185b75 100644 --- a/docs/examples/sqlcommenter/instrumented_query.py +++ b/docs/examples/sqlcommenter/instrumented_query.py @@ -39,13 +39,6 @@ ) trace.get_tracer_provider().add_span_processor(span_processor) -# Instrument MySQL queries with sqlcommenter enabled -# and comment-in-span-attribute enabled -MySQLInstrumentor().instrument( - enable_commenter=True, - enable_attribute_commenter=True, -) - cnx = connect( host="localhost", port=3366, @@ -54,13 +47,22 @@ 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, 3): +for cid in range(1, 4): cursor.execute(statement, (cid,)) rows = cursor.fetchall() print(f"Found author: {rows[0]}") From b27a0d1f7df9404e7375e4137674220c70b4f17e Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:13:45 -0700 Subject: [PATCH 6/8] Add how-to-check --- docs/examples/sqlcommenter/README.rst | 67 +++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/docs/examples/sqlcommenter/README.rst b/docs/examples/sqlcommenter/README.rst index d880a59b3c7..e090c32144c 100644 --- a/docs/examples/sqlcommenter/README.rst +++ b/docs/examples/sqlcommenter/README.rst @@ -23,7 +23,7 @@ A running MySQL server with general logs enabled will store query statements wit docker run -d --name books-db -p 3366:3306 books-db cd .. -Check that the run worked and the general log is available: +Check that the run is working and the general log is available: .. code-block:: sh @@ -33,7 +33,7 @@ Run OpenTelemetry Collector --------------------------- Running the OpenTelemetry collector will show the MySQL instrumentor's -comment-in-span-attribute feature that the example has also been enabled. +comment-in-span-attribute feature, which this example has also enabled. .. code-block:: sh @@ -52,22 +52,73 @@ dependencies of the sqlcommenter example: pip install opentelemetry-sdk \ opentelemetry-exporter-otlp-proto-grpc \ - opentelemetry-instrumentation-mysql - pip install mysql-connector-python + opentelemetry-instrumentation-mysql \ + mysql-connector-python Then, run this script, which instruments all mysql-connector calls with -two sqlcommenter features opted in. For each ``SELECT`` call, a query is -made with a sqlcomment appended and one OTel span with ``db.statement`` -attribute is also generated with sqlcomment. +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 `_ * `OpenTelemetry Collector `_ -* `OpenTelemetry MySQL instrumentation `_ \ No newline at end of file +* `OpenTelemetry MySQL instrumentation `_ +* `Semantic Conventions - Database Spans `_ +* `sqlcommenter `_ \ No newline at end of file From 1be2939c0b587df38647ec38f230ad6315afdbb9 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:19:56 -0700 Subject: [PATCH 7/8] lint --- docs/examples/sqlcommenter/instrumented_query.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/examples/sqlcommenter/instrumented_query.py b/docs/examples/sqlcommenter/instrumented_query.py index 123d0185b75..1844b46a42c 100644 --- a/docs/examples/sqlcommenter/instrumented_query.py +++ b/docs/examples/sqlcommenter/instrumented_query.py @@ -15,27 +15,22 @@ from mysql.connector import connect from opentelemetry import trace -from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +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 - ) -) +trace.set_tracer_provider(TracerProvider(resource=resource)) span_processor = BatchSpanProcessor( - OTLPSpanExporter( - endpoint="http://localhost:4317" - ) + OTLPSpanExporter(endpoint="http://localhost:4317") ) trace.get_tracer_provider().add_span_processor(span_processor) From 85471d9703bd797dd0424de59ef769fa4fa10ec7 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:22:25 -0700 Subject: [PATCH 8/8] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c46cf395c8..b3bb934625e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4634](https://github.com/open-telemetry/opentelemetry-python/pull/4634)) - semantic-conventions: Bump to 1.37.0 ([#4731](https://github.com/open-telemetry/opentelemetry-python/pull/4731)) +- docs: Added sqlcommenter example + ([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734)) ## Version 1.36.0/0.57b0 (2025-07-29)