Repository navigation
fix(core): log tracebacks without per-frame values on the long-lived sinks - #1663
Open
sammywachtel wants to merge 1 commit into
Open
sammywachtel wants to merge 1 commit into
sammywachtel wants to merge 1 commit into
Conversation
…sinks The file and stdout sinks used loguru's diagnose=True, which calls repr() on every value named in every frame of a logged traceback. Each ASGI middleware frame holds `scope`, and under FastAPI 0.139 repr(scope) is about 190 MB because its "fastapi" entry carries the route's dependency tree. The API's catch-all exception handler logs with logger.exception, so one unhandled request exception repeated that repr once per frame: about 15-20 s of CPU on the event loop on a laptop, during which no other request was served. Use diagnose=False on those two sinks. The traceback is still logged (backtrace=True); only the per-frame value dump goes. Test mode keeps diagnose=True. Signed-off-by: sammywachtel <subp@wachtel.us>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On a server, one unhandled exception in an API request takes 15-20 seconds of CPU to log, and on a small VM it took over a minute. The work runs on the event loop, so no other request is answered until it finishes. A client sees its own request time out, and every other client of that process sees everything stall.
The cause is
diagnose=Trueon the file and stdout sinks insetup_logging. Withdiagnose, loguru callsrepr()on every value named in every frame of a logged traceback, synchronously. The API's catch-all handler (api/app.py,exception_handler) logs withlogger.exception, and the traceback of a request runs through about thirty ASGI middleware frames, each holdingscope. Under FastAPI 0.139,repr(scope)is very large, because its"fastapi"entry carries the route's dependency tree.Evidence
Measured on current
main, in-process, with the test app fromtests/api/v2/conftest.py, on a request that raised insidePOST /v2/projects/{id}/knowledge/resolve:repr(scope): 158,454,847 characters, 0.53 s. The"fastapi"entry alone accounts for 158,452,963 of those characters.On a server process, I profiled one such request with py-spy (1,877 samples). Of the 1,539 main-thread samples, 1,516 were under
exception_handler→loguru._logger.exception→_better_exceptions._format_exception→_get_relevant_values→_format_value, and 1,188 were in dataclass__repr__.py-spy dump --localsshowed the value being formatted was the ASGI scope dict, recursing through_IncludedRouter→_EffectiveRouteContext→Dependant. A concurrent request to the same process waited 13.4 s.The backend makes no difference. SQLite and Postgres both took about 15 s, and the database was idle throughout.
What changed
setup_logging: the file sink and the stdout sink usediagnose=False.backtrace=Truestays, so the full traceback is still logged. Only the dump of each frame's variable values is gone.BASIC_MEMORY_ENV=test) keepsdiagnose=True, so local test failures still show values.Testing
tests/api/v2/test_unhandled_exception_logging.py, both of which fail onmain:test_a_logged_traceback_does_not_repr_frame_values: with the production sinks installed, logging one traceback callsrepr()on a frame value 0 times. Onmain:assert 4 == 0.test_an_unhandled_request_exception_answers_fast: a real unhandled exception through the real app, with the production sinks installed, answers its 500 in under 2 s. Onmain:one unhandled exception took 19.42s to answer. With this change: about 0.2 s.tests/api/v2,tests/utils,tests/test_telemetry.py: 540 passed. All 39 errors are intests/api/v2/test_scoped_search_router.py(SemanticSearchDisabledError, which comes from this machine's environment). They also occur on unchangedmain.ruff format,ruff check,ty check src tests test-int(with themilvusextra installed): clean.Risks / follow-ups
diagnoseprinted. Those values are also a privacy risk on a server, because they can carry request bodies, headers and note content into the log file. The traceback itself is unchanged.repr(scope)comes from FastAPI's internals, so a future FastAPI release could make it larger or smaller. Withdiagnose=Falsethe cost no longer depends on it.