Odoo's Logging System
Odoo uses Python's standard logging module with custom configuration. Understanding how to configure and use logging is essential for debugging during development and diagnosing issues in production.
Basic Logging in Modules
Every Odoo module should use a module-level logger:
import logging
_logger = logging.getLogger(__name__)
class MyModel(models.Model):
_name = 'my.model'
def process_records(self):
_logger.info('Processing %d records', len(self))
for record in self:
try:
record._do_work()
_logger.debug('Processed record %s', record.name)
except Exception as e:
_logger.error(
'Failed to process record %s: %s',
record.name, e, exc_info=True)
The convention is to name the logger _logger (with underscore prefix) and initialize it with __name__, which resolves to the module path (e.g., odoo.addons.my_module.models.my_model).
Log Levels
Python logging levels from least to most severe:
| Level | When to Use | Example |
|---|---|---|
| DEBUG | Detailed diagnostic info | Variable values, loop iterations |
| INFO | Normal operations | Record processed, cron started |
| WARNING | Unexpected but handled | Deprecated feature used, fallback applied |
| ERROR | Operation failed | API call failed, invalid data |
| CRITICAL | System-level failure | Database unavailable, disk full |
Configuring Log Levels
Command Line
# Set global log level
odoo --log-level=debug
# Set per-handler log levels
odoo --log-handler=odoo.addons.my_module:DEBUG
odoo --log-handler=odoo.models:DEBUG
odoo --log-handler=werkzeug:WARNING
# Enable SQL logging
odoo --log-level=debug_sql
# Multiple handlers
odoo --log-handler=odoo.addons.my_module:DEBUG \
--log-handler=odoo.http:INFO \
--log-handler=werkzeug:WARNING
Configuration File (odoo.conf)
[options]
log_level = info
log_handler = :INFO,odoo.addons.my_module:DEBUG,werkzeug:WARNING
log_db = False
log_db_level = warning
logfile = /var/log/odoo/odoo-server.log
logrotate = True
Structured Logging Patterns
# Good: use format strings with %s (lazy evaluation)
_logger.info('Order %s confirmed for %s', order.name, partner.name)
# Bad: f-strings evaluate even if log level is disabled
_logger.debug(f'Order {order.name} confirmed') # Always evaluates
# Good: include relevant context
_logger.error(
'Payment failed for order %s (partner=%s, amount=%s): %s',
order.name, order.partner_id.id, order.amount_total, error)
# Good: include traceback for errors
try:
self._process()
except Exception:
_logger.exception('Processing failed for %s', self.name)
# .exception() auto-includes traceback
SQL Debugging
Enable SQL logging to see every database query:
# Show all SQL queries
odoo --log-level=debug_sql
# Or specific handler
odoo --log-handler=odoo.sql_db:DEBUG
This outputs every SQL query with execution time. Essential for finding N+1 query problems and optimizing performance.
Using pdb for Interactive Debugging
The Python debugger lets you pause execution and inspect state:
def action_process(self):
import pdb; pdb.set_trace() # execution stops here
# Or in Python 3.7+:
breakpoint()
for record in self:
record._do_work()
When execution hits the breakpoint, the terminal where Odoo is running becomes an interactive Python shell. Commands:
n— next lines— step into functionc— continue executionp variable— print variable valuel— show current code contextpp self.read()— pretty-print record data
Note: pdb only works when Odoo runs with --workers=0 (single-process mode). Multi-worker mode does not support interactive debugging.
Debugging ORM Queries
# Log the SQL generated by a search
self.env.cr.execute('EXPLAIN ANALYZE ' + query)
# Check what SQL the ORM generates
import logging
logging.getLogger('odoo.sql_db').setLevel(logging.DEBUG)
result = self.env['res.partner'].search(
[('is_company', '=', True)])
logging.getLogger('odoo.sql_db').setLevel(logging.WARNING)
Request Tracing
Log HTTP request details for API debugging:
from odoo.http import request
_logger.info(
'API request: %s %s from %s (uid=%s)',
request.httprequest.method,
request.httprequest.path,
request.httprequest.remote_addr,
request.uid)
Production Log Analysis
Common patterns for analyzing production logs:
# Find errors in the last hour
grep 'ERROR' /var/log/odoo/odoo-server.log | tail -50
# Find slow queries (over 100ms)
grep 'query.*[0-9]\{3,\}ms' /var/log/odoo/odoo-server.log
# Track a specific request
grep 'request_id_abc123' /var/log/odoo/odoo-server.log
# Monitor memory usage
grep 'VmRSS' /var/log/odoo/odoo-server.log
Log to Database
Odoo can log to a database table for analysis:
[options]
log_db = True
log_db_level = warning
Logs go to the ir_logging table, viewable in Settings > Technical > Database > Logging.
Custom Exception Handling
from odoo.exceptions import UserError, ValidationError
def action_validate(self):
try:
self._check_constraints()
except ValidationError:
_logger.warning(
'Validation failed for %s', self.name,
exc_info=True)
raise # re-raise to show user the error
except Exception:
_logger.exception(
'Unexpected error validating %s', self.name)
raise UserError(
'An unexpected error occurred. '
'Please contact support.')
Best Practices
- Always use
_loggerwith__name__, never print() - Use
%sformatting (not f-strings) for lazy evaluation - Include record identifiers (name, id) in log messages for traceability
- Use
_logger.exception()in except blocks to auto-include tracebacks - Set production logs to INFO level, development to DEBUG
- Rotate logs with
logrotate=Trueor system logrotate