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
14 changes: 14 additions & 0 deletions docs/integrations/litestar.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,17 @@ application = bootstrapper.bootstrap()
```

Read more about available configuration options [here](../../../introduction/configuration):

## Logging

Structlog is integrated via Litestar's `StructlogPlugin`, which makes `request.logger` available in route handlers:

```python
from litestar import Request, get


@get("/items")
async def list_items(request: Request) -> list[str]:
request.logger.info("listing items")
return []
```
15 changes: 15 additions & 0 deletions docs/introduction/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ Additional parameters:
- `logging_extra_processors`
- `logging_unset_handlers`.

### Structlog Litestar

When using Litestar, the `StructlogPlugin` is automatically registered, which enables `request.logger` inside route handlers:

```python
from litestar import Litestar, Request, get
from lite_bootstrap import LitestarConfig, LitestarBootstrapper


@get("/")
async def handler(request: Request) -> dict[str, str]:
request.logger.info("handling request")
return {"status": "ok"}
```

### Structlog FastStream

When using FastStream, the structlog logger is automatically injected into the broker so that all broker
Expand Down
24 changes: 24 additions & 0 deletions lite_bootstrap/bootstrappers/litestar_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import litestar
from litestar.config.app import AppConfig
from litestar.config.cors import CORSConfig
from litestar.logging.config import StructLoggingConfig
from litestar.openapi import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
from litestar.plugins.prometheus import PrometheusConfig, PrometheusController
from litestar.plugins.structlog import StructlogConfig, StructlogPlugin
from litestar.static_files import create_static_files_router

if import_checker.is_litestar_opentelemetry_installed:
Expand All @@ -42,6 +44,9 @@
if import_checker.is_opentelemetry_installed:
from opentelemetry.trace import get_tracer_provider

if import_checker.is_structlog_installed:
import structlog


def build_span_name(method: str, route: str) -> str:
if not route:
Expand Down Expand Up @@ -144,6 +149,25 @@ def bootstrap(self) -> None:
class LitestarLoggingInstrument(LoggingInstrument):
bootstrap_config: LitestarConfig

def bootstrap(self) -> None:
self._unset_handlers()
if import_checker.is_structlog_installed and import_checker.is_litestar_installed:
self.bootstrap_config.application_config.plugins.append(
StructlogPlugin(
config=StructlogConfig(
structlog_logging_config=StructLoggingConfig(
processors=self.structlog_processors,
logger_factory=self.memory_logger_factory,
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
pretty_print_tty=False,
standard_lib_logging_config=None,
),
),
)
)
self._configure_foreign_loggers()


@dataclasses.dataclass(kw_only=True, frozen=True)
class LitestarOpenTelemetryInstrument(OpenTelemetryInstrument):
Expand Down
30 changes: 19 additions & 11 deletions lite_bootstrap/instruments/logging_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,28 @@ def _unset_handlers(self) -> None:
for unset_handlers_logger in self.bootstrap_config.logging_unset_handlers:
logging.getLogger(unset_handlers_logger).handlers = []

@property
def structlog_processors(self) -> list[typing.Any]:
return [
structlog.stdlib.filter_by_level,
*self.structlog_pre_chain_processors,
*self.bootstrap_config.logging_extra_processors,
structlog.processors.JSONRenderer(serializer=_serialize_log_with_orjson_to_string),
]

@property
def memory_logger_factory(self) -> "MemoryLoggerFactory":
return MemoryLoggerFactory(
logging_buffer_capacity=self.bootstrap_config.logging_buffer_capacity,
logging_flush_level=self.bootstrap_config.logging_flush_level,
logging_log_level=self.bootstrap_config.logging_log_level,
)

def _configure_structlog_loggers(self) -> None:
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
*self.structlog_pre_chain_processors,
*self.bootstrap_config.logging_extra_processors,
structlog.processors.JSONRenderer(serializer=_serialize_log_with_orjson_to_string),
],
processors=self.structlog_processors,
context_class=dict,
logger_factory=MemoryLoggerFactory(
logging_buffer_capacity=self.bootstrap_config.logging_buffer_capacity,
logging_flush_level=self.bootstrap_config.logging_flush_level,
logging_log_level=self.bootstrap_config.logging_log_level,
),
logger_factory=self.memory_logger_factory,
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
Expand Down
16 changes: 16 additions & 0 deletions tests/test_litestar_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ async def get_item(item_id: int) -> dict[str, int]:
assert any("GET /items/{item_id}" in name for name in span_names)


def test_litestar_request_logger(litestar_config: LitestarConfig) -> None:
@litestar.get("/log-test")
async def log_handler(request: litestar.Request) -> dict[str, str]:
request.logger.info("test log from handler", key="value")
return {"status": "ok"}

config = dataclasses.replace(litestar_config, application_config=AppConfig(route_handlers=[log_handler]))
bootstrapper = LitestarBootstrapper(bootstrap_config=config)
application = bootstrapper.bootstrap()

with TestClient(app=application) as client:
response = client.get("/log-test")
assert response.status_code == status_codes.HTTP_200_OK
assert response.json() == {"status": "ok"}


def test_build_span_name_no_route() -> None:
assert build_span_name("GET", "") == "GET"

Expand Down
Loading