# What it records (/docs/django/what-it-records)

With `tracepanel.django` installed, Tracepanel records these parts of Django.
You don't configure any of them.

## The ORM [#the-orm]

Tracepanel records every SQL query on every database connection, with its
parameters, its time, the rows it returned and the line of your code that ran
it. The async ORM counts too, and Tracepanel keeps the place in your code
across `sync_to_async()`.

From the queries, the agent finds these [issues](/docs/issues):

- **N+1 queries.** The same query repeated for each row, with the
  `select_related()` or `prefetch_related()` that removes it.
- **Duplicate queries.** The same query with the same parameters, more than
  once in a request.
- **Slow SQL.** A query over `slow_sql_ms`. The console runs `EXPLAIN` on it.
  A `LIKE` pattern that starts with `%`, as `contains` and `icontains` make,
  gets a hint about why no index helps.
- **Large results.** A query without a `LIMIT` that returned 1,000 rows or
  more. Tracepanel counts the rows as Django fetches them. The hint names
  `Paginator`, `.iterator()` and `.values_list()`.

A query that a template runs, such as a relation read inside `{% for %}`,
shows under that template's render, so you see which template caused an N+1.

## Transactions [#transactions]

Each `atomic()` block is a transaction, and a nested one is a savepoint. Each
`on_commit()` callback shows where it ran. The agent reports two issues here:

- **Long transactions.** An outer `atomic()` block longer than
  `long_transaction_ms`, with the work inside it.
- **Work inside a transaction.** An email sent, a task queued or an HTTP call
  made before `COMMIT`. If the transaction rolls back, the email is already
  sent. The fix is `transaction.on_commit()`.

## Views, templates and the request [#views-templates-and-the-request]

Each request names its route and its view, after the view's function or class.
Each template render is a step in the call tree. The panel shows the logged-in
user, and a logout records the user who left.

The [panel](/docs/panel) can show your site as another user. See
[Switching the user](/docs/panel#switching-the-user).

## Cache, signals and emails [#cache-signals-and-emails]

- **Cache.** Each call on every backend you create through `caches`, with its
  key and whether it hit. On Redis, the Redis command shows inside the cache
  call. A cache call repeated per item is reported like an N+1, once the
  repeats cost 5 ms together.
- **Signals.** Each signal with receivers in your project, with a step for
  each receiver, so a slow `post_save` handler shows where it ran.
  `pre_init` and `post_init` are left out: they fire for every object a query
  loads.
- **Emails.** Each email sent with `send_mail()`, `EmailMessage` or
  `EmailMultiAlternatives`, with its text and HTML. The console's Emails page
  lists them.

## Calls to other services [#calls-to-other-services]

Tracepanel records what your app sends to other systems, each as a step in
the call tree with the line of your code that made it:

- **HTTP calls** from `requests`, `urllib3`, `httpx` and `aiohttp`. A call
  over `slow_http_ms` is reported as slow.
- **Redis commands** from `redis-py` and `valkey-py`, when you use Redis
  directly. A command repeated per item is reported like an N+1.
- **MongoDB queries** from `pymongo` and Motor. See [MongoDB](#mongodb).

The console has a page for each system your app called, with the calls, their
time and the failures.

When your app calls another app that Tracepanel or OpenTelemetry traces, the
two traces link. An outgoing HTTP call carries a `traceparent` header, and a
Django request that comes with one continues that trace. The call tree shows
the other app's trace under the call, and the console's Services page draws
an arrow from app to app.

![The console's Services page: the apps of a project, the servers they call, and an arrow for each kind of call](/screenshots/console-services.png)

## MongoDB [#mongodb]

Each `pymongo` command is a step, from `MongoClient`, `AsyncMongoClient` or
Motor. Tracepanel records the clients the app creates after it imports
`pymongo`. A step shows the command's shape, such as
`{"find": "users", "filter": {"email": "?"}}`, and keeps its values apart, as
it does SQL parameters.

The agent finds these issues in MongoDB queries:

- **N+1 queries.** A `find` for each item, with `$in` or `$lookup` as the fix.
- **Duplicate queries.** The same `find` with the same values.
- **Slow calls.** A query over `slow_sql_ms`. The hint names the index its
  filter and sort need, as the shell creates it:
  `db.users.createIndex({"email": 1})`.

![The console's MongoDB page: the calls by collection and operation, with a slow find](/screenshots/console-mongodb.png)

## Async views [#async-views]

In an async view, a synchronous call blocks the event loop, and every other
request waits for it. That happens with the sync cache API, such as
`cache.get()`, an HTTP call through `requests`, a `redis-py` command or a
database driver used directly. The ORM itself refuses to run synchronously in
an async view and raises `SynchronousOnlyOperation`.

Tracepanel marks such calls, and the agent reports them as **blocking
calls**: one issue for each place in your code, once its calls cost 5 ms
together. The hint names the async API to use instead, such as `aget()` and
`aset()` on the cache, `httpx.AsyncClient` or `redis.asyncio`. A call that
runs in a thread, through `sync_to_async()` or `asyncio.to_thread()`, blocks
nothing and isn't marked.

## Management commands [#management-commands]

Each management command is a trace of its own, with the same SQL and issues
as a request. The terminal prints a line with a link to it when the command
ends. The `ignore_commands` setting leaves out the dev server, shells, the
test runner and task workers.

## Django libraries [#django-libraries]

Tracepanel traces these libraries when they are installed:

| Library | Setting | What Tracepanel records |
| --- | --- | --- |
| Django REST framework | `drf_enabled` | The view named after the ViewSet action, such as `PostViewSet.list`. Authentication, permissions with the class that refused, throttles, validation with the 400's errors, and serialization as steps. |
| Django Ninja | `ninja_enabled` | The view named after the operation's function. Authentication, input validation with the 422's errors, and response serialization as steps. |
| Channels | `channels_enabled` | Each WebSocket event as a trace: the connect, each frame, each channel layer message and the disconnect. Group messages link to the span that sent them. |
| Django Tasks | `django_tasks_enabled` | Each task run as a trace, linked to the request that queued it. `django.tasks` on Django 6.0 and later, and the `django-tasks` backport on Django 5.2. |
| Celery | `celery_enabled` | Each task run as a trace, linked to the request that queued it, with its time in the queue, retries, failures, arguments and result. |
| Strawberry | `strawberry_enabled` | GraphQL requests named after the operation, and your resolvers as steps. |

RQ, Dramatiq, SQLAlchemy, the database drivers, the HTTP clients, Redis,
MongoDB, htmx and OpenTelemetry work the same as in any Python app. See
[Integrations](/docs/python/integrations).

## Tasks and the request that queued them [#tasks-and-the-request-that-queued-them]

When a request queues a task, the call tree shows the enqueue as a step. Each
run of the task is a trace of its own that links back to it. A task queued
inside `atomic()` is reported as work inside a transaction, since the worker
may run it before the data is committed.

![The console's Tasks page: runs and latency over time, the slowest and the failing tasks](/screenshots/console-tasks.png)

A task that waited in its queue for a second or more is reported as a
**queue wait**: the workers were all busy. Celery, RQ, Dramatiq and Django
Tasks record the wait.

## Turn a part off [#turn-a-part-off]

Each library above has an `*_enabled` setting. Set it to `False` in the
`TRACEPANEL` dict, and Tracepanel leaves the library alone:

```python
# settings.py
TRACEPANEL = {"drf_enabled": False}
```

Tracepanel installs the integrations when Django starts. Restart the server
after you change one. See [Settings](/docs/django/settings).
