Skip to content

Commit be1575c

Browse files
Add sqlcommenter example to docs (#4734)
* Add sqlcommenter docs/example * Change port * User creation * Rm unneeded vars * Fix query script * Add how-to-check * lint * Changelog * Update CHANGELOG.md * Apply suggestions from code review --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 9ed864a commit be1575c

File tree

6 files changed

+303
-0
lines changed

6 files changed

+303
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
1313
## Unreleased
1414

15+
- docs: Added sqlcommenter example
16+
([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734))
17+
1518
## Version 1.38.0/0.59b0 (2025-10-16)
1619

1720
- Add `rstcheck` to pre-commit to stop introducing invalid RST
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
sqlcommenter
2+
============
3+
4+
This is an example of how to use OpenTelemetry Python instrumention with
5+
sqlcommenter to enrich database query statements with contextual information.
6+
For more information on sqlcommenter concepts, see:
7+
8+
* `Semantic Conventions - Database Spans <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#sql-commenter>`_
9+
* `sqlcommenter <https://google.github.io/sqlcommenter/>`_
10+
11+
The source files of this example are available `here <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/sqlcommenter/>`_.
12+
This example uses Docker to manage a database server and OpenTelemetry collector.
13+
14+
Run MySQL server
15+
----------------
16+
17+
A running MySQL server with general logs enabled will store query statements with context resulting from the sqlcommenter feature enabled in this example.
18+
19+
.. code-block:: sh
20+
21+
cd books_database
22+
docker build -t books-db .
23+
docker run -d --name books-db -p 3366:3306 books-db
24+
cd ..
25+
26+
Check that the run is working and the general log is available:
27+
28+
.. code-block:: sh
29+
30+
docker exec -it books-db tail -f /var/log/general.log
31+
32+
Run OpenTelemetry Collector
33+
---------------------------
34+
35+
Running the OpenTelemetry collector will show the MySQL instrumentor's
36+
comment-in-span-attribute feature, which this example has also enabled.
37+
38+
.. code-block:: sh
39+
40+
docker run \
41+
-p 4317:4317 \
42+
-v $(pwd)/collector-config.yaml:/etc/otel/config.yaml \
43+
otel/opentelemetry-collector-contrib:latest
44+
45+
Run the sqlcommenter example
46+
----------------------------
47+
48+
Set up and activate a Python virtual environment. Install these
49+
dependencies of the sqlcommenter example:
50+
51+
.. code-block:: sh
52+
53+
pip install opentelemetry-sdk \
54+
opentelemetry-exporter-otlp-proto-grpc \
55+
opentelemetry-instrumentation-mysql \
56+
mysql-connector-python
57+
58+
Then, run this script, which instruments all mysql-connector calls with
59+
two sqlcommenter features opted in.
60+
61+
.. code-block:: sh
62+
63+
python instrumented_query.py
64+
65+
Note that OpenTelemetry instrumentation with sqlcommenter is also
66+
available for other Python database client drivers/object relation
67+
mappers (ORMs). See full list at `instrumentation`_.
68+
69+
.. _instrumentation: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation
70+
71+
Check MySQL server general log and spans for sqlcomment
72+
-------------------------------------------------------
73+
74+
After running the query script, check the MySQL general log contents:
75+
76+
.. code-block:: sh
77+
78+
docker exec -it books-db tail -f /var/log/general.log
79+
80+
For each instrumented ``SELECT`` call, a query was made and logged with
81+
a sqlcomment appended. For example:
82+
83+
.. code::
84+
85+
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'*/
86+
87+
In the running OpenTelemetry collector, you'll also see one span per
88+
``SELECT`` call. Each of those span's trace ID and span ID will
89+
correspond to a query log sqlcomment. With the comment-in-attribute
90+
feature enabled, the span's ``db.statement`` attribute will also contain
91+
the sqlcomment. For example:
92+
93+
.. code::
94+
95+
ScopeSpans #0
96+
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.11.0
97+
InstrumentationScope opentelemetry.instrumentation.mysql 0.57b0
98+
Span #0
99+
Trace ID : 2c45248f2beefdd9688b0a94eb4ac9ee
100+
Parent ID :
101+
ID : 4f3af9a825aae9b1
102+
Name : SELECT
103+
Kind : Client
104+
Start time : 2025-09-02 18:49:06.982341 +0000 UTC
105+
End time : 2025-09-02 18:49:06.98463 +0000 UTC
106+
Status code : Unset
107+
Status message :
108+
Attributes:
109+
-> db.system: Str(mysql)
110+
-> db.name: Str(books)
111+
-> 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'*/)
112+
-> db.user: Str(books)
113+
-> net.peer.name: Str(localhost)
114+
-> net.peer.port: Int(3366)
115+
116+
117+
References
118+
----------
119+
120+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
121+
* `OpenTelemetry Collector <https://github.com/open-telemetry/opentelemetry-collector>`_
122+
* `OpenTelemetry MySQL instrumentation <https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-mysql>`_
123+
* `Semantic Conventions - Database Spans <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#sql-commenter>`_
124+
* `sqlcommenter <https://google.github.io/sqlcommenter/>`_
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM mysql:8.0
2+
3+
ENV MYSQL_ROOT_PASSWORD=root
4+
ENV MYSQL_DATABASE=books
5+
6+
ADD books.sql /docker-entrypoint-initdb.d/
7+
8+
RUN echo "CREATE USER IF NOT EXISTS 'books'@'%' IDENTIFIED WITH mysql_native_password BY 'books123';" > /docker-entrypoint-initdb.d/01-create-user.sql && \
9+
echo "GRANT ALL PRIVILEGES ON books.* TO 'books'@'%';" >> /docker-entrypoint-initdb.d/01-create-user.sql && \
10+
echo "FLUSH PRIVILEGES;" >> /docker-entrypoint-initdb.d/01-create-user.sql
11+
12+
# Prepare general logs
13+
RUN mkdir -p /var/log && \
14+
touch /var/log/general.log && \
15+
chown mysql:mysql /var/log/general.log
16+
17+
EXPOSE 3306
18+
19+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
20+
CMD mysqladmin ping -p${MYSQL_ROOT_PASSWORD} || exit 1
21+
22+
# Start MySQL with general logging enabled and compatible authentication
23+
CMD ["mysqld", "--general-log=1", "--general-log-file=/var/log/general.log"]
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
-- MySQL dump for Books Database
2+
-- Database: books_db
3+
-- Generated on: 2025-08-29
4+
5+
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
6+
START TRANSACTION;
7+
SET time_zone = "+00:00";
8+
9+
-- Database: `books`
10+
CREATE DATABASE IF NOT EXISTS `books` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
11+
USE `books`;
12+
13+
-- --------------------------------------------------------
14+
15+
-- Table structure for table `authors`
16+
17+
DROP TABLE IF EXISTS `authors`;
18+
CREATE TABLE `authors` (
19+
`id` int(11) NOT NULL AUTO_INCREMENT,
20+
`name` varchar(255) NOT NULL,
21+
`home_town` varchar(255) DEFAULT NULL,
22+
`birthdate` date DEFAULT NULL,
23+
PRIMARY KEY (`id`)
24+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
25+
26+
-- Dumping data for table `authors`
27+
28+
INSERT INTO `authors` (`id`, `name`, `home_town`, `birthdate`) VALUES
29+
(1, 'Frank Herbert', 'Tacoma, Washington', '1920-10-08'),
30+
(2, 'Isaac Asimov', 'Petrovichi, Russia', '1920-01-02'),
31+
(3, 'Terry Pratchett', 'Beaconsfield, England', '1948-04-28');
32+
33+
-- --------------------------------------------------------
34+
35+
-- Table structure for table `books`
36+
37+
DROP TABLE IF EXISTS `books`;
38+
CREATE TABLE `books` (
39+
`id` int(11) NOT NULL AUTO_INCREMENT,
40+
`title` varchar(255) NOT NULL,
41+
`author_id` int(11) NOT NULL,
42+
`year_published` int(4) DEFAULT NULL,
43+
`genre` varchar(100) DEFAULT NULL,
44+
PRIMARY KEY (`id`),
45+
KEY `fk_author` (`author_id`),
46+
CONSTRAINT `fk_author` FOREIGN KEY (`author_id`) REFERENCES `authors` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
47+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
48+
49+
-- Dumping data for table `books`
50+
51+
INSERT INTO `books` (`id`, `title`, `author_id`, `year_published`, `genre`) VALUES
52+
(1, 'Dune', 1, 1965, 'Science Fiction'),
53+
(2, 'Foundation', 2, 1951, 'Science Fiction'),
54+
(3, 'The Colour of Magic', 3, 1983, 'Fantasy Comedy');
55+
56+
-- --------------------------------------------------------
57+
58+
-- Additional books to show the many-to-one relationship
59+
60+
INSERT INTO `books` (`id`, `title`, `author_id`, `year_published`, `genre`) VALUES
61+
(4, 'Dune Messiah', 1, 1969, 'Science Fiction'),
62+
(5, 'I, Robot', 2, 1950, 'Science Fiction'),
63+
(6, 'Good Omens', 3, 1990, 'Fantasy Comedy');
64+
65+
-- --------------------------------------------------------
66+
67+
-- Auto increment values
68+
69+
ALTER TABLE `authors` AUTO_INCREMENT = 4;
70+
ALTER TABLE `books` AUTO_INCREMENT = 7;
71+
72+
COMMIT;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
grpc:
5+
endpoint: 0.0.0.0:4317
6+
7+
exporters:
8+
debug:
9+
verbosity: detailed
10+
11+
service:
12+
pipelines:
13+
traces:
14+
receivers: [otlp]
15+
processors: []
16+
exporters: [debug]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from mysql.connector import connect
16+
17+
from opentelemetry import trace
18+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
19+
OTLPSpanExporter,
20+
)
21+
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
22+
from opentelemetry.sdk.resources import Resource
23+
from opentelemetry.sdk.trace import TracerProvider
24+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
25+
26+
resource = Resource.create(
27+
attributes={
28+
"service.name": "sqlcommenter-example",
29+
}
30+
)
31+
trace.set_tracer_provider(TracerProvider(resource=resource))
32+
span_processor = BatchSpanProcessor(
33+
OTLPSpanExporter(endpoint="http://localhost:4317")
34+
)
35+
trace.get_tracer_provider().add_span_processor(span_processor)
36+
37+
cnx = connect(
38+
host="localhost",
39+
port=3366,
40+
user="books",
41+
password="books123",
42+
database="books",
43+
)
44+
45+
# Instruments MySQL queries with sqlcommenter enabled
46+
# and comment-in-span-attribute enabled.
47+
# Returns wrapped connection to generate traces.
48+
cnx = MySQLInstrumentor().instrument_connection(
49+
connection=cnx,
50+
enable_commenter=True,
51+
enable_attribute_commenter=True,
52+
)
53+
54+
cursor = cnx.cursor()
55+
statement = "SELECT * FROM authors WHERE id = %s"
56+
57+
# Each SELECT query generates one mysql log with sqlcomment
58+
# and one OTel span with `db.statement` attribute that also
59+
# includes sqlcomment.
60+
for cid in range(1, 4):
61+
cursor.execute(statement, (cid,))
62+
rows = cursor.fetchall()
63+
print(f"Found author: {rows[0]}")
64+
65+
print("Done.")

0 commit comments

Comments
 (0)