Trace Django requests¶
KoloMiddleware records one trace per Django request. It is one of several ways to capture a trace: to trace a management command, a test run or a script in a Django project, use KOLO=1 or kolo run instead.
Set up the middleware¶
Install Kolo using
pip install kolo.Add
"kolo.middleware.KoloMiddleware"to the top of yourMIDDLEWARElist insettings.py:# settings.py MIDDLEWARE = [ "kolo.middleware.KoloMiddleware", "django.middleware.security.SecurityMiddleware", ... ]
Start your server with
python manage.py runserver. Kolo confirms it is active:View recent requests at http://127.0.0.1:8000/_kolo/
Make a request to any page.
Read the trace¶
Each request is saved as its own trace and emitted to .kolo/traces/. A request trace contains the request and response, the view and the functions in your project that it called, and the SQL queries and outbound HTTP requests made along the way.
kolo trace list # one line per request: status, method, path, trace ID
kolo cat # call tree of the most recent request
grep -rn "auth_user" .kolo/traces/ # find a query, function or value across recent requests
Kolo also serves a web UI for browsing traces at /_kolo/ on your development server, for example http://localhost:8000/_kolo/.
Which SQL is recorded¶
Kolo records the statements the Django ORM compiles and runs: queryset reads, save(), create(), update(), delete(), bulk_create() and raw(). A query that raises is recorded too. When Kolo traces with sys.monitoring (Python 3.12 and later) it is marked with the exception ([raised IntegrityError]).
Some statements do not go through the ORM’s execute_sql and are not recorded, so a trace can show fewer statements than connection.queries or assertNumQueries count:
On Django 6.0 and later, the
UPDATE ... RETURNINGthatsave()runs when a field is assigned an expression (obj.count = F("count") + 1).Transaction control:
SAVEPOINT,RELEASE SAVEPOINT,BEGIN,COMMITandROLLBACK(for example fromtransaction.atomic()).SQL your code or a library runs directly on a cursor (
connection.cursor().execute(...),executemany,callproc), including migrations and schema changes.
To see every statement, add Django’s cursor wrapper to include_frames. Each CursorWrapper.execute call then appears as a function call with its sql and params:
[filters]
include_frames = ["django/db/backends/utils"]
When the middleware is active¶
KoloMiddleware is for local development. By default it only records requests when Django’s DEBUG setting is True, and it only ever serves the /_kolo/ web UI when DEBUG is True. With DEBUG = False, requests to /_kolo/ fall through to your own URL configuration.
Setting the KOLO_DISABLE environment variable (to anything other than 0 or false) turns off both recording and the web UI. The middleware also stops recording while another profiler is active in the process.
If you would rather not have the middleware installed outside development at all, add it conditionally:
# settings.py
if DEBUG:
MIDDLEWARE.insert(0, "kolo.middleware.KoloMiddleware")
To skip some URLs, set ignore_request_paths in the filters config.