Skip to content

Commit 3fbea2f

Browse files
Add the logfire.instrument_mysql() (#341)
Co-authored-by: Alex Hall <[email protected]>
1 parent b185a55 commit 3fbea2f

File tree

14 files changed

+328
-0
lines changed

14 files changed

+328
-0
lines changed

docs/integrations/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Below you can see more details on how to use Logfire with some of the most popul
2525
| [Asyncpg](asyncpg.md) | Databases |
2626
| [Psycopg](psycopg.md) | Databases |
2727
| [PyMongo](pymongo.md) | Databases |
28+
| [MySQL](mysql.md) | Databases |
2829
| [Redis](redis.md) | Databases |
2930
| [Celery](celery.md) | Task Queue |
3031
| [System Metrics](system_metrics.md) | System Metrics |

docs/integrations/mysql.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# MySQL
2+
3+
The [`logfire.instrument_mysql()`][logfire.Logfire.instrument_mysql] method can be used to instrument the [MySQL Connector/Python][mysql-connector] database driver with **Logfire**, creating a span for every query.
4+
5+
## Installation
6+
7+
Install `logfire` with the `mysql` extra:
8+
9+
{{ install_logfire(extras=['mysql']) }}
10+
11+
## Usage
12+
13+
Let's setup a MySQL database using Docker and run a Python script that connects to the database using MySQL connector to
14+
demonstrate how to use **Logfire** with MySQL.
15+
16+
### Setup a MySQL Database Using Docker
17+
18+
First, we need to initialize a MySQL database. This can be easily done using Docker with the following command:
19+
20+
```bash
21+
docker run --name mysql \
22+
-e MYSQL_ROOT_PASSWORD=secret \
23+
-e MYSQL_DATABASE=database \
24+
-e MYSQL_USER=user \
25+
-e MYSQL_PASSWORD=secret \
26+
-p 3306:3306 -d mysql
27+
```
28+
29+
This command accomplishes the following:
30+
31+
- `--name mysql`: gives the container a name of "mysql".
32+
- `-e MYSQL_ROOT_PASSWORD=secret` sets the root password to "secret".
33+
- `-e MYSQL_DATABASE=database` creates a new database named "database".
34+
- `-e MYSQL_USER=user` creates a new user named "user".
35+
- `-e MYSQL_PASSWORD=secret` sets the password for the new user to "secret".
36+
- `-p 3306:3306` maps port 3306 inside Docker as port 3306 on the host machine.
37+
- `-d mysql` runs the container in the background and prints the container ID. The image is "mysql".
38+
39+
### Run the Python script
40+
41+
The following Python script connects to the MySQL database and executes some SQL queries:
42+
43+
```py
44+
import logfire
45+
import mysql.connector
46+
47+
logfire.configure()
48+
49+
# To instrument the whole module:
50+
logfire.instrument_mysql()
51+
52+
connection = mysql.connector.connect(
53+
host="localhost",
54+
user="user",
55+
password="secret",
56+
database="database",
57+
port=3306,
58+
use_pure=True,
59+
)
60+
61+
# Or instrument just the connection:
62+
# connection = logfire.instrument_mysql(connection)
63+
64+
with logfire.span('Create table and insert data'), connection.cursor() as cursor:
65+
cursor.execute(
66+
'CREATE TABLE IF NOT EXISTS test (id INT AUTO_INCREMENT PRIMARY KEY, num integer, data varchar(255));'
67+
)
68+
69+
# Insert some data
70+
cursor.execute('INSERT INTO test (num, data) VALUES (%s, %s)', (100, 'abc'))
71+
cursor.execute('INSERT INTO test (num, data) VALUES (%s, %s)', (200, 'def'))
72+
73+
# Query the data
74+
cursor.execute('SELECT * FROM test')
75+
results = cursor.fetchall() # Fetch all rows
76+
for row in results:
77+
print(row) # Print each row
78+
```
79+
80+
[`logfire.instrument_mysql()`][logfire.Logfire.instrument_mysql] uses the
81+
**OpenTelemetry MySQL Instrumentation** package,
82+
which you can find more information about [here][opentelemetry-mysql].
83+
84+
[opentelemetry-mysql]: https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/mysql/mysql.html
85+
[mysql]: https://www.mysql.com/
86+
[mysql-connector]: https://dev.mysql.com/doc/connector-python/en/

logfire-api/logfire_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def shutdown(self, *args, **kwargs) -> None: ...
154154
instrument_sqlalchemy = DEFAULT_LOGFIRE_INSTANCE.instrument_sqlalchemy
155155
instrument_redis = DEFAULT_LOGFIRE_INSTANCE.instrument_redis
156156
instrument_pymongo = DEFAULT_LOGFIRE_INSTANCE.instrument_pymongo
157+
instrument_mysql = DEFAULT_LOGFIRE_INSTANCE.instrument_mysql
157158
shutdown = DEFAULT_LOGFIRE_INSTANCE.shutdown
158159

159160
def no_auto_trace(x):

logfire-api/logfire_api/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ __all__ = [
5151
'instrument_sqlalchemy',
5252
'instrument_redis',
5353
'instrument_pymongo',
54+
'instrument_mysql',
5455
'AutoTraceModule',
5556
'with_tags',
5657
'with_settings',
@@ -88,6 +89,7 @@ instrument_aiohttp_client = DEFAULT_LOGFIRE_INSTANCE.instrument_aiohttp_client
8889
instrument_sqlalchemy = DEFAULT_LOGFIRE_INSTANCE.instrument_sqlalchemy
8990
instrument_redis = DEFAULT_LOGFIRE_INSTANCE.instrument_redis
9091
instrument_pymongo = DEFAULT_LOGFIRE_INSTANCE.instrument_pymongo
92+
instrument_mysql = DEFAULT_LOGFIRE_INSTANCE.instrument_mysql
9193
shutdown = DEFAULT_LOGFIRE_INSTANCE.shutdown
9294
with_tags = DEFAULT_LOGFIRE_INSTANCE.with_tags
9395
with_settings = DEFAULT_LOGFIRE_INSTANCE.with_settings
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
6+
7+
if TYPE_CHECKING:
8+
from mysql.connector.abstracts import MySQLConnectionAbstract
9+
from mysql.connector.pooling import PooledMySQLConnection
10+
from typing_extensions import TypedDict, TypeVar, Unpack
11+
12+
MySQLConnection = TypeVar('MySQLConnection', PooledMySQLConnection, MySQLConnectionAbstract, None)
13+
14+
class MySQLInstrumentKwargs(TypedDict, total=False):
15+
skip_dep_check: bool
16+
17+
18+
def instrument_mysql(
19+
conn: MySQLConnection = None,
20+
**kwargs: Unpack[MySQLInstrumentKwargs],
21+
) -> MySQLConnection:
22+
"""Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation.
23+
24+
This function uses the OpenTelemetry MySQL Instrumentation library to instrument either the entire `mysql` module or a specific MySQL connection.
25+
26+
Args:
27+
conn: The MySQL connection to instrument. If None, the entire `mysql` module is instrumented.
28+
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods.
29+
30+
Returns:
31+
If a connection is provided, returns the instrumented connection. If no connection is provided, returns None.
32+
33+
See the `Logfire.instrument_mysql` method for details.
34+
"""

logfire-api/logfire_api/_internal/main.pyi

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ from .integrations.pymongo import PymongoInstrumentKwargs as PymongoInstrumentKw
1818
from .integrations.redis import RedisInstrumentKwargs as RedisInstrumentKwargs
1919
from .integrations.sqlalchemy import SQLAlchemyInstrumentKwargs as SQLAlchemyInstrumentKwargs
2020
from .integrations.starlette import StarletteInstrumentKwargs as StarletteInstrumentKwargs
21+
from .integrations.mysql import MySQLConnection as MySQLConnection, MySQLInstrumentKwargs as MySQLInstrumentKwargs
2122
from .json_encoder import logfire_json_dumps as logfire_json_dumps
2223
from .json_schema import JsonSchemaProperties as JsonSchemaProperties, attributes_json_schema as attributes_json_schema, attributes_json_schema_properties as attributes_json_schema_properties, create_json_schema as create_json_schema
2324
from .metrics import ProxyMeterProvider as ProxyMeterProvider
@@ -618,6 +619,22 @@ class Logfire:
618619
[OpenTelemetry pymongo Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/pymongo/pymongo.html)
619620
library, specifically `PymongoInstrumentor().instrument()`, to which it passes `**kwargs`.
620621
"""
622+
def instrument_mysql(self, conn: MySQLConnection, **kwargs: Unpack[MySQLInstrumentKwargs],
623+
) -> MySQLConnection:
624+
"""Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation.
625+
626+
Uses the
627+
[OpenTelemetry MySQL Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/mysql/mysql.html)
628+
library.
629+
630+
Args:
631+
conn: The `mysql` connection to instrument, or `None` to instrument all connections.
632+
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods.
633+
634+
Returns:
635+
If a connection is provided, returns the instrumented connection. If no connection is provided, returns None.
636+
637+
"""
621638
def instrument_redis(self, **kwargs: Unpack[RedisInstrumentKwargs]) -> None:
622639
"""Instrument the `redis` module so that spans are automatically created for each operation.
623640

logfire/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
instrument_sqlalchemy = DEFAULT_LOGFIRE_INSTANCE.instrument_sqlalchemy
3939
instrument_redis = DEFAULT_LOGFIRE_INSTANCE.instrument_redis
4040
instrument_pymongo = DEFAULT_LOGFIRE_INSTANCE.instrument_pymongo
41+
instrument_mysql = DEFAULT_LOGFIRE_INSTANCE.instrument_mysql
4142
shutdown = DEFAULT_LOGFIRE_INSTANCE.shutdown
4243
with_tags = DEFAULT_LOGFIRE_INSTANCE.with_tags
4344
# with_trace_sample_rate = DEFAULT_LOGFIRE_INSTANCE.with_trace_sample_rate
@@ -112,6 +113,7 @@ def loguru_handler() -> dict[str, Any]:
112113
'instrument_sqlalchemy',
113114
'instrument_redis',
114115
'instrument_pymongo',
116+
'instrument_mysql',
115117
'AutoTraceModule',
116118
'with_tags',
117119
'with_settings',
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
6+
7+
if TYPE_CHECKING:
8+
from mysql.connector.abstracts import MySQLConnectionAbstract
9+
from mysql.connector.pooling import PooledMySQLConnection
10+
from typing_extensions import TypedDict, TypeVar, Unpack
11+
12+
MySQLConnection = TypeVar('MySQLConnection', bound=PooledMySQLConnection | MySQLConnectionAbstract | None)
13+
14+
class MySQLInstrumentKwargs(TypedDict, total=False):
15+
skip_dep_check: bool
16+
17+
18+
def instrument_mysql(
19+
conn: MySQLConnection = None,
20+
**kwargs: Unpack[MySQLInstrumentKwargs],
21+
) -> MySQLConnection:
22+
"""Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation.
23+
24+
This function uses the OpenTelemetry MySQL Instrumentation library to instrument either the entire `mysql` module or a specific MySQL connection.
25+
26+
Args:
27+
conn: The MySQL connection to instrument. If None, the entire `mysql` module is instrumented.
28+
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods.
29+
30+
Returns:
31+
If a connection is provided, returns the instrumented connection. If no connection is provided, returns None.
32+
33+
See the `Logfire.instrument_mysql` method for details.
34+
"""
35+
if conn is not None:
36+
return MySQLInstrumentor().instrument_connection(conn) # type: ignore[reportUnknownMemberType]
37+
return MySQLInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType]

logfire/_internal/main.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from .integrations.celery import CeleryInstrumentKwargs
6969
from .integrations.flask import FlaskInstrumentKwargs
7070
from .integrations.httpx import HTTPXInstrumentKwargs
71+
from .integrations.mysql import MySQLConnection, MySQLInstrumentKwargs
7172
from .integrations.psycopg import PsycopgInstrumentKwargs
7273
from .integrations.pymongo import PymongoInstrumentKwargs
7374
from .integrations.redis import RedisInstrumentKwargs
@@ -1223,6 +1224,30 @@ def instrument_redis(self, **kwargs: Unpack[RedisInstrumentKwargs]) -> None:
12231224
self._warn_if_not_initialized_for_instrumentation()
12241225
return instrument_redis(**kwargs)
12251226

1227+
def instrument_mysql(
1228+
self,
1229+
conn: MySQLConnection = None,
1230+
**kwargs: Unpack[MySQLInstrumentKwargs],
1231+
) -> MySQLConnection:
1232+
"""Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation.
1233+
1234+
Uses the
1235+
[OpenTelemetry MySQL Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/mysql/mysql.html)
1236+
library.
1237+
1238+
Args:
1239+
conn: The `mysql` connection to instrument, or `None` to instrument all connections.
1240+
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods.
1241+
1242+
Returns:
1243+
If a connection is provided, returns the instrumented connection. If no connection is provided, returns None.
1244+
1245+
"""
1246+
from .integrations.mysql import instrument_mysql
1247+
1248+
self._warn_if_not_initialized_for_instrumentation()
1249+
return instrument_mysql(conn, **kwargs)
1250+
12261251
def metric_counter(self, name: str, *, unit: str = '', description: str = '') -> Counter:
12271252
"""Create a counter metric.
12281253

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ nav:
107107
- Asyncpg: integrations/asyncpg.md
108108
- Psycopg: integrations/psycopg.md
109109
- PyMongo: integrations/pymongo.md
110+
- MySQL: integrations/mysql.md
110111
- Redis: integrations/redis.md
111112
- Celery: integrations/celery.md
112113
- System Metrics: integrations/system_metrics.md

0 commit comments

Comments
 (0)