Skip to content

Commit 8ec64fa

Browse files
committed
chore: drop the asyncpg OpenTelemetry instrumentor
Keep SQLAlchemyInstrumentor as the only database instrumentor and add a test that DB queries still produce SQLAlchemy spans.
1 parent a0e425a commit 8ec64fa

5 files changed

Lines changed: 43 additions & 23 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ CI (`.github/workflows/main.yml`) runs `ruff format --check`, `ruff check --no-f
3131
`app/__main__.py` boots Granian pointing at `app.application:build_app` (factory). `build_app()` in `app/application.py`:
3232

3333
1. Creates a `modern_di.Container` with the `Dependencies` group from `app/ioc.py`.
34-
2. Builds a `FastAPIBootstrapper` from `settings.api_bootstrapper_config`, injecting SQLAlchemy + asyncpg OpenTelemetry instrumentors.
34+
2. Builds a `FastAPIBootstrapper` from `settings.api_bootstrapper_config`, injecting the SQLAlchemy OpenTelemetry instrumentor.
3535
3. `modern_di_fastapi.setup_di(app, container)` wires DI scopes onto the FastAPI app.
3636
4. Includes one `ROUTER` per resource (`app.api.decks.ROUTER`, `app.api.cards.ROUTER`) under `/api` via `include_routers`. Add a new resource by creating `app/api/<name>.py` with its own `APIRouter`-based `ROUTER` and registering it there.
3737
5. Registers `DuplicateKeyError` → 422 handler from `app/exceptions.py`.

‎app/application.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import modern_di_fastapi
66
from advanced_alchemy.exceptions import DuplicateKeyError, NotFoundError
77
from lite_bootstrap import FastAPIBootstrapper
8-
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
98
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
109

1110
from app import exceptions, ioc
@@ -26,10 +25,7 @@ def build_app() -> fastapi.FastAPI:
2625
di_container = modern_di.Container(groups=[ioc.Dependencies])
2726
bootstrap_config = dataclasses.replace(
2827
settings.api_bootstrapper_config,
29-
opentelemetry_instrumentors=[
30-
SQLAlchemyInstrumentor(),
31-
AsyncPGInstrumentor(capture_parameters=True),
32-
],
28+
opentelemetry_instrumentors=[SQLAlchemyInstrumentor()],
3329
)
3430
bootstrapper = FastAPIBootstrapper(bootstrap_config=bootstrap_config)
3531
app: fastapi.FastAPI = bootstrapper.bootstrap()

‎pyproject.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies = [
2121
"sqlalchemy[asyncio]<2.1",
2222
"asyncpg",
2323
# tracing
24-
"opentelemetry-instrumentation-asyncpg",
2524
"opentelemetry-instrumentation-sqlalchemy",
2625
]
2726

‎tests/test_tracing.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import dataclasses
2+
from typing import TYPE_CHECKING
3+
4+
from asgi_lifespan import LifespanManager
5+
from fastapi import status
6+
from httpx import ASGITransport, AsyncClient
7+
from opentelemetry import trace
8+
from opentelemetry.sdk.trace import TracerProvider
9+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
10+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
11+
12+
from app.application import build_app
13+
from app.settings import Settings, settings
14+
15+
16+
if TYPE_CHECKING:
17+
import pytest
18+
19+
20+
async def test_db_queries_produce_sqlalchemy_spans(monkeypatch: pytest.MonkeyPatch) -> None:
21+
traced_config = dataclasses.replace(settings.api_bootstrapper_config, opentelemetry_log_traces=True)
22+
monkeypatch.setattr(Settings, "api_bootstrapper_config", traced_config)
23+
exporter = InMemorySpanExporter()
24+
app = build_app()
25+
tracer_provider = trace.get_tracer_provider()
26+
assert isinstance(tracer_provider, TracerProvider)
27+
tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
28+
29+
async with LifespanManager(app), AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
30+
response = await client.get("/api/decks/")
31+
32+
assert response.status_code == status.HTTP_200_OK
33+
db_spans = [
34+
span
35+
for span in exporter.get_finished_spans()
36+
if span.instrumentation_scope is not None
37+
and span.instrumentation_scope.name == "opentelemetry.instrumentation.sqlalchemy"
38+
and span.name.startswith("SELECT")
39+
]
40+
assert db_spans
41+
assert all(span.parent is not None for span in db_spans)

‎uv.lock‎

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)