Configuration¶
Kolo works without any configuration. When you need to change its behaviour, use the .kolo/config.toml file and a few environment variables.
Environment variables¶
KOLO¶
Set KOLO=1 (or true) to trace every Python process that starts with it, from start to finish. See Trace a function or script.
KOLO_DISABLE¶
Set KOLO_DISABLE=1 to turn off KOLO=1 tracing and KoloMiddleware, including its /_kolo/ web UI. It does not affect kolo run, kolo attach or kolo.enable.
For KOLO=1, any non-empty value of KOLO_DISABLE turns tracing off. The middleware treats 0 and false as not disabled.
KOLO_PATH¶
Kolo keeps its config, traces and internal data in a .kolo directory inside the current working directory. Set KOLO_PATH to put the .kolo directory inside a different directory.
KOLO_REPR_REUSE¶
On CPython 3.12.5 and newer, Kolo reuses the text of a value’s __repr__ across events when it can prove the object has not changed. Set KOLO_REPR_REUSE=0 to turn this off.
KOLO_KEEP_DB_CONNECTION and KOLO_REUSE_SAVE_WORKERS¶
Kolo keeps a few things between traces that follow each other closely, so that a process tracing many short requests or tests does not set them up again for every one. Both are on by default, and each can be turned off by setting its variable to 0 before Kolo is imported:
KOLO_KEEP_DB_CONNECTION=0: every trace save, and every check of the store at the start of a trace, opens and closes its own connection to.kolo/.internal/db.sqlite3. Without it, Kolo keeps one connection per store open for up to 50 ms after its last use (not on Windows or Python 3.8, where it never keeps one).KOLO_REUSE_SAVE_WORKERS=0: every trace is saved by akolo-save_trace_in_dbthread of its own that exits when the save is done, and every trace gets a writer thread of its own. Without it, a save thread waits 20 ms for the next trace and the writer thread up to two seconds.
Neither changes what is recorded. They exist so that either can be ruled out, or switched off, without a new release.
config.toml¶
Config lives in .kolo/config.toml. Create the file if it does not exist yet.
pyproject.toml¶
You can put your config in a [tool.kolo] section of pyproject.toml instead. If the pyproject.toml in the current working directory has a [tool.kolo] section, Kolo uses it and ignores .kolo/config.toml.
# pyproject.toml
[tool.kolo]
line_events = true
[tool.kolo.filters]
ignore_frames = ["/migrations/"]
Filters¶
By default Kolo traces all code in your project and ignores standard library or third party code. Filters let you customize this behavior.
Ignore Frames¶
ignore_frames is a list of file path fragments:
# .kolo/config.toml
[filters]
ignore_frames = ["ignore_me.py", "/ignore_directory/"]
This will ignore any file called ignore_me.py or any file with /ignore_directory/ in its path.
Include Frames¶
# .kolo/config.toml
[filters]
include_frames = ["json", "django/db"]
This will include any frames from the json module of the standard library and from Django’s database code in django.db.
Note
Including standard library and third party code can generate a lot of extra data, which may slow down execution. If you notice slow execution, try to make your includes more narrow.
Ignore Request Paths¶
By default KoloMiddleware records a trace for every Django request. To skip some URLs, add path fragments to ignore_request_paths:
# .kolo/config.toml
[filters]
ignore_request_paths = ["/static/", "favicon.ico"]
auto_emit¶
Shortly after each trace is saved, Kolo emits the five most recent traces to .kolo/traces/ as text, updates .kolo/kolo.txt and removes older emitted traces. This runs in the background and is on by default. To turn it off:
# .kolo/config.toml
auto_emit = false
Traces are still saved, and you can emit one yourself with kolo trace emit TRACE_ID. A trace you emit by hand is kept in .kolo/traces/ for at least 24 hours, even after five newer traces have arrived.
line_events¶
By default Kolo records function calls and returns, with the local variables at each. Set line_events to true to also record each assignment to a local variable as it happens:
# .kolo/config.toml
line_events = true
Default:
falseNeeds Python 3.12 or newer (the
sys.monitoringbackend)Tracing is slower with it on
Each function’s emitted trace file lists its assignments in order under --- Assignments ---, as line N: name = value, and kolo cat --returns shows them between a call’s input and return value, marked ≔.
max_value_mb¶
Caps how much trace data a single value may take. Kolo records each argument, return value and local variable at every event, so one runaway value (a huge structure, or one that references the same lists many times over) could otherwise stall the traced program and fill the disk. When a value’s encoded size exceeds this many megabytes, Kolo stops encoding it and records a KoloTruncatedValue: <type> exceeded max_value_mb (<bytes> bytes); raise max_value_mb in .kolo/config.toml or set it to false marker in its place. Other values in the same frame are kept: if all of a frame’s locals together exceed the limit, the limit applies to each local variable separately. Line-event assignments and plugin frames (SQL queries, HTTP requests and responses, log records) are capped the same way: a plugin frame over the limit keeps its fields, with each oversized field replaced by the marker.
# .kolo/config.toml
max_value_mb = 16
Default:
64(64 MB per value)Set to
falseto record every value in full
Values that contain the same list or dict many times (for example a = [x, x]; b = [a, a]; ...) are stored once and referenced afterwards, so they stay small and decode to equal values. When a frame would expand to roughly a gigabyte (counting repeated values everywhere they appear), trace readers show a KoloSharedValue marker for the repeats instead of expanding them.
flush_subtree_mb¶
New in v3.1.0.
Controls subtree flushing for large traces. When the in-progress trace grows past this many megabytes, Kolo spills the largest closed subtree to disk as its own child trace, leaving a subtree_flushed placeholder in the parent. This keeps memory bounded on long-running traces.
# .kolo/config.toml
flush_subtree_mb = 100
Default:
500(500 MB)Set to
falseto disable subtree flushing entirely
Flushing is not exact — the actual trace size can overshoot the threshold somewhat before a coarse-enough closed subtree becomes available to flush.
Trace points¶
New in v3.1.0. Needs Python 3.12 or newer (the sys.monitoring backend).
Trace points let you nominate functions whose returns should produce standalone, top-level traces. When a listed function returns, the frames from its call to its return are saved as their own first-class trace with a fresh trace_id — independent of whatever outer trace is in flight.
# .kolo/config.toml
[trace_points]
on_return = ["dispatch", "process_job"]
Useful for splitting long-running processes into smaller chunks, for example when tracing using KOLO=1. Matches are done by function name.
lightweight_repr¶
Some Python objects have __repr__ methods that are expensive to call. By default Kolo serializes unknown objects by calling repr, so this can have a significant impact on Kolo’s overhead. If you think this is a problem in your project, you can set the lightweight_repr config option to true.
In Django projects, Kolo refuses database access through Django’s database layer from a __repr__ or __str__ it calls while recording a value. Loading a deferred field, an unloaded relation or a lazy object is stopped before the query runs, and the value is recorded as <User pk=1 deferred=[first_name, username]: Kolo skipped repr to avoid a database query> for model instances (the deferred part only when fields are deferred), or <module.Class object: Kolo skipped repr to avoid a database query> for anything else. Other ORMs, such as SQLAlchemy, are not covered.
Kolo also never calls repr on an object whose constructor is still running. While __new__, __init__, __post_init__ or __setstate__ is on the stack, the object it is building is recorded as <module.Class object: Kolo skipped repr during construction>, because a __repr__ that reads a lazily computed, cached property would cache a value derived from fields that are not set yet, and the program would keep reading it. The object is recorded in full from the moment its constructor returns (that return included). Objects whose serialization runs none of their class’s own code are not affected: those with the default object.__repr__, and for example exceptions or dict subclasses that leave the builtin’s methods alone. Cases Kolo cannot see: an object made with a bare cls.__new__(cls) and filled in by its caller; a __repr__ of another object that itself reprs the half-built one; a constructor wrapped by a decorator, while the wrapper’s own frame is the one running; an object its constructor has handed to another thread; a constructor with no positional parameter but keyword-only ones (def __init__(*args, flag=False)); and a class that is still being created (__init_subclass__, __set_name__).
Two environment variables, read once when Kolo is imported, turn this off. KOLO_CONSTRUCTION_CHECK=0 disables it altogether: Kolo calls repr on every object again, whatever is running. KOLO_CONSTRUCTION_NATIVE=0 keeps the protection but finds the running constructors in Python only, without the faster native stack walk that reads CPython’s internal frame layout; use it if that walk misbehaves on your interpreter.
use_monitoring and use_rust¶
On Python 3.12 and newer Kolo traces with sys.monitoring, implemented in Rust. This is the fastest backend and the right choice for almost everyone. To fall back to the older sys.setprofile profiler, set use_monitoring = false. To use the pure Python implementation of either, set use_rust = false.
hide_startup_message¶
When the django development server starts up, Kolo prints out a message to let users know where to find the Kolo web UI. This message can be disabled by setting the hide_startup_message config option to true.