Skip to content

Commit 795064a

Browse files
Handle async engines in engines arg of instrument_sqlalchemy (#1425)
Co-authored-by: Alex Hall <[email protected]>
1 parent f93aeb6 commit 795064a

File tree

5 files changed

+41
-12
lines changed

5 files changed

+41
-12
lines changed

logfire-api/logfire_api/_internal/integrations/sqlalchemy.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from logfire.integrations.sqlalchemy import CommenterOptions as CommenterOptions
22
from sqlalchemy import Engine
33
from sqlalchemy.ext.asyncio import AsyncEngine
4-
from typing import Any
4+
from typing import Any, Iterable
55

6-
def instrument_sqlalchemy(engine: AsyncEngine | Engine | None, enable_commenter: bool, commenter_options: CommenterOptions, **kwargs: Any) -> None:
6+
def instrument_sqlalchemy(engine: AsyncEngine | Engine | None, engines: Iterable[AsyncEngine | Engine] | None, enable_commenter: bool, commenter_options: CommenterOptions, **kwargs: Any) -> None:
77
"""Instrument the `sqlalchemy` module so that spans are automatically created for each query.
88
99
See the `Logfire.instrument_sqlalchemy` method for details.

logfire-api/logfire_api/_internal/main.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ class Logfire:
777777
[OpenTelemetry aiohttp server Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/aiohttp_server/aiohttp_server.html)
778778
library, specifically `AioHttpServerInstrumentor().instrument()`, to which it passes `**kwargs`.
779779
"""
780-
def instrument_sqlalchemy(self, engine: AsyncEngine | Engine | None = None, enable_commenter: bool = False, commenter_options: SQLAlchemyCommenterOptions | None = None, **kwargs: Any) -> None:
780+
def instrument_sqlalchemy(self, engine: AsyncEngine | Engine | None = None, engines: Iterable[AsyncEngine | Engine] | None = None, enable_commenter: bool = False, commenter_options: SQLAlchemyCommenterOptions | None = None, **kwargs: Any) -> None:
781781
"""Instrument the `sqlalchemy` module so that spans are automatically created for each query.
782782
783783
Uses the

logfire/_internal/integrations/sqlalchemy.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import contextlib
4+
from collections.abc import Iterable
45
from typing import TYPE_CHECKING, Any
56

67
try:
@@ -19,8 +20,17 @@
1920
from sqlalchemy.ext.asyncio import AsyncEngine
2021

2122

23+
def _convert_to_sync_engine(engine: AsyncEngine | Engine | None) -> Engine | None:
24+
from sqlalchemy.ext.asyncio import AsyncEngine
25+
26+
if isinstance(engine, AsyncEngine):
27+
return engine.sync_engine
28+
return engine
29+
30+
2231
def instrument_sqlalchemy(
2332
engine: AsyncEngine | Engine | None,
33+
engines: Iterable[AsyncEngine | Engine] | None,
2434
enable_commenter: bool,
2535
commenter_options: CommenterOptions,
2636
**kwargs: Any,
@@ -30,10 +40,11 @@ def instrument_sqlalchemy(
3040
See the `Logfire.instrument_sqlalchemy` method for details.
3141
"""
3242
with contextlib.suppress(ImportError):
33-
from sqlalchemy.ext.asyncio import AsyncEngine
43+
engine = _convert_to_sync_engine(engine)
44+
45+
if engines is not None:
46+
engines = [_convert_to_sync_engine(engine_entry) for engine_entry in engines] # type: ignore
3447

35-
if isinstance(engine, AsyncEngine):
36-
engine = engine.sync_engine
3748
return SQLAlchemyInstrumentor().instrument(
38-
engine=engine, enable_commenter=enable_commenter, commenter_options=commenter_options, **kwargs
49+
engine=engine, engines=engines, enable_commenter=enable_commenter, commenter_options=commenter_options, **kwargs
3950
)

logfire/_internal/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,7 @@ def instrument_aiohttp_server(self, **kwargs: Any) -> None:
18161816
def instrument_sqlalchemy(
18171817
self,
18181818
engine: AsyncEngine | Engine | None = None,
1819+
engines: Iterable[AsyncEngine | Engine] | None = None,
18191820
enable_commenter: bool = False,
18201821
commenter_options: SQLAlchemyCommenterOptions | None = None,
18211822
**kwargs: Any,
@@ -1827,7 +1828,8 @@ def instrument_sqlalchemy(
18271828
library, specifically `SQLAlchemyInstrumentor().instrument()`, to which it passes `**kwargs`.
18281829
18291830
Args:
1830-
engine: The `sqlalchemy` engine to instrument, or `None` to instrument all engines.
1831+
engine: The `sqlalchemy` engine to instrument.
1832+
engines: An iterable of `sqlalchemy` engines to instrument.
18311833
enable_commenter: Adds comments to SQL queries performed by SQLAlchemy, so that database logs have additional context.
18321834
commenter_options: Configure the tags to be added to the SQL comments.
18331835
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods.
@@ -1837,6 +1839,7 @@ def instrument_sqlalchemy(
18371839
self._warn_if_not_initialized_for_instrumentation()
18381840
return instrument_sqlalchemy(
18391841
engine=engine,
1842+
engines=engines,
18401843
enable_commenter=enable_commenter,
18411844
commenter_options=commenter_options or {},
18421845
**{

tests/otel_integrations/test_sqlalchemy.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from collections.abc import Iterator
55
from contextlib import contextmanager
66
from pathlib import Path
7+
from typing import Any
78
from unittest import mock
89

910
import pytest
@@ -210,9 +211,19 @@ def test_sqlalchemy_instrumentation(exporter: TestExporter):
210211
SQLAlchemyInstrumentor().uninstrument()
211212

212213

213-
def test_sqlalchemy_instrumentation_commenter(exporter: TestExporter):
214+
@pytest.mark.parametrize('parameter', ['engine', 'engines'])
215+
def test_sqlalchemy_instrumentation_commenter(parameter: str, exporter: TestExporter):
214216
with sqlite_engine(Path('example3.db')) as engine:
215-
logfire.instrument_sqlalchemy(engine=engine, enable_commenter=True, commenter_options={'db_framework': False})
217+
instrumentation_parameters: dict[str, Any] = {
218+
'enable_commenter': True,
219+
'commenter_options': {'db_framework': False},
220+
}
221+
if parameter == 'engine':
222+
instrumentation_parameters['engine'] = engine
223+
else:
224+
instrumentation_parameters['engines'] = [engine]
225+
226+
logfire.instrument_sqlalchemy(**instrumentation_parameters)
216227
logfire.instrument_sqlite3()
217228

218229
Base.metadata.create_all(engine)
@@ -377,9 +388,13 @@ def sqlite_async_engine(path: Path) -> Iterator[AsyncEngine]:
377388

378389

379390
@pytest.mark.anyio
380-
async def test_sqlalchemy_async_instrumentation(exporter: TestExporter):
391+
@pytest.mark.parametrize('parameter', ['engine', 'engines'])
392+
async def test_sqlalchemy_async_instrumentation(parameter: str, exporter: TestExporter):
381393
with sqlite_async_engine(Path('example2.db')) as engine:
382-
logfire.instrument_sqlalchemy(engine=engine)
394+
if parameter == 'engine':
395+
logfire.instrument_sqlalchemy(engine=engine)
396+
else:
397+
logfire.instrument_sqlalchemy(engines=[engine])
383398

384399
async with engine.begin() as conn:
385400
await conn.run_sync(Base.metadata.create_all)

0 commit comments

Comments
 (0)