PySnooper: a decorator that logs every line your Python function runs
Never use print for debugging again
At a glance
- What is it?
- PySnooper replaces hand-written print statements with a single decorator that records executed lines, local variable changes and timings. It is aimed at developers who want a play-by-play trace without configuring a debugger.
- Who is it for?
- PySnooper fits developers who need a line-by-line trace of a specific function inside a codebase they cannot easily attach a debugger to, and who are willing to accept stderr noise in exchange for zero setup. It is the wrong tool when you need to pause execution, inspect the whole call stack interactively or step through code, because the README describes a logging decorator rather than an interactive debugger.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 114 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem PySnooper solves: print-driven debugging without the print lines
The README frames the situation directly: you are trying to work out why a function is not doing what you expect, you would like a real debugger with breakpoints and watches, but you cannot be bothered to set one up right now. The usual fallback is a scattering of print statements, some of which show variable values. PySnooper's pitch is that you add one decorator line to the function you care about and get a play-by-play log instead, covering which lines ran, when they ran, and when local variables changed.
The intended audience is narrow but real. The README says the decorator can be used in a sprawling enterprise codebase without any setup. That is the case PySnooper is built for: a large or unfamiliar codebase where attaching a debugger is awkward, but where you can still edit one function and run it. It is not aimed at people who already have a working interactive debugging session, nor at production instrumentation, since the output is a trace log written to stderr by default.
How the decorator produces a line-by-line trace
The mechanism visible in the README is a decorator applied to a function, or a context manager used as a with block around a section of code. In both forms it wraps the target and emits a log of the lines that execute, the values of local variables as they appear or change, and timestamps.
The README's example output shows the shape of that log. Lines appear as New var entries when a variable is first bound, followed by timestamped line entries showing the source line that is about to run, and an Elapsed time line at the end. In the with-block example the log records lower, upper and mid as they are assigned, and interleaves the function's own print output with the trace lines.
Two options extend the trace beyond the wrapped frame. The watch argument takes a tuple of expression strings, so values that are not local variables can be reported. The depth argument makes snoop lines appear for functions the wrapped function calls, which is how you follow execution into a helper without decorating it separately. The README points to ADVANCED_USAGE.md for the remaining options rather than listing them, so the full set of controls is not in the main file.
Installing PySnooper and running a first trace
The README says the best way to install PySnooper is with Pip. The command is a single package install:
pip install pysnooperOther installation options listed in the README are conda with the conda-forge channel, yay on Arch Linux and dnf on Fedora. For a first trace, the README's own example decorates a function that converts a number to a list of bits. Save it as a file and run it:
import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)With no argument the trace goes to stderr, so on a terminal you will see the timestamped line entries and the New var entries for bits, number and remainder as the loop runs. If stderr is not convenient, the README shows passing a path as the first argument, and also says you can pass a stream or a callable instead and they will be used:
@pysnooper.snoop('/my/log/file.log')To trace only part of a function rather than the whole body, the README wraps the relevant lines in a with block using pysnooper.snoop() as the context manager, which is useful when the setup code above the block would otherwise clutter the trace.
Where PySnooper stops being the right tool
PySnooper is a logging wrapper, not an interactive debugger. The README describes it as a poor man's debugger and compares it to set -x in Bash. That comparison sets the boundary: you get a record of what already happened, printed after the fact, not a prompt where you can pause, inspect the stack and step forward. If your problem is that a value is wrong somewhere in a call chain you have not identified yet, a trace of one decorated function may not be enough, and the depth option only widens the trace rather than giving you control over it.
The output is also the cost. Every executed line in the wrapped region produces a log entry, and the with-block example shows the trace interleaved with the program's own print output. On a hot loop or a function called thousands of times, that volume is the point of the tool and also its failure mode. The README does not document any sampling, rate limiting or conditional tracing, so the only control you have is how narrowly you place the decorator or the with block.
There is a version question too. The most recent release listed is 1.2.0 from 2024-01-05, and setup.py carries classifiers up to Python 3.15. The last push to the repository was on 2026-06-08, so the project is not archived and has seen activity since that release, but the release itself is the older of those two dates. If you depend on a Python version newer than what the 1.2.0 release was tested against, that gap is worth checking before you build on it.
PySnooper compared with VizTracer and breakpoint()
The two alternatives worth naming take opposite approaches. VizTracer, which appears in the related searches for this project, is a tracing profiler: it records execution and produces a timeline you inspect afterwards in a viewer. The difference from PySnooper is where the work happens. PySnooper writes a text log of lines and variable changes for the region you decorate, read directly in your terminal or log file. VizTracer gives you a visual timeline of a whole run. If you need to see timing and ordering across many functions, a timeline view answers questions a text log makes tedious. If you need to know the value of a local variable at one specific line, a text log is faster to read.
The other alternative is Python's own breakpoint() function, which drops you into an interactive debugger session at the point you call it. That gives you a prompt, stack inspection and stepping, none of which PySnooper provides. The trade-off is setup and interruption: breakpoint() stops the program and expects you to drive it, while the decorator runs to completion and leaves a log. The README's whole argument is that the decorator is the option you will actually reach for when you cannot be bothered to set up the interactive one.
Licence and the cost of keeping PySnooper in a codebase
PySnooper is released under the MIT licence, with copyright held by Ram Rachum and collaborators. MIT is permissive, so the practical constraint is attribution rather than redistribution: the licence text and copyright notice need to travel with the code. This is a general description of the licence, not legal advice for your situation.
The upgrade cost is low in the ordinary case. The public surface described in the README is a decorator, a context manager and a handful of keyword arguments, and the package has no runtime dependencies listed in requirements.in beyond what setup.py installs. A pinned version in a requirements file will not drift on its own.
The real maintenance cost is the traces themselves. A decorator left in place on a frequently called function writes a log line per executed line, and that output has to go somewhere. The README's advice to redirect to a dedicated log file with a path argument is the mitigation it offers; if you adopt PySnooper, decide up front whether the decorator is a temporary edit you remove after debugging or something that stays behind a configuration flag, because the README does not describe any built-in switch for turning tracing off.
Editorial conclusion
PySnooper fits developers who need a line-by-line trace of a specific function inside a codebase they cannot easily attach a debugger to, and who are willing to accept stderr noise in exchange for zero setup. It is the wrong tool when you need to pause execution, inspect the whole call stack interactively or step through code, because the README describes a logging decorator rather than an interactive debugger. Before adopting it, check the ADVANCED_USAGE.md file for the options your case needs, confirm the output destination you want (a path, a stream or a callable), and verify that the last release, 1.2.0 from 2024-01-05, covers your Python version, since setup.py lists classifiers up to Python 3.15.
Frequently asked questions
What is an example of debugging with PySnooper?
The README's own example is a function that converts a number to a list of bits. Adding @pysnooper.snoop() above it writes a trace to stderr showing New var entries for bits, number and remainder as the loop runs, plus timestamped entries for each executed line.
How does PySnooper relate to the breakpoint() function in Python?
They solve different problems. breakpoint() drops you into an interactive debugger session where you can pause, inspect the stack and step, while PySnooper's decorator runs the function to completion and leaves a text log of executed lines and variable changes.
Is PySnooper the best Python debugger?
The README does not make that claim; it calls PySnooper a poor man's debugger and compares it to set -x in Bash. It is a logging decorator rather than an interactive debugger, so it suits tracing one function without setup, not setting breakpoints and stepping through code.
How do I debug my code with PySnooper?
Install it with pip install pysnooper, then add @pysnooper.snoop() above the function you want to inspect, or wrap the relevant lines in a with pysnooper.snoop() block. The trace goes to stderr unless you pass a path, a stream or a callable as the first argument.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cool-rr-pysnooper)