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_loggingenable_sql_logging() # call in development to log all queriesWith 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": 89047 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 partiescases = models.Case.query.all()for case in cases: parties = case.parties # triggers a new query per case
# GOOD — eager load parties in one querycases = 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()returnedNonethat 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.KeyErrorin a Jinja template — a variable you expected to exist in the template context was not passed. Check therender_template()call.
Check: The view function is returning something, but not what you expect.
- Is the route matched? Check that the URL matches the
@blueprint.route()pattern exactly. - Is the template rendering? Add a simple
{{ 'debug' }}to the template to confirm it is being rendered. - Is the data missing? Add a
print(variable)in the view function to confirm values before passing to the template.
Check: Inspect the response in the browser’s Network tab.
401— not logged in, or session expired403— logged in but auth check failed (wrong user type)404— the resource was not found (check IDs)500— server error (check terminal logs)
For JSON API endpoints, inspect the response body — error messages are usually included:
{"error": "Case not found", "code": 404}Check: Open browser DevTools → Console for JavaScript errors.
Uncaught ReferenceError: $ is not defined— jQuery not loaded on this page404on a bundle URL — runnpm run build-dev- Function not called — check that the DOM event listener is attached after DOMContentLoaded
Also check: have you run npm run build-dev since making JS changes? The dev server does not auto-rebuild.
Expected in local mode. In _LOCAL_MODE, SendGrid sends are silenced. Email content is logged to the console instead. Search for "SendGrid" or "email" in your terminal output to see what would have been sent.
If you need to test real email delivery locally, temporarily set SENDGRID_API_KEY in localflaskapp.cfg and set the recipient to your own email address.
g object inspection
If you are not sure what g contains during a request, add a temporary debug line:
from flask import gimport jsonprint(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_enabledif is_enabled('my_feature_flag', agency=g.agency): # feature is on for this agencyIn local dev, you can temporarily hardcode flag values to test feature-flagged code paths.