Instrumenting SQL Queries in Django: A Deep Dive

This guide explores how to instrument SQL queries within Django, using the built-in ORM capabilities to facilitate query logging, debugging, performance monitoring, and more.
Table of Contents
Django's BaseDatabaseWrapper class represents an individual database connection, serving as an interface between your application and the underlying database engine. This class has various methods and attributes to facilitate SQL execution and manage the connection.
Location: db.backends.base
To monitor and manipulate SQL queries during execution, the execute_wrappers attribute is used within the BaseDatabaseWrapper. This attribute is a stack of functions that wrap calls to execute() and executemany() in CursorWrapper.
execute_wrappers functions act as middleware, intervening in SQL query execution. They accept five parameters: execute, sql, params, many, and context, allowing inspection or modification of the SQL statement and parameters.
Developers can utilize the execute_wrapper function to append custom wrappers for advanced capabilities such as logging and performance monitoring.
Execute wrappers are a subpart of the backend[mysql, psogres] connection class class DatabaseWrapper(BaseDatabaseWrapper): from where one can create CursorWrapper.
In the normal Django way, sqlcompilor creates the cursor of the CursorWrapper class, which executes execute_wrappers [i.e execute_wrappers is a list]
BASIC Flow:
Model > ModelManager > queryset > SQLCOMPILOR [initialises the cursor] > DB-BACKEND-BASE
The CursorWrapper class manages query execution, invoking functions in execute_wrappers sequentially, with the required parameters. This class ensures adherence to specific protocols and logic.
In your local environment, add the following to your logger configs in settings.py:
"django.db": { "handlers": ["console"], "level": "DEBUG", }
Logging when debug is equal to False
Utilize the following code to log SQL queries:
import logging
def logsql(execute, sql, params, many, context):
start = time.monotonic()
try:
return execute(sql, params, many, context)
finally:
duration = time.monotonic() - start
logging.debug(
"(%.3f) %s; args=%s",
duration,
sql,
params,
extra={"duration": duration, "sql": sql, "params": params},
)
Instrumenting a particular View
def my_view(request):
context = {...}
template_name = ...
with connection.execute_wrapper(logsql):
pass
Instrumenting every query in the Django application using middleware
class DatabaseInstrumentationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
with connection.execute_wrapper(DatabaseLogger()):
return self.get_response(request)
class DatabaseLogger:
def __call__(self, execute, sql, params, many, context):
start = time.monotonic()
try:
return execute(sql, params, many, context)
finally:
duration = time.monotonic() - start
logging.debug(
"(%.3f) %s; args=%s",
duration,
sql,
params,
extra={"duration": duration, "sql": sql, "params": params},
)
Note
For multi-DB setups or read replicas, use connections["name"] to instrument a particular database connection. By default, only the default database is instrumented.
For more Ref : https://docs.djangoproject.com/en/4.2/topics/db/instrumentation/
If you're involved in incident management, Xurrent IMR can enhance your MTTA & MTTR by at least 60%. With our platform, your engineers receive timely alerts, reducing fatigue and boosting productivity.
Sign up for a free trial today and see firsthand how you can achieve these results Additionally, you can also schedule a demo to understand more about the tool.
Originally published @https://github.com/Kdheeru12/nightowlideas/blob/master/django/SQL_Query_Monitoring_Django.md

Xurrent named a Market Leader in Research In Action’s Vendor Selection Matrix™ for IT & Enterprise Service Management Solutions
Xurrent earns #1 rankings in customer satisfaction, price vs value, and recommendation index in Research In Action's global ITSM/ESM Vendor Selection Matrix report.


