Python trace follows statement execution, counts how many times source lines run, lists functions reached, and records caller-callee relationships. It works from the command line or through a programmatic API, making it useful for quick coverage reports, educational tools, and hard-to-reproduce control-flow diagnostics.
Configuration is simple, but runtime overhead is not. Line tracing can slow a program dramatically and produce enormous output. Use it in development and controlled tests rather than as permanent production monitoring.
Count executed lines
--count runs a script and writes annotated .cover files.
python -m trace --count -C coverage application.pyExecutable lines receive counters. Lines without a number may be comments, docstrings, declarations, or statements that were not considered executable.
Mark missing lines
--missing marks executable lines with no hits using >>>>>>.
python -m trace \
--count \
--missing \
--summary \
-C coverage \
application.pyA per-file summary identifies weakly exercised modules, but line coverage does not prove that conditions, combinations, and results are correct.
Print every executed line
--trace displays source lines as they run.
python -m trace --trace task.pyThe output becomes noisy when imports and dependencies are included. Apply module and directory filters so the report focuses on application code.
Add relative timing
--timing prefixes each traced line with time elapsed since the program started.
python -m trace --trace --timing task.pyPauses can become visible, but tracing overhead affects the timing. For function-level profiling, use Python pstats with cProfile.
List functions reached
--listfuncs reports functions executed during the run.
python -m trace --listfuncs application.pyThis option is mutually exclusive with line tracing and line counting. It is useful for confirming which handlers, callbacks, and feature paths actually ran.
Track call relationships
--trackcalls records caller-callee pairs.
python -m trace --trackcalls application.pyThe report is simpler than a complete profiler, but it can reveal unexpected coupling between modules and functions.
Ignore modules
--ignore-module accepts comma-separated module names and can be repeated.
python -m trace \
--count \
--ignore-module=urllib,json \
-C coverage \
application.pyFilters reduce noise. Do not exclude application code merely to improve a percentage; document the coverage policy.
Ignore directories
--ignore-dir excludes modules located under selected directories.
python -m trace \
--count \
--ignore-dir=.venv \
-C coverage \
application.pyUse the operating system path separator for several directories. Resolve relative paths so a broad match does not hide more files than intended.
Accumulate several runs
--file stores counters that can be reused across scenarios.
python -m trace --count --no-report \
--file counts.dat test_a.py
python -m trace --count --no-report \
--file counts.dat test_b.py
python -m trace --report \
--file counts.dat \
-C coverage--no-report avoids intermediate listings. The final command produces one combined report.
Avoid concurrent writes
The count file is not a transactional database. Several processes writing the same file can lose or corrupt data. Give every worker a separate output and merge results in a controlled step.
Run a module
--module executes a module instead of a script path.
python -m trace --count --module my_package.cliThis better preserves package-relative import behavior.
Use the Trace class
import sys
import trace
tracer = trace.Trace(
count=True,
trace=False,
ignoredirs=[sys.prefix, sys.exec_prefix],
)
tracer.runfunc(run_scenario)
results = tracer.results()
results.write_results(
show_missing=True,
summary=True,
coverdir="coverage",
)runfunc() is preferable to building a command string when the target is already a Python callable.
run, runctx, and runfunc
run() accepts source text or a code object suitable for exec(). runctx() also receives global and local mappings. runfunc() invokes a callable with arguments.
tracer.runctx(
"result = calculate(value)",
{"calculate": calculate},
{"value": 10},
)Never interpolate external input into the command. These methods execute real Python code.
Select collection modes
The constructor controls several independent features:
countrecords line counts.traceprints lines.countfuncslists functions.countcallersrecords call relationships.timingshows relative time.
Enable only what the investigation needs. Combining modes increases overhead and output volume.
Read accumulated results
results() returns CoverageResults without resetting the tracer.
first = tracer.results()
tracer.runfunc(another_scenario)
second = tracer.results()The second object reflects accumulated data. Create another Trace instance for isolated runs.
Merge results
CoverageResults.update() combines another result set.
total.update(worker_result)Only combine data produced from the same source revision. Counts from different commits are not meaningful together.
Handle missing source files
Current versions support ignore_missing_files=True when writing reports.
results.write_results(
coverdir="coverage",
ignore_missing_files=True,
)This is convenient when generated files are cleaned. In audits, a missing source may be important evidence and should not be hidden.
Coverage is not correctness
An executed line can still return the wrong value. The standard module also lacks advanced branch coverage, contexts, HTML output, and plugin integrations available in Coverage.py.
Use trace for quick diagnostics and simple reports. Large projects should normally adopt a dedicated coverage tool.
Trace and traceback are different
The Python traceback guide formats call stacks after exceptions. trace follows normal execution line by line. One explains how an error was reached; the other can show the broader path that ran.
Trace and tracemalloc are different
Python tracemalloc records memory allocations. The similar name does not imply line coverage or execution tracking.
Trace and dis complement each other
Python dis examines compiled instructions, often without executing code. trace observes a particular runtime execution.
Threads and child processes
Tracing relies on interpreter hooks and may not automatically cover every thread created by libraries. Child processes have separate runtimes. Start collection in each worker and merge results afterward.
Async functions and generators
Coroutine and generator lines are counted only when execution enters them. Creating an awaitable or generator without awaiting or iterating it does not contribute coverage. Test scenarios must drive the actual asynchronous flow.
Security
- The tracer executes the target program.
- Do not trace untrusted code in the main process.
- Reports may expose paths and source.
- Restrict the output directory.
- Do not accept external expressions for
run(). - Apply time and resource limits.
- Protect count files and reports.
Recommended practices
- Ignore the standard library and virtual environment.
- Use
--modulefor packages. - Keep counters separate by commit.
- Do not share one count file between workers.
- Review missing lines, not just percentages.
- Combine coverage with behavioral tests.
- Measure tracing overhead.
- Use Coverage.py for branch coverage.
Conclusion
Python trace provides line tracing, execution counts, function listing, and caller relationships without external dependencies. It is valuable for quick diagnostics, teaching, and simple coverage.
Consult the official trace documentation and the official Coverage.py documentation.







