diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e174f982e..3b52350512c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/docs/examples/sqlcommenter/README.rst b/docs/examples/sqlcommenter/README.rst new file mode 100644 index 00000000000..e090c32144c --- /dev/null +++ b/docs/examples/sqlcommenter/README.rst @@ -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 `_ +* `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 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 `_ +* `OpenTelemetry Collector `_ +* `OpenTelemetry MySQL instrumentation `_ +* `Semantic Conventions - Database Spans `_ +* `sqlcommenter `_ \ 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..cb683dba086 --- /dev/null +++ b/docs/examples/sqlcommenter/books_database/Dockerfile @@ -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"] \ 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..1844b46a42c --- /dev/null +++ b/docs/examples/sqlcommenter/instrumented_query.py @@ -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.")