Skip to content

Debugging Locally

Request tracing with request_id

Every request gets a unique UUID in g.request_id. This ID is included in all log lines for that request and returned in the X-Request-ID response header.

In local dev: Check your terminal for log output. Find the request_id in the log line for the failing request, then search for that ID in all subsequent log lines to see everything that happened.

In production: Use CloudWatch. Filter log events by the request_id value from the failing request’s X-Request-ID response header.

SQL query inspection

sqldebug.py — log every SQL query

orchid/utils/sqldebug.py provides utilities to log the raw SQL that SQLAlchemy generates:

from orchid.utils.sqldebug import enable_sql_logging
enable_sql_logging() # call in development to log all queries

With SQL logging enabled, every database query prints to the console with its parameters. Useful for understanding what queries a complex view is running.

debug_queries.html — browser-based query viewer

In local mode, navigate to a page and then visit /debug-queries (if enabled) to see a formatted list of SQL queries that ran for the last request — including query text, execution time, and call stack.

g.query_count and g.query_total_ms

These are logged in after_request for every request. If a page is slow, check the log output for:

"query_count": 47, "query_total_ms": 890

47 queries is a sign of an N+1 query problem. Look for loops that call model.relationship without eager loading.

Fixing N+1 queries

N+1 is the most common performance issue in SQLAlchemy apps. Example:

# BAD — runs 1 query for cases, then N queries for parties
cases = models.Case.query.all()
for case in cases:
parties = case.parties # triggers a new query per case
# GOOD — eager load parties in one query
cases = models.Case.query.options(
db.joinedload(models.Case.parties)
).all()

Python debugger (pdb / VS Code)

# Drop a breakpoint anywhere:
import pdb; pdb.set_trace()
# Or use the modern built-in (Python 3.7+):
breakpoint()

Flask’s dev server (python flaskapp.py) runs with DEBUG=True, which enables interactive debugging in the terminal.

In VS Code: add a launch configuration for Flask:

{
"name": "Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "flaskapp.py",
"FLASK_ENV": "development"
},
"args": ["run", "--no-debugger", "--no-reload"]
}

Common failure patterns and fixes

Check: Flask’s dev server prints the full traceback to the terminal. Look for the line that starts with Exception: or Error:.

Common causes:

  • AttributeError: 'NoneType' object has no attribute 'x' — a .first() returned None that you expected to be non-None. Use .first_or_404() for cases where the missing object should be a 404.
  • sqlalchemy.exc.OperationalError — database connection issue or invalid SQL.
  • KeyError in a Jinja template — a variable you expected to exist in the template context was not passed. Check the render_template() call.

g object inspection

If you are not sure what g contains during a request, add a temporary debug line:

from flask import g
import json
print(json.dumps({k: str(v) for k, v in g.__dict__.items()}, indent=2))

This prints all g attributes to the console, showing what before_request populated.

Checking feature flags

Feature flags are managed in orchid/utils/feature_flags.py. To check if a flag is enabled:

from orchid.utils.feature_flags import is_enabled
if is_enabled('my_feature_flag', agency=g.agency):
# feature is on for this agency

In local dev, you can temporarily hardcode flag values to test feature-flagged code paths.