Logging

A quick logging primer

Django uses Python’s builtin logging module to perform system logging.The usage of this module is discussed in detail in Python’s own documentation.However, if you’ve never used Python’s logging framework (or even if you have),here’s a quick primer.

The cast of players

A Python logging configuration consists of four parts:

Loggers

A logger is the entry point into the logging system. Each logger isa named bucket to which messages can be written for processing.

A logger is configured to have a log level. This log level describesthe severity of the messages that the logger will handle. Pythondefines the following log levels:

  • DEBUG: Low level system information for debugging purposes
  • INFO: General system information
  • WARNING: Information describing a minor problem that hasoccurred.
  • ERROR: Information describing a major problem that hasoccurred.
  • CRITICAL: Information describing a critical problem that hasoccurred.Each message that is written to the logger is a Log Record. Each logrecord also has a log level indicating the severity of that specificmessage. A log record can also contain useful metadata that describesthe event that is being logged. This can include details such as astack trace or an error code.

When a message is given to the logger, the log level of the message iscompared to the log level of the logger. If the log level of themessage meets or exceeds the log level of the logger itself, themessage will undergo further processing. If it doesn’t, the messagewill be ignored.

Once a logger has determined that a message needs to be processed,it is passed to a Handler.

Handlers

The handler is the engine that determines what happens to each messagein a logger. It describes a particular logging behavior, such aswriting a message to the screen, to a file, or to a network socket.

Like loggers, handlers also have a log level. If the log level of alog record doesn’t meet or exceed the level of the handler, thehandler will ignore the message.

A logger can have multiple handlers, and each handler can have adifferent log level. In this way, it is possible to provide differentforms of notification depending on the importance of a message. Forexample, you could install one handler that forwards ERROR andCRITICAL messages to a paging service, while a second handlerlogs all messages (including ERROR and CRITICAL messages) to afile for later analysis.

Filters

A filter is used to provide additional control over which log recordsare passed from logger to handler.

By default, any log message that meets log level requirements will behandled. However, by installing a filter, you can place additionalcriteria on the logging process. For example, you could install afilter that only allows ERROR messages from a particular source tobe emitted.

Filters can also be used to modify the logging record prior to beingemitted. For example, you could write a filter that downgradesERROR log records to WARNING records if a particular set ofcriteria are met.

Filters can be installed on loggers or on handlers; multiple filterscan be used in a chain to perform multiple filtering actions.

Formatters

Ultimately, a log record needs to be rendered as text. Formattersdescribe the exact format of that text. A formatter usually consistsof a Python formatting string containingLogRecord attributes; however,you can also write custom formatters to implement specific formatting behavior.

Using logging

Once you have configured your loggers, handlers, filters andformatters, you need to place logging calls into your code. Using thelogging framework works like this:

  1. # import the logging library
  2. import logging
  3.  
  4. # Get an instance of a logger
  5. logger = logging.getLogger(__name__)
  6.  
  7. def my_view(request, arg1, arg):
  8. ...
  9. if bad_mojo:
  10. # Log an error message
  11. logger.error('Something went wrong!')

And that’s it! Every time the bad_mojo condition is activated, anerror log record will be written.

Naming loggers

The call to logging.getLogger() obtains (creating, ifnecessary) an instance of a logger. The logger instance is identifiedby a name. This name is used to identify the logger for configurationpurposes.

By convention, the logger name is usually name, the name ofthe Python module that contains the logger. This allows you to filterand handle logging calls on a per-module basis. However, if you havesome other way of organizing your logging messages, you can provideany dot-separated name to identify your logger:

  1. # Get an instance of a specific named logger
  2. logger = logging.getLogger('project.interesting.stuff')

The dotted paths of logger names define a hierarchy. Theproject.interesting logger is considered to be a parent of theproject.interesting.stuff logger; the project loggeris a parent of the project.interesting logger.

Why is the hierarchy important? Well, because loggers can be set topropagate their logging calls to their parents. In this way, you candefine a single set of handlers at the root of a logger tree, andcapture all logging calls in the subtree of loggers. A logging handlerdefined in the project namespace will catch all logging messagesissued on the project.interesting andproject.interesting.stuff loggers.

This propagation can be controlled on a per-logger basis. Ifyou don’t want a particular logger to propagate to its parents, youcan turn off this behavior.

Making logging calls

The logger instance contains an entry method for each of the defaultlog levels:

  • logger.debug()
  • logger.info()
  • logger.warning()
  • logger.error()
  • logger.critical()There are two other logging calls available:

  • logger.log(): Manually emits a logging message with aspecific log level.

  • logger.exception(): Creates an ERROR level loggingmessage wrapping the current exception stack frame.

Configuring logging

Of course, it isn’t enough to just put logging calls into your code.You also need to configure the loggers, handlers, filters andformatters to ensure that logging output is output in a useful way.

Python’s logging library provides several techniques to configurelogging, ranging from a programmatic interface to configuration files.By default, Django uses the dictConfig format.

In order to configure logging, you use LOGGING to define adictionary of logging settings. These settings describes the loggers,handlers, filters and formatters that you want in your logging setup,and the log levels and other properties that you want those componentsto have.

By default, the LOGGING setting is merged with Django’sdefault logging configuration using thefollowing scheme.

If the disable_existing_loggers key in the LOGGING dictConfig isset to True (which is the dictConfig default if the key is missing)then all loggers from the default configuration will be disabled. Disabledloggers are not the same as removed; the logger will still exist, but willsilently discard anything logged to it, not even propagating entries to aparent logger. Thus you should be very careful using'disable_existing_loggers': True; it’s probably not what you want. Instead,you can set disable_existing_loggers to False and redefine some or allof the default loggers; or you can set LOGGING_CONFIG to Noneand handle logging config yourself.

Logging is configured as part of the general Django setup() function.Therefore, you can be certain that loggers are always ready for use in yourproject code.

Examples

The full documentation for dictConfig formatis the best source of information about logging configuration dictionaries.However, to give you a taste of what is possible, here are several examples.

First, here’s a configuration which writes all logging from thedjango logger to a local file:

  1. .. code-block:: python
  2. :caption: settings.py
  3.  
  4. LOGGING = {
  5. 'version': 1,
  6. 'disable_existing_loggers': False,
  7. 'handlers': {
  8. 'file': {
  9. 'level': 'DEBUG',
  10. 'class': 'logging.FileHandler',
  11. 'filename': '/path/to/django/debug.log',
  12. },
  13. },
  14. 'loggers': {
  15. 'django': {
  16. 'handlers': ['file'],
  17. 'level': 'DEBUG',
  18. 'propagate': True,
  19. },
  20. },
  21. }

If you use this example, be sure to change the 'filename' path to alocation that’s writable by the user that’s running the Django application.

Second, here’s an example of how to make the logging system print Django’slogging to the console. It may be useful during local development.

By default, this config only sends messages of level INFO or higher to theconsole (same as Django’s default logging config, except that the default onlydisplays log records when DEBUG=True). Django does not log many suchmessages. With this config, however, you can also set the environment variableDJANGO_LOG_LEVEL=DEBUG to see all of Django’s debug logging which is veryverbose as it includes all database queries:

  1. .. code-block:: python
  2. :caption: settings.py
  3.  
  4. import os
  5.  
  6. LOGGING = {
  7. 'version': 1,
  8. 'disable_existing_loggers': False,
  9. 'handlers': {
  10. 'console': {
  11. 'class': 'logging.StreamHandler',
  12. },
  13. },
  14. 'loggers': {
  15. 'django': {
  16. 'handlers': ['console'],
  17. 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
  18. },
  19. },
  20. }

Finally, here’s an example of a fairly complex logging setup:

  1. .. code-block:: python
  2. :caption: settings.py
  3.  
  4. LOGGING = {
  5. 'version': 1,
  6. 'disable_existing_loggers': False,
  7. 'formatters': {
  8. 'verbose': {
  9. 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
  10. 'style': '{',
  11. },
  12. 'simple': {
  13. 'format': '{levelname} {message}',
  14. 'style': '{',
  15. },
  16. },
  17. 'filters': {
  18. 'special': {
  19. '()': 'project.logging.SpecialFilter',
  20. 'foo': 'bar',
  21. },
  22. 'require_debug_true': {
  23. '()': 'django.utils.log.RequireDebugTrue',
  24. },
  25. },
  26. 'handlers': {
  27. 'console': {
  28. 'level': 'INFO',
  29. 'filters': ['require_debug_true'],
  30. 'class': 'logging.StreamHandler',
  31. 'formatter': 'simple'
  32. },
  33. 'mail_admins': {
  34. 'level': 'ERROR',
  35. 'class': 'django.utils.log.AdminEmailHandler',
  36. 'filters': ['special']
  37. }
  38. },
  39. 'loggers': {
  40. 'django': {
  41. 'handlers': ['console'],
  42. 'propagate': True,
  43. },
  44. 'django.request': {
  45. 'handlers': ['mail_admins'],
  46. 'level': 'ERROR',
  47. 'propagate': False,
  48. },
  49. 'myproject.custom': {
  50. 'handlers': ['console', 'mail_admins'],
  51. 'level': 'INFO',
  52. 'filters': ['special']
  53. }
  54. }
  55. }

This logging configuration does the following things:

  • Identifies the configuration as being in ‘dictConfig version 1’format. At present, this is the only dictConfig format version.

  • Defines two formatters:

    • simple, that outputs the log level name (e.g., DEBUG) and the logmessage.

The format string is a normal Python formatting stringdescribing the details that are to be output on each loggingline. The full list of detail that can be output can befound in Formatter Objects.

  • verbose, that outputs the log level name, the logmessage, plus the time, process, thread and module thatgenerate the log message.
  • Defines two filters:

    • project.logging.SpecialFilter, using the alias special. If thisfilter required additional arguments, they can be provided as additionalkeys in the filter configuration dictionary. In this case, the argumentfoo will be given a value of bar when instantiatingSpecialFilter.
    • django.utils.log.RequireDebugTrue, which passes on records whenDEBUG is True.
  • Defines two handlers:

    • console, a StreamHandler, which prints any INFO(or higher) message to sys.stderr. This handler uses the simpleoutput format.
    • mail_admins, an AdminEmailHandler, which emails any ERROR(or higher) message to the site ADMINS. This handler uses thespecial filter.
  • Configures three loggers:

    • django, which passes all messages to the console handler.
    • django.request, which passes all ERROR messages tothe mailadmins handler. In addition, this logger ismarked to _not propagate messages. This means that logmessages written to django.request will not be handledby the django logger.
    • myproject.custom, which passes all messages at INFOor higher that also pass the special filter to twohandlers – the console, and mail_admins. Thismeans that all INFO level messages (or higher) will beprinted to the console; ERROR and CRITICALmessages will also be output via email.

Custom logging configuration

If you don’t want to use Python’s dictConfig format to configure yourlogger, you can specify your own configuration scheme.

The LOGGING_CONFIG setting defines the callable that willbe used to configure Django’s loggers. By default, it points atPython’s logging.config.dictConfig() function. However, if you want touse a different configuration process, you can use any other callablethat takes a single argument. The contents of LOGGING willbe provided as the value of that argument when logging is configured.

Disabling logging configuration

If you don’t want to configure logging at all (or you want to manuallyconfigure logging using your own approach), you can setLOGGING_CONFIG to None. This will disable theconfiguration process for Django’s default logging. Here’s an example that disables Django’slogging configuration and then manually configures logging:

settings.py

  1. LOGGING_CONFIG = None
  2.  
  3. import logging.config
  4. logging.config.dictConfig(...)

Setting LOGGING_CONFIG to None only means that the automaticconfiguration process is disabled, not logging itself. If you disable theconfiguration process, Django will still make logging calls, falling back towhatever default logging behavior is defined.

Django’s logging extensions

Django provides a number of utilities to handle the uniquerequirements of logging in Web server environment.

Loggers

Django provides several built-in loggers.

django

The catch-all logger for messages in the django hierarchy. No messages areposted using this name but instead using one of the loggers below.

django.request

Log messages related to the handling of requests. 5XX responses areraised as ERROR messages; 4XX responses are raised as WARNINGmessages. Requests that are logged to the django.security logger aren’tlogged to django.request.

Messages to this logger have the following extra context:

  • status_code: The HTTP response code associated with therequest.
  • request: The request object that generated the loggingmessage.

django.server

Log messages related to the handling of requests received by the server invokedby the runserver command. HTTP 5XX responses are logged as ERRORmessages, 4XX responses are logged as WARNING messages, and everything elseis logged as INFO.

Messages to this logger have the following extra context:

  • status_code: The HTTP response code associated with the request.
  • request: The request object that generated the logging message.

django.template

Log messages related to the rendering of templates.

  • Missing context variables are logged as DEBUG messages.

django.db.backends

Messages relating to the interaction of code with the database. For example,every application-level SQL statement executed by a request is logged at theDEBUG level to this logger.

Messages to this logger have the following extra context:

  • duration: The time taken to execute the SQL statement.
  • sql: The SQL statement that was executed.
  • params: The parameters that were used in the SQL call.For performance reasons, SQL logging is only enabled whensettings.DEBUG is set to True, regardless of the logginglevel or handlers that are installed.

This logging does not include framework-level initialization (e.g.SET TIMEZONE) or transaction management queries (e.g. BEGIN,COMMIT, and ROLLBACK). Turn on query logging in your database if youwish to view all database queries.

django.security.*

The security loggers will receive messages on any occurrence ofSuspiciousOperation and other security-relatederrors. There is a sub-logger for each subtype of security error, including allSuspiciousOperations. The level of the log event depends on where theexception is handled. Most occurrences are logged as a warning, whileany SuspiciousOperation that reaches the WSGI handler will be logged as anerror. For example, when an HTTP Host header is included in a request froma client that does not match ALLOWED_HOSTS, Django will return a 400response, and an error message will be logged to thedjango.security.DisallowedHost logger.

These log events will reach the django logger by default, which mails errorevents to admins when DEBUG=False. Requests resulting in a 400 response dueto a SuspiciousOperation will not be logged to the django.requestlogger, but only to the django.security logger.

To silence a particular type of SuspiciousOperation, you can override thatspecific logger following this example:

  1. 'handlers': {
  2. 'null': {
  3. 'class': 'logging.NullHandler',
  4. },
  5. },
  6. 'loggers': {
  7. 'django.security.DisallowedHost': {
  8. 'handlers': ['null'],
  9. 'propagate': False,
  10. },
  11. },

Other django.security loggers not based on SuspiciousOperation are:

django.db.backends.schema

Logs the SQL queries that are executed during schema changes to the database bythe migrations framework. Note that it won’t log thequeries executed by RunPython.Messages to this logger have params and sql in their extra context (butunlike django.db.backends, not duration). The values have the same meaningas explained in django.db.backends.

Handlers

Django provides one log handler in addition to those provided by thePython logging module.

  • class AdminEmailHandler(include_html=False, email_backend=None, reporter_class=None)
  • This handler sends an email to the site ADMINS for each logmessage it receives.

If the log record contains a request attribute, the full detailsof the request will be included in the email. The email subject willinclude the phrase “internal IP” if the client’s IP address is in theINTERNAL_IPS setting; if not, it will include “EXTERNAL IP”.

If the log record contains stack trace information, that stacktrace will be included in the email.

The include_html argument of AdminEmailHandler is used tocontrol whether the traceback email includes an HTML attachmentcontaining the full content of the debug Web page that would have beenproduced if DEBUG were True. To set this value in yourconfiguration, include it in the handler definition fordjango.utils.log.AdminEmailHandler, like this:

  1. 'handlers': {
  2. 'mail_admins': {
  3. 'level': 'ERROR',
  4. 'class': 'django.utils.log.AdminEmailHandler',
  5. 'include_html': True,
  6. }
  7. },

Note that this HTML version of the email contains a full traceback,with names and values of local variables at each level of the stack, plusthe values of your Django settings. This information is potentially verysensitive, and you may not want to send it over email. Consider usingsomething such as Sentry to get the best of both worlds – therich information of full tracebacks plus the security of not sending theinformation over email. You may also explicitly designate certainsensitive information to be filtered out of error reports – learn more onFiltering error reports.

By setting the email_backend argument of AdminEmailHandler, theemail backend that is being used by thehandler can be overridden, like this:

  1. 'handlers': {
  2. 'mail_admins': {
  3. 'level': 'ERROR',
  4. 'class': 'django.utils.log.AdminEmailHandler',
  5. 'email_backend': 'django.core.mail.backends.filebased.EmailBackend',
  6. }
  7. },

By default, an instance of the email backend specified inEMAIL_BACKEND will be used.

The reporter_class argument of AdminEmailHandler allows providingan django.views.debug.ExceptionReporter subclass to customize thetraceback text sent in the email body. You provide a string import path tothe class you wish to use, like this:

  1. 'handlers': {
  2. 'mail_admins': {
  3. 'level': 'ERROR',
  4. 'class': 'django.utils.log.AdminEmailHandler',
  5. 'include_html': True,
  6. 'reporter_class': 'somepackage.error_reporter.CustomErrorReporter'
  7. }
  8. },

New in Django 3.0:The reporter_class argument was added.

  • sendmail(_subject, message, *args, **kwargs)
  • Sends emails to admin users. To customize this behavior, you cansubclass the AdminEmailHandler class andoverride this method.

Filters

Django provides some log filters in addition to those provided by the Pythonlogging module.

  • class CallbackFilter(callback)
  • This filter accepts a callback function (which should accept a singleargument, the record to be logged), and calls it for each record thatpasses through the filter. Handling of that record will not proceed if thecallback returns False.

For instance, to filter out UnreadablePostError(raised when a user cancels an upload) from the admin emails, you wouldcreate a filter function:

  1. from django.http import UnreadablePostError
  2.  
  3. def skip_unreadable_post(record):
  4. if record.exc_info:
  5. exc_type, exc_value = record.exc_info[:2]
  6. if isinstance(exc_value, UnreadablePostError):
  7. return False
  8. return True

and then add it to your logging config:

  1. 'filters': {
  2. 'skip_unreadable_posts': {
  3. '()': 'django.utils.log.CallbackFilter',
  4. 'callback': skip_unreadable_post,
  5. }
  6. },
  7. 'handlers': {
  8. 'mail_admins': {
  9. 'level': 'ERROR',
  10. 'filters': ['skip_unreadable_posts'],
  11. 'class': 'django.utils.log.AdminEmailHandler'
  12. }
  13. },
  • class RequireDebugFalse
  • This filter will only pass on records when settings.DEBUG is False.

This filter is used as follows in the default LOGGINGconfiguration to ensure that the AdminEmailHandler only sendserror emails to admins when DEBUG is False:

  1. 'filters': {
  2. 'require_debug_false': {
  3. '()': 'django.utils.log.RequireDebugFalse',
  4. }
  5. },
  6. 'handlers': {
  7. 'mail_admins': {
  8. 'level': 'ERROR',
  9. 'filters': ['require_debug_false'],
  10. 'class': 'django.utils.log.AdminEmailHandler'
  11. }
  12. },
  • class RequireDebugTrue
  • This filter is similar to RequireDebugFalse, except that records arepassed only when DEBUG is True.

Django’s default logging configuration

By default, Django configures the following logging:

When DEBUG is True:

  • The django logger sends messages in the django hierarchy (exceptdjango.server) at the INFO level or higher to the console.When DEBUG is False:

  • The django logger sends messages in the django hierarchy (exceptdjango.server) with ERROR or CRITICAL level toAdminEmailHandler.Independent of the value of DEBUG:

  • The django.server logger sends messages at the INFO levelor higher to the console.All loggers except django.server propagate logging to theirparents, up to the root django logger. The console and mail_adminshandlers are attached to the root logger to provide the behavior describedabove.

See also Configuring logging to learn how you cancomplement or replace this default logging configuration defined indjango/utils/log.py.