Repository navigation
Description
LitestarLoggingInstrument._configure_structlog_loggers passes StructLoggingConfig(standard_lib_logging_config=None) (lite_bootstrap/bootstrappers/litestar_bootstrapper.py:195-201). Litestar's StructLoggingConfig.__post_init__ treats None as "use the default" and replaces it with a stdlib LoggingConfig (litestar/logging/config.py:509-519 in litestar 2.24.0), whose configure() runs dictConfig at app startup. Observed after startup on 705d8a3:
- The root logger's handlers are
[QueueHandler]. lite-bootstrap's rootStreamHandlerwithProcessorFormatter(lite_bootstrap/instruments/logging_instrument.py:176-191) is gone, so stdlib records from any library come out in Litestar's format (nologger, notracing, an extramessagekey) on stderr instead of lite-bootstrap's JSON on stdout. - The
litestarlogger has Litestar'sQueueHandler, andMemoryLoggerFactoryadds itsMemoryHandleron first use (lite_bootstrap/instruments/logging_factory.py:120-133). Each record on that logger (the access log fromlitestar_logging_middleware_enabled, Litestar's exception log) is written twice: once as lite-bootstrap JSON, once by Litestar's queue listener thread as a JSON string nested inside another JSON object.
The duplicate also costs time. Measured on 2026-10-24 at 705d8a3 on Linux with 2 CPUs (CPython 3.14.9), with litestar_logging_middleware_enabled=True and the fix below as a prototype: 29 µs per request in-process (+74.4 to +45.2 µs over no logging) and 61 µs on Granian (1 worker, ab -k -c 16, +115.4 to +54.0 µs). With the access log off and nothing else logging, it costs nothing. The tables and scripts are to be published with #335.
A fix that worked in the prototype: pass standard_lib_logging_config=LoggingConfig(configure_root_logger=False, loggers={"litestar": {"level": ..., "handlers": [], "propagate": False}}). That leaves one line per record, keeps lite-bootstrap's root handler, and renders foreign records as lite-bootstrap JSON.
Separately, the access log renders content_type as ["",{}], Litestar's (media_type, options) tuple. Out of scope here; worth its own issue if it matters.
Work
- Test first, both failing today: after a Litestar app bootstrapped with logging on has started (
TestClientas a context manager), the root logger still carries lite-bootstrap'sStreamHandlerand noQueueHandler; and withlitestar_logging_middleware_enabled=Trueone request produces exactly one "HTTP Request" and one "HTTP Response" record across stdout and stderr. - Pass the
LoggingConfigabove from_configure_structlog_loggers, with thelitestarlevel taken fromlogging_log_level. - Check that Litestar's exception log still reaches lite-bootstrap's output once, and that
teardown()leaves no Litestar handler behind. - Docs: none, unless
docs/integrations/litestar.mdshows the old output.
Verify: the new tests pass along with the existing access-log tests in tests/test_litestar_bootstrap.py.
Changes the output of stdlib records on Litestar back to lite-bootstrap's JSON on stdout, as on every other framework. A service that parsed Litestar's format on stderr has to adjust. No config change.
Activity