Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def api_bootstrapper_config(self) -> LitestarConfig:
service_environment=self.service_environment,
service_debug=self.service_debug,
opentelemetry_endpoint=self.opentelemetry_endpoint,
opentelemetry_exclude_spans=["receive", "send"],
sentry_dsn=self.sentry_dsn,
cors_allowed_origins=self.cors_allowed_origins,
cors_allowed_methods=self.cors_allowed_methods,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
license = "MIT"
dependencies = [
"litestar",
"lite-bootstrap[litestar-all]",
"lite-bootstrap[litestar-all]>=1.13",
"modern-di-litestar>=4,<5",
"advanced-alchemy",
"pydantic-settings",
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ OTEL_TRACES_SAMPLER_ARG=0.1

This keeps one trace in ten, and follows the caller's decision when a request arrives with a trace context. Errors still reach Sentry when `SENTRY_DSN` is set.

The template also turns off the ASGI `send` and `receive` spans (`opentelemetry_exclude_spans` in `app/settings.py`), so each request records its server span and database spans only. With every request traced, that raised throughput by 6 to 7% in the same benchmark. Remove the setting if you need those spans.

## 📝 [License](https://github.com/modern-python/litestar-sqlalchemy-template/blob/main/LICENSE)

## Part of `modern-python`
Expand Down
15 changes: 12 additions & 3 deletions tests/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from opentelemetry.trace import SpanKind

from app.application import build_app
from app.settings import Settings, settings


async def test_db_queries_produce_sqlalchemy_spans(monkeypatch: pytest.MonkeyPatch) -> None:
async def test_traced_request_records_server_and_db_spans_only(monkeypatch: pytest.MonkeyPatch) -> None:
traced_config = dataclasses.replace(settings.api_bootstrapper_config, opentelemetry_log_traces=True)
monkeypatch.setattr(Settings, "api_bootstrapper_config", traced_config)
exporter = InMemorySpanExporter()
Expand All @@ -29,12 +30,20 @@ async def test_db_queries_produce_sqlalchemy_spans(monkeypatch: pytest.MonkeyPat
response = await client.get("/api/decks/")

assert response.status_code == status_codes.HTTP_200_OK
spans = exporter.get_finished_spans()
sqlalchemy_scope = "opentelemetry.instrumentation.sqlalchemy"
db_spans = [
span
for span in exporter.get_finished_spans()
for span in spans
if span.instrumentation_scope is not None
and span.instrumentation_scope.name == "opentelemetry.instrumentation.sqlalchemy"
and span.instrumentation_scope.name == sqlalchemy_scope
and span.name.startswith("SELECT")
]
assert db_spans
assert all(span.parent is not None for span in db_spans)
request_spans = [
span
for span in spans
if span.instrumentation_scope is None or span.instrumentation_scope.name != sqlalchemy_scope
]
assert [span.kind for span in request_spans] == [SpanKind.SERVER]
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading