Skip to content

Reporters

Reporters format analysis results for different audiences and workflows. Each reporter receives the same DiagnosisReport and outputs it in its own format. Multiple reporters can be active simultaneously.


Configuring Reporters

The middleware dispatches to reporters named in the REPORTERS setting. Three names are recognized: "console", "json", and "log".

settings.py
QUERY_DOCTOR = {
    "REPORTERS": ["console"],  # Default
}
QUERY_DOCTOR = {
    "REPORTERS": ["console", "json", "log"],
    "JSON_REPORT_PATH": "reports/query-doctor.json",  # used by the json reporter
}

There is no severity filter setting for reporters; every prescription in the report is output. To suppress specific findings, use a .queryignore file.


Console

Terminal output. Uses Rich panels and colors when Rich is installed, and falls back to plain text without it. Rich is optional:

pip install django-query-doctor[rich]

Example output (plain text fallback):

============================================================
Query Doctor Report
Total queries: 127 | Time: 342.5ms | Issues: 2
============================================================

CRITICAL: N+1 detected: 50 queries for table "myapp_author" (via Book.author)
   Location: /app/myapp/views.py:42 in book_list
   Fix: Add .select_related('author') to your Book queryset
   Queries: 50 | Est. savings: ~120.0ms

WARNING: Duplicate query: 12 identical queries for table "books_category"
   Location: /app/myapp/serializers.py:18 in to_representation
   Fix: Assign the queryset result to a variable and reuse it instead of executing the same query multiple times
   Queries: 12 | Est. savings: ~8.3ms

JSON

Structured JSON for CI/CD pipelines and automated tooling. When JSON_REPORT_PATH is set, the middleware's JSON reporter writes the report to that file after each analyzed request:

settings.py
QUERY_DOCTOR = {
    "REPORTERS": ["json"],
    "JSON_REPORT_PATH": "reports/query-doctor.json",
}

Example output:

{
  "version": "2.3.1",
  "timestamp": "2026-07-14T12:00:00+00:00",
  "summary": {
    "total_queries": 127,
    "total_time_ms": 342.5,
    "issues_found": 2,
    "critical": 1,
    "warnings": 1,
    "info": 0
  },
  "prescriptions": [
    {
      "issue_type": "n_plus_one",
      "severity": "critical",
      "description": "N+1 detected: 50 queries for table \"myapp_author\" (via Book.author)",
      "fix_suggestion": "Add .select_related('author') to your Book queryset",
      "location": {"file": "/app/myapp/views.py", "line": 42, "function": "book_list"},
      "query_count": 50,
      "estimated_savings_ms": 120.0
    }
  ]
}

Filter with jq: jq '.prescriptions[] | select(.severity == "critical")' reports/query-doctor.json

The check_queries management command produces the same JSON with --format json (optionally --output <path>); see Management Commands.


Log

Outputs prescriptions through Python's logging module (logger name query_doctor), integrating with your existing log infrastructure (files, Sentry, ELK, CloudWatch).

settings.py
QUERY_DOCTOR = {
    "REPORTERS": ["log"],
}

Severity mapping: CRITICAL → logging.ERROR, WARNING → logging.WARNING, INFO → logging.INFO.

settings.py
LOGGING = {
    "version": 1,
    "handlers": {
        "query_doctor_file": {
            "level": "WARNING",
            "class": "logging.FileHandler",
            "filename": "logs/query_doctor.log",
        },
    },
    "loggers": {
        "query_doctor": {
            "handlers": ["query_doctor_file"],
            "level": "WARNING",
        },
    },
}

Note: the query_doctor logger is also used for the package's own diagnostic warnings, so a handler attached to it receives both.


HTML Reports

Two HTML outputs exist, both generated by management commands rather than the middleware:

  • python manage.py diagnose_project --format html --output report.html -- project-wide health report (per-app scores, per-URL findings).
  • python manage.py query_doctor_report --output report.html -- QueryTurbo benchmark dashboard (cache hit rates, Chart.js graphs). See Benchmark Dashboard.

OpenTelemetry

An OTelReporter class ships in query_doctor.reporters.otel_exporter. It exports the report as a span (query_doctor.diagnosis) with summary attributes (query_doctor.total_queries, query_doctor.total_time_ms, query_doctor.issues_found) and one span event per prescription. If the opentelemetry packages are not installed, it is a no-op.

Not wired to REPORTERS: the middleware only recognizes console, json, and log. To use OTelReporter, invoke it yourself, e.g. from your own code:

from query_doctor.context_managers import diagnose_queries
from query_doctor.reporters.otel_exporter import OTelReporter

with diagnose_queries() as report:
    ...  # your ORM code
OTelReporter().report(report)

OpenTelemetry configuration (exporter endpoint, service name) is done through the standard OTel SDK/environment variables, not through QUERY_DOCTOR settings.


Next Steps