You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In a project with fastapi I found myself looking at insanely long stacktraces which aren't just boring, but also irrelevant to my debugging of errors in production. My suggestion is this, that format takes another kwarg "truncate_suppressed_to_lines" (or something similar). If this is set to 2, it would truncate for example 10 "boring" frames down to 1 boring frame before and one after the truncated part of the stack:
File: ....
[ --- 8 frames truncated ---]
File: ...
Given truncate_suppressed_to_lines=4 you would get 2 boring frames, then 6 frames truncated, then 2 boring frames, etc. Or perhaps it is more useful with 3 boring, then truncated, then 1?
Looking at the source code this looks fairly easy to do, it only looks like some changes in format_stack() is needed to achieve it.
If this is interesting I can send you a PR with this, also I can include typing through a .pyi for the public facing methods in init.py if you'd like?
If possible, can you post an example of such a long traceback so I can better understand the issue? The current option to write just 1 line per boring frame is pretty compact already compared to standard python tracebacks, so I'd like to learn more
I also thought for a moment about how to fit it into the existing options without making it too confusing. How about a boolean that simply controls whether or not the frames below the first within boring code are rendered? That should solve the too many 'irrelevant lines' problem while being relatively simple to understand. (Adding configurable depth after which frames are skipped seems like one step too far in terms of config complexity. The current method that shows the first frame in boring code incl arguments passed is the right balance, it basically says "consider everything in these paths a black box, but do show me how my code called the black box")
suppressed_paths: list of regex patterns
Set less verbose formatting for frames whose code lives in certain paths
(e.g. library code). Files whose path matches any of the given regex
patterns will be considered boring. The first call to boring code is
rendered with fewer code lines (but with argument values still visible),
- while deeper calls within boring code get a single line and no variable- values.+ while deeper calls within boring code are rendered as a single line+ (or skipped completely if `skip_suppressed_paths` is True).
Example: To hide numpy internals from the traceback, set
`suppressed_paths=[r"lib/python.*/site-packages/numpy"]`
or
`suppressed_paths=[re.compile(r"lib/python.*/site-packages/numpy")]`
++ skip_suppressed_paths: bool (default False)+ completely omit frames beyond the first call into boring code+ (= files matched by `suppressed_paths`)
Some open questions still -- I guess we'd still need to count how many frames will be skipped, and only if it's more than one or so display something like "Skipped due to regex match", because skipping only makes sense if the frames we're skipping would've taken considerably more space than the current behavior of just printing 1 line per frame.. but it can be an opinionated number, doesn't need to be configurable IMO.
Overall, not completely sure about the tradeoff re complexity, hence my interest in an example
Hey! Here's a sample of what is a very simple stack trace from a basic test of a global exception handler i FastAPI. It is an ultrasimple endpoint directly attached to the main app instance of FastAPI:
def test_global_exception_handler_multipart_urlencode(client: TestClient, caplog: pytest.LogCaptureFixture):
# Defining a temporary route that raises an error
@app.post("/force-error/{path_param}")
def force_error_post(path_param: str) -> None:
raise ValueError("Dette er en simulert uventet feil")
with caplog.at_level("ERROR"):
response_post = client.post("/force-error/testvalue", data={"key": "value", "password": "rosebud"})
assert response_post.status_code == 500
data_post = response_post.json()
app_err = AppErrorDetail.model_validate(data_post["detail"])
print(data_post)
assert app_err.i18nkey == AppErrorStringKey.INTERNAL_SERVER_ERROR
assert "Dette er en simulert uventet feil" not in response_post.text
assert "POST" in caplog.text
assert "Dette er en simulert uventet feil" in caplog.text
assert "path_params:({'path_param': 'testvalue'})" in caplog.text
assert "body=(key=['value'], password=['*******'])" in caplog.text
Path("/tmp/test_app_exception.log").write_text(caplog.text)
Se the attached log file... it's insanely long, with 21 lines of internals of fastapi and starlette (which fastapi builds on) alone, which are pretty much useless for debugging the errors in my own code. One such log entry i 6.8k...
Somehow this mockup of doing skipped frames feels even more cluttered / confusing than the original The single-line version actually creates a block of text that I can easily visually skip over, and doesn't throw away any information (you can theoretically reconstruct all information of the default stracktrace with the file & lineno given). Could this be a good compromise?
alright, conclusion: I'm open to both, the singleline version, or skipping with well chosen formatting (perhaps the key is not to indent the "skipped x frames" message)
and definitely happy to take a PR (though I can't promise a super quick review turnaround if the diff is complex)
side note: I had trouble initially to understand the intended usage of truncate_suppressed_to_lines -- maybe a simple bool (e.g. "skip_suppressed_frames - show only the first and last of a consecutive block of boring frames") is enough and reduces the cognitive hurdle
A simple bool and including just the first and last of the block of boring frames sounds like the best solution, agreed! I have some vacation coming up so should have time to make a PR with the suggested changes.
Want me to include a .pyi with typing for the public facing api as well?
Activity
cknd commentedon Jan 19, 2026
Hi, thank you!
If possible, can you post an example of such a long traceback so I can better understand the issue? The current option to write just 1 line per boring frame is pretty compact already compared to standard python tracebacks, so I'd like to learn more
I also thought for a moment about how to fit it into the existing options without making it too confusing. How about a boolean that simply controls whether or not the frames below the first within boring code are rendered? That should solve the too many 'irrelevant lines' problem while being relatively simple to understand. (Adding configurable depth after which frames are skipped seems like one step too far in terms of config complexity. The current method that shows the first frame in boring code incl arguments passed is the right balance, it basically says "consider everything in these paths a black box, but do show me how my code called the black box")
suppressed_paths: list of regex patterns Set less verbose formatting for frames whose code lives in certain paths (e.g. library code). Files whose path matches any of the given regex patterns will be considered boring. The first call to boring code is rendered with fewer code lines (but with argument values still visible), - while deeper calls within boring code get a single line and no variable - values. + while deeper calls within boring code are rendered as a single line + (or skipped completely if `skip_suppressed_paths` is True). Example: To hide numpy internals from the traceback, set `suppressed_paths=[r"lib/python.*/site-packages/numpy"]` or `suppressed_paths=[re.compile(r"lib/python.*/site-packages/numpy")]` + + skip_suppressed_paths: bool (default False) + completely omit frames beyond the first call into boring code + (= files matched by `suppressed_paths`)Some open questions still -- I guess we'd still need to count how many frames will be skipped, and only if it's more than one or so display something like "Skipped due to regex match", because skipping only makes sense if the frames we're skipping would've taken considerably more space than the current behavior of just printing 1 line per frame.. but it can be an opinionated number, doesn't need to be configurable IMO.
Overall, not completely sure about the tradeoff re complexity, hence my interest in an example
cknd commentedon Jan 19, 2026
ah btw - what if we simply skip the newlines between the 1-line prints for boring frames? bam, 50% reduction of spam. should be easy to change
tepedersen commentedon Jan 21, 2026
Hey! Here's a sample of what is a very simple stack trace from a basic test of a global exception handler i FastAPI. It is an ultrasimple endpoint directly attached to the main app instance of FastAPI:
Se the attached log file... it's insanely long, with 21 lines of internals of fastapi and starlette (which fastapi builds on) alone, which are pretty much useless for debugging the errors in my own code. One such log entry i 6.8k...
test_app_exception.log
cknd commentedon Jan 23, 2026
I see! thanks
I've playing around with these outputs a little bit to try various ideas.
original.txt
log_skipped.txt
log_singleline.txt
Somehow this mockup of doing skipped frames feels even more cluttered / confusing than the original The single-line version actually creates a block of text that I can easily visually skip over, and doesn't throw away any information (you can theoretically reconstruct all information of the default stracktrace with the file & lineno given). Could this be a good compromise?
cknd commentedon Jan 23, 2026
maybe just a question of formatting though - this variant of skipping is a little better
log_skipped_2.txt
cknd commentedon Jan 23, 2026
alright, conclusion: I'm open to both, the singleline version, or skipping with well chosen formatting (perhaps the key is not to indent the "skipped x frames" message)
and definitely happy to take a PR (though I can't promise a super quick review turnaround if the diff is complex)
cknd commentedon Jan 23, 2026
your
[ --- 8 frames truncated ---]is perfect actuallylog_skipped_3.txt
cknd commentedon Jan 23, 2026
side note: I had trouble initially to understand the intended usage of
truncate_suppressed_to_lines-- maybe a simple bool (e.g."skip_suppressed_frames - show only the first and last of a consecutive block of boring frames") is enough and reduces the cognitive hurdletepedersen commentedon Jan 23, 2026
A simple bool and including just the first and last of the block of boring frames sounds like the best solution, agreed! I have some vacation coming up so should have time to make a PR with the suggested changes.
Want me to include a .pyi with typing for the public facing api as well?
cknd commentedon Jan 24, 2026
awesome thanks!
the pyi sounds like something for a separate PR since the project doesn't have it yet for anything
tepedersen commentedon Jan 30, 2026
Implemented the proposed feature and sent you a PR. I'll look into creating the typing info tomorrow.
tepedersen commentedon Mar 25, 2026
Hey, had the time to look at the PR?