Logging & error reports
Logging user actions
User actions within the system, such as viewing and modifying data, should leave an audit trace. These logs are stored in the database.
Django action logs
Django provides LogEntry model for storing user action logs.
ActionAuditMixin is used to automate logging in django admin.
For class based views, use BaseActionAuditMixin.
To manually log user action, use log_action()
(or log_bulk_action() for bulk operations).
Tracker log entry
TrackerLogEntry is an extension to django’s LogEntry model
that stores additional details about the change made by the user, such as previous and new value, and name of the field
where the change was made.
This is commonly used to track observation changes in review page.
Use track_change() to log the change into tracker.
Reversion
Reversion stores object’s changes over time in Revision and Version models.
import reversion
with reversion.create_revision():
reversion.set_user(request.user)
reversion.set_comment("Created revision 1")
# Save a new model instance.
obj = YourModel()
obj.name = "obj v1"
obj.save()
with reversion.create_revision():
reversion.set_user(request.user)
reversion.set_comment("Created revision 2")
# Update the model instance.
obj.name = "obj v2"
obj.save()
CKEditor Revision history
CKEditor revision history plugin integration is implemented in Task #29912. It tracks changes to fields using CKEditor 5 where version control plugin is enabled (document draft).
See Revision History for more details.
See also
Analytics
Built-in analytics
Anonymised information about objects viewed by the user is stored in AnalyticsEntry model
that can be accessed by clients and displayed in dashboard widgets.
Use AnalyticsViewLogMixin to log view to the analytics model automatically.
Google Analytics
Google analytics are automatically sent by the front-end based on the GOOGLE_ANALYTICS_4_PROPERTY_ID configuration.
See also
Logging code
These utilities help monitor the system in deployment or when debugging locally.
Python logging
Python has built-in logging facility.
Logs are writen to a log file, and standard output, depending how project is configured.
Configure LOGFILE_LOG_LEVEL and CONSOLE_LOG_LEVEL to defined which messages get logged where.
Errors can be written to a separate error file, or e-mailed if LOG_ERRORS or EMAIL_ERRORS is set.
Utilities are provided by this project to simplify adding logs.
Use logger.get_logger() to get logger instance, or logger.log_debug() shortcut to log a debug message.
from logger import get_logger, log_debug
get_logger().debug("debug message")
log_debug("Same as above")
get_logger().info("example information")
get_logger().warning("a warning message")
try:
...
except Exception as e:
get_logger().error("An error has occurred", exc_info=e)
Important
Do not use print() in production code, always use logger
(or stdout/stderr in django commands)
Tracing
Tracing sends samples of code and their execution time to Azure Insights.
Tracing utilities include logger.trace_span() (context manager) and logger.trace_decorator() (function / method decorator).
Use them to monitor and troubleshoot performance of python functions.
See also
More about monitoring in Telemetry
Crash reports
Crashes from production are automatically sent to Sentry. Staging Sites send crash reports to GitLab Error Tracking
If you need to log an error manually, use sentry_sdk.capture_exception()
try:
...
except Exception:
import sentry_sdk
sentry_sdk.capture_exception()
If you can ignore the exception but still want to report it to Sentry, use the catch_exception() context manager.
with catch_exception(TypeError, KeyError):
...
Configuration errors
Invalid configuration of django models such as dashboard widgets, report rules, etc can cause crashes that will not be reported to Sentry. These are most often stored in a json field where database constraints and validation are limited.
Instead, when underlying cause of the crash is determined to be misconfiguration, the error will be reported to the e-mail address set in CONFIG_ERROR_REPORT_EMAIL.
If the address is not set, the errors will not be reported.
As these errors can occur at high frequency under heavy traffic, the error reporting is limited by CONFIG_ERROR_REPORT_RATE_LIMIT_HOURS per each error.
See also
Task #29232 is where the functionality was originally implemented. It documents rationale and decisions behind this feature.
ConfigurationError is the base error class for configuration errors.
This exception and it’s subclasses are handled by handle_config_error().
The exception must at minimum have a clear error message, and reference to the model instance holding the erroneous configuration. It does not need to be translatable.
The url and config_field parameters are good to have to add context to the error report e-mail.
try:
ward_id: int = obj.config['ward_id']
Ward.objects.get(pk=ward_id)
except KeyError as e:
raise ConfigurationError("'ward_id' configuration is missing", obj, url=request.path, config_field='ward_id') from e
except Ward.DoesNotExist as e:
raise ConfigurationError(f"Ward {ward_id} does not exist", obj, url=request.path, config_field='ward_id') from e
try:
...
except ConfigurationError as e:
handle_config_error(e)