Skip to content

Commit c7b2115

Browse files
authored
Add logfire.instrument_asyncpg() (#44)
1 parent 147ee12 commit c7b2115

File tree

9 files changed

+114
-0
lines changed

9 files changed

+114
-0
lines changed

docs/integrations/asyncpg.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# asyncpg
2+
3+
The [`logfire.instrument_asyncpg()`][logfire.Logfire.instrument_asyncpg] function can be used to instrument the [asyncpg][asyncpg] PostgreSQL driver with **Logfire**.
4+
5+
## Installation
6+
7+
Install `logfire` with the `asyncpg` extra:
8+
9+
{{ install_logfire(extras=['asyncpg']) }}
10+
11+
## Usage
12+
13+
Let's setup a PostgreSQL database using Docker and run a Python script that connects to the database using asyncpg to
14+
demonstrate how to use **Logfire** with asyncpg.
15+
16+
### Setup a PostgreSQL Database Using Docker
17+
18+
First, we need to initialize a PostgreSQL database. This can be easily done using Docker with the following command:
19+
20+
```bash
21+
docker run --name postgres \
22+
-e POSTGRES_USER=user \
23+
-e POSTGRES_PASSWORD=secret \
24+
-e POSTGRES_DB=database \
25+
-p 5432:5432 -d postgres
26+
```
27+
28+
This command accomplishes the following:
29+
30+
- `--name postgres`: This defines the name of the Docker container.
31+
- `-e POSTGRES_USER=user`: This sets a user for the PostgreSQL server.
32+
- `-e POSTGRES_PASSWORD=secret`: This sets a password for the PostgreSQL server.
33+
- `-e POSTGRES_DB=database`: This creates a new database named "database", the same as the one used in your Python script.
34+
- `-p 5432:5432`: This makes the PostgreSQL instance available on your local machine under port 5432.
35+
- `-d postgres`: This denotes the Docker image to be used, in this case, "postgres".
36+
37+
### Run the Python script
38+
39+
The following Python script connects to the PostgreSQL database and executes some SQL queries:
40+
41+
```py
42+
import asyncio
43+
44+
import asyncpg
45+
46+
import logfire
47+
48+
logfire.configure()
49+
logfire.instrument_asyncpg()
50+
51+
52+
async def main():
53+
connection: asyncpg.Connection = await asyncpg.connect(
54+
user='user', password='secret', database='database', host='0.0.0.0', port=5432
55+
)
56+
57+
with logfire.span('Create table and insert data'):
58+
await connection.execute('CREATE TABLE IF NOT EXISTS test (id serial PRIMARY KEY, num integer, data varchar);')
59+
60+
# Insert some data
61+
await connection.execute('INSERT INTO test (num, data) VALUES ($1, $2)', 100, 'abc')
62+
await connection.execute('INSERT INTO test (num, data) VALUES ($1, $2)', 200, 'def')
63+
64+
# Query the data
65+
for record in await connection.fetch('SELECT * FROM test'):
66+
logfire.info('Retrieved {record=}', record=record)
67+
68+
69+
asyncio.run(main())
70+
```
71+
72+
If you go to your project on the UI, you will see the span created by the script.
73+
74+
[opentelemetry-asyncpg]: https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asyncpg/asyncpg.html
75+
[opentelemetry-asyncpg2]: https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asyncpg2/asyncpg2.html
76+
[asyncpg]: https://magicstack.github.io/asyncpg/

docs/integrations/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Below you can see more details on how to use Logfire with some of the most popul
2222
| [Requests](requests.md) | HTTP Client |
2323
| [AIOHTTP](aiohttp.md) | HTTP Client |
2424
| [SQLAlchemy](sqlalchemy.md) | Databases |
25+
| [Asyncpg](asyncpg.md) | Databases |
2526
| [Psycopg](psycopg.md) | Databases |
2627
| [PyMongo](pymongo.md) | Databases |
2728
| [Redis](redis.md) | Databases |

logfire/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
install_auto_tracing = DEFAULT_LOGFIRE_INSTANCE.install_auto_tracing
2222
instrument_fastapi = DEFAULT_LOGFIRE_INSTANCE.instrument_fastapi
2323
instrument_openai = DEFAULT_LOGFIRE_INSTANCE.instrument_openai
24+
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
2425
instrument_psycopg = DEFAULT_LOGFIRE_INSTANCE.instrument_psycopg
2526
shutdown = DEFAULT_LOGFIRE_INSTANCE.shutdown
2627
with_tags = DEFAULT_LOGFIRE_INSTANCE.with_tags
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
2+
3+
4+
def instrument_asyncpg():
5+
"""Instrument the `asyncpg` module so that spans are automatically created for each query.
6+
7+
See the `Logfire.instrument_asyncpg` method for details.
8+
"""
9+
AsyncPGInstrumentor().instrument()

logfire/_internal/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,12 @@ def instrument_openai(
831831

832832
return instrument_openai(self, openai_client, suppress_other_instrumentation)
833833

834+
def instrument_asyncpg(self):
835+
"""Instrument the `asyncpg` module so that spans are automatically created for each query."""
836+
from .integrations.asyncpg import instrument_asyncpg
837+
838+
return instrument_asyncpg()
839+
834840
def instrument_psycopg(self, conn_or_module: Any = None, **kwargs: Any):
835841
"""Instrument a `psycopg` connection or module so that spans are automatically created for each query.
836842

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ nav:
104104
- Requests: integrations/requests.md
105105
- AIOHTTP: integrations/aiohttp.md
106106
- SQLAlchemy: integrations/sqlalchemy.md
107+
- Asyncpg: integrations/asyncpg.md
107108
- Psycopg: integrations/psycopg.md
108109
- PyMongo: integrations/pymongo.md
109110
- Redis: integrations/redis.md

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ flask = ["opentelemetry-instrumentation-flask >= 0.42b0"]
5959
httpx = ["opentelemetry-instrumentation-httpx >= 0.42b0"]
6060
starlette = ["opentelemetry-instrumentation-starlette >= 0.42b0"]
6161
sqlalchemy = ["opentelemetry-instrumentation-sqlalchemy >= 0.42b0"]
62+
asyncpg = ["opentelemetry-instrumentation-psycopg2 >= 0.42b0"]
6263
psycopg = ["opentelemetry-instrumentation-psycopg2 >= 0.42b0", "packaging"]
6364
psycopg2 = ["opentelemetry-instrumentation-psycopg2 >= 0.42b0", "packaging"]
6465
pymongo = ["opentelemetry-instrumentation-pymongo >= 0.42b0"]
@@ -104,6 +105,7 @@ dev-dependencies = [
104105
"opentelemetry-instrumentation-requests",
105106
"opentelemetry-instrumentation-sqlalchemy",
106107
"opentelemetry-instrumentation-system-metrics",
108+
"opentelemetry-instrumentation-asyncpg",
107109
"opentelemetry-instrumentation-psycopg",
108110
"opentelemetry-instrumentation-psycopg2",
109111
"gitpython",
@@ -123,6 +125,7 @@ dev-dependencies = [
123125
"coverage[toml]>=7.5.0",
124126
"psycopg[binary]",
125127
"psycopg2-binary",
128+
"asyncpg",
126129
]
127130

128131
[tool.rye.scripts]

requirements-dev.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ asgiref==3.8.1
2020
asttokens==2.4.1
2121
# via devtools
2222
# via inline-snapshot
23+
asyncpg==0.29.0
2324
attrs==23.2.0
2425
babel==2.14.0
2526
# via mkdocs-material
@@ -144,6 +145,7 @@ opentelemetry-api==1.24.0
144145
# via opentelemetry-instrumentation
145146
# via opentelemetry-instrumentation-aiohttp-client
146147
# via opentelemetry-instrumentation-asgi
148+
# via opentelemetry-instrumentation-asyncpg
147149
# via opentelemetry-instrumentation-dbapi
148150
# via opentelemetry-instrumentation-django
149151
# via opentelemetry-instrumentation-fastapi
@@ -165,6 +167,7 @@ opentelemetry-instrumentation==0.45b0
165167
# via logfire
166168
# via opentelemetry-instrumentation-aiohttp-client
167169
# via opentelemetry-instrumentation-asgi
170+
# via opentelemetry-instrumentation-asyncpg
168171
# via opentelemetry-instrumentation-dbapi
169172
# via opentelemetry-instrumentation-django
170173
# via opentelemetry-instrumentation-fastapi
@@ -181,6 +184,7 @@ opentelemetry-instrumentation-aiohttp-client==0.45b0
181184
opentelemetry-instrumentation-asgi==0.45b0
182185
# via opentelemetry-instrumentation-fastapi
183186
# via opentelemetry-instrumentation-starlette
187+
opentelemetry-instrumentation-asyncpg==0.45b0
184188
opentelemetry-instrumentation-dbapi==0.45b0
185189
# via opentelemetry-instrumentation-psycopg
186190
# via opentelemetry-instrumentation-psycopg2
@@ -207,6 +211,7 @@ opentelemetry-sdk==1.24.0
207211
opentelemetry-semantic-conventions==0.45b0
208212
# via opentelemetry-instrumentation-aiohttp-client
209213
# via opentelemetry-instrumentation-asgi
214+
# via opentelemetry-instrumentation-asyncpg
210215
# via opentelemetry-instrumentation-dbapi
211216
# via opentelemetry-instrumentation-django
212217
# via opentelemetry-instrumentation-fastapi
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import asyncpg
2+
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
3+
4+
import logfire
5+
6+
7+
def test_asyncpg():
8+
original_execute = asyncpg.Connection.execute
9+
logfire.instrument_asyncpg()
10+
assert original_execute is not asyncpg.Connection.execute
11+
AsyncPGInstrumentor().uninstrument()
12+
assert original_execute is asyncpg.Connection.execute

0 commit comments

Comments
 (0)