Ticket #12012: t12012-rc1.diff

File t12012-rc1.diff, 62.0 KB (added by Russell Keith-Magee, 14 years ago)

RC1 of logging patch

  • django/conf/__init__.py

    diff -r d861e2426ce4 django/conf/__init__.py
    a b  
    1616
    1717ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
    1818
     19
    1920class LazySettings(LazyObject):
    2021    """
    2122    A lazy proxy for either global Django settings or a custom settings object.
     
    114115            os.environ['TZ'] = self.TIME_ZONE
    115116            time.tzset()
    116117
     118        # Settings are configured, so we can set up the logger if required
     119        if self.LOGGING_CONFIG:
     120            # First find the logging configuration function ...
     121            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
     122            logging_config_module = importlib.import_module(logging_config_path)
     123            logging_config_func = getattr(logging_config_module, logging_config_func_name)
     124
     125            # ... then invoke it with the logging settings
     126            logging_config_func(self.LOGGING)
     127
    117128class UserSettingsHolder(object):
    118129    """
    119130    Holder for user configured settings.
  • django/conf/global_settings.py

    diff -r d861e2426ce4 django/conf/global_settings.py
    a b  
    499499# django.contrib.messages to avoid imports in this settings file.
    500500
    501501###########
     502# LOGGING #
     503###########
     504
     505# The callable to use to configure logging
     506LOGGING_CONFIG = 'django.utils.log.dictConfig'
     507
     508# The default logging configuration
     509LOGGING = {
     510    'version': 1,
     511    'disable_existing_loggers': False,
     512    'handlers': {
     513        'null': {
     514            'level':'DEBUG',
     515            'class':'django.utils.log.NullHandler',
     516        },
     517        'console':{
     518            'level':'DEBUG',
     519            'class':'logging.StreamHandler',
     520        },
     521        'mail_admins': {
     522            'level': 'ERROR',
     523            'class': 'django.utils.log.AdminEmailHandler'
     524        }
     525    },
     526    'loggers': {
     527        'django': {
     528            'handlers':['null'],
     529            'propagate': True,
     530            'level':'INFO',
     531        },
     532        'django.db.backends':{
     533            'handlers': [],
     534            'level': 'INFO',
     535            'propagate': True,
     536        },
     537        'django.request':{
     538            'handlers': ['mail_admins'],
     539            'level': 'ERROR',
     540            'propagate': True,
     541        },
     542    }
     543}
     544
     545###########
    502546# TESTING #
    503547###########
    504548
  • django/conf/project_template/settings.py

    diff -r d861e2426ce4 django/conf/project_template/settings.py
    a b  
    9494    # Uncomment the next line to enable admin documentation:
    9595    # 'django.contrib.admindocs',
    9696)
     97
     98LOGGING = {
     99    'version': 1,
     100    'disable_existing_loggers': False,
     101    'handlers': {
     102        'null': {
     103            'level':'DEBUG',
     104            'class':'django.utils.log.NullHandler',
     105        },
     106        'console':{
     107            'level':'DEBUG',
     108            'class':'logging.StreamHandler',
     109        },
     110        'mail_admins': {
     111            'level': 'ERROR',
     112            'class': 'django.utils.log.AdminEmailHandler'
     113        }
     114    },
     115    'loggers': {
     116        'django': {
     117            'handlers':['null'],
     118            'propagate': True,
     119            'level':'INFO',
     120        },
     121        'django.db.backends':{
     122            'handlers': [],
     123            'level': 'INFO',
     124            'propagate': True,
     125        },
     126        'django.request':{
     127            'handlers': ['mail_admins'],
     128            'level': 'ERROR',
     129            'propagate': True,
     130        },
     131    }
     132}
  • django/core/handlers/base.py

    diff -r d861e2426ce4 django/core/handlers/base.py
    a b  
     1import logging
    12import sys
    23
    34from django import http
     
    56from django.utils.encoding import force_unicode
    67from django.utils.importlib import import_module
    78
     9logger = logging.getLogger('django.request')
     10
     11
    812class BaseHandler(object):
    913    # Changes that are always applied to a response (in this order).
    1014    response_fixes = [
     
    118122
    119123                return response
    120124            except http.Http404, e:
     125                logger.warning('Not Found: %s' % request.path,
     126                            extra={
     127                                'status_code': 404,
     128                                'request': request
     129                            })
    121130                if settings.DEBUG:
    122131                    from django.views import debug
    123132                    return debug.technical_404_response(request, e)
     
    131140                        finally:
    132141                            receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
    133142            except exceptions.PermissionDenied:
     143                logger.warning('Forbidden (Permission denied): %s' % request.path,
     144                            extra={
     145                                'status_code': 403,
     146                                'request': request
     147                            })
    134148                return http.HttpResponseForbidden('<h1>Permission denied</h1>')
    135149            except SystemExit:
    136150                # Allow sys.exit() to actually exit. See tickets #1023 and #4701
     
    155169        available would be an error.
    156170        """
    157171        from django.conf import settings
    158         from django.core.mail import mail_admins
    159172
    160173        if settings.DEBUG_PROPAGATE_EXCEPTIONS:
    161174            raise
     
    164177            from django.views import debug
    165178            return debug.technical_500_response(request, *exc_info)
    166179
    167         # When DEBUG is False, send an error message to the admins.
    168         subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
    169         try:
    170             request_repr = repr(request)
    171         except:
    172             request_repr = "Request repr() unavailable"
    173         message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr)
    174         mail_admins(subject, message, fail_silently=True)
     180        logger.error('Internal Server Error: %s' % request.path,
     181            exc_info=exc_info,
     182            extra={
     183                'status_code': 500,
     184                'request':request
     185            }
     186        )
     187
    175188        # If Http500 handler is not installed, re-raise last exception
    176189        if resolver.urlconf_module is None:
    177190            raise exc_info[1], None, exc_info[2]
     
    179192        callback, param_dict = resolver.resolve500()
    180193        return callback(request, **param_dict)
    181194
    182     def _get_traceback(self, exc_info=None):
    183         "Helper function to return the traceback as a string"
    184         import traceback
    185         return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
    186 
    187195    def apply_response_fixes(self, request, response):
    188196        """
    189197        Applies each of the functions in self.response_fixes to the request and
  • django/core/handlers/modpython.py

    diff -r d861e2426ce4 django/core/handlers/modpython.py
    a b  
     1import logging
    12import os
    23from pprint import pformat
     4import sys
    35from warnings import warn
    46
    57from django import http
     
    911from django.utils import datastructures
    1012from django.utils.encoding import force_unicode, smart_str, iri_to_uri
    1113
     14logger = logging.getLogger('django.request')
     15
     16
    1217# NOTE: do *not* import settings (or any module which eventually imports
    1318# settings) until after ModPythonHandler has been called; otherwise os.environ
    1419# won't be set up correctly (with respect to settings).
     
    200205            try:
    201206                request = self.request_class(req)
    202207            except UnicodeDecodeError:
     208                logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
     209                    exc_info=sys.exc_info(),
     210                    extra={
     211                        'status_code': 400,
     212                        'request': request
     213                    }
     214                )
    203215                response = http.HttpResponseBadRequest()
    204216            else:
    205217                response = self.get_response(request)
  • django/core/handlers/wsgi.py

    diff -r d861e2426ce4 django/core/handlers/wsgi.py
    a b  
     1import logging
     2from pprint import pformat
     3import sys
    14from threading import Lock
    2 from pprint import pformat
    35try:
    46    from cStringIO import StringIO
    57except ImportError:
     
    1214from django.utils import datastructures
    1315from django.utils.encoding import force_unicode, iri_to_uri
    1416
     17logger = logging.getLogger('django.request')
     18
     19
    1520# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    1621STATUS_CODE_TEXT = {
    1722    100: 'CONTINUE',
     
    236241            try:
    237242                request = self.request_class(environ)
    238243            except UnicodeDecodeError:
     244                logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
     245                    exc_info=sys.exc_info(),
     246                    extra={
     247                        'status_code': 400,
     248                        'request': request
     249                    }
     250                )
    239251                response = http.HttpResponseBadRequest()
    240252            else:
    241253                response = self.get_response(request)
  • django/db/backends/util.py

    diff -r d861e2426ce4 django/db/backends/util.py
    a b  
    11import datetime
    22import decimal
     3import logging
    34from time import time
    45
    56from django.utils.hashcompat import md5_constructor
    67
     8logger = logging.getLogger('django.db.backends')
     9
    710class CursorDebugWrapper(object):
    811    def __init__(self, cursor, db):
    912        self.cursor = cursor
     
    1518            return self.cursor.execute(sql, params)
    1619        finally:
    1720            stop = time()
     21            duration = stop - start
    1822            sql = self.db.ops.last_executed_query(self.cursor, sql, params)
    1923            self.db.queries.append({
    2024                'sql': sql,
    21                 'time': "%.3f" % (stop - start),
     25                'time': "%.3f" % duration,
    2226            })
     27            logger.debug('(%.3f) %s; args=%s' % (duration, sql, params),
     28                extra={'duration':duration, 'sql':sql, 'params':params}
     29            )
    2330
    2431    def executemany(self, sql, param_list):
    2532        start = time()
     
    2734            return self.cursor.executemany(sql, param_list)
    2835        finally:
    2936            stop = time()
     37            duration = stop - start
    3038            self.db.queries.append({
    3139                'sql': '%s times: %s' % (len(param_list), sql),
    32                 'time': "%.3f" % (stop - start),
     40                'time': "%.3f" % duration,
    3341            })
     42            logger.debug('(%.3f) %s; args=%s' % (duration, sql, param_list),
     43                extra={'duration':duration, 'sql':sql, 'params':param_list}
     44            )
    3445
    3546    def __getattr__(self, attr):
    3647        if attr in self.__dict__:
  • django/middleware/common.py

    diff -r d861e2426ce4 django/middleware/common.py
    a b  
     1import logging
    12import re
    23
    34from django.conf import settings
     
    78from django.core import urlresolvers
    89from django.utils.hashcompat import md5_constructor
    910
     11logger = logging.getLogger('django.request')
     12
     13
    1014class CommonMiddleware(object):
    1115    """
    1216    "Common" middleware for taking care of some basic operations:
     
    3842        if 'HTTP_USER_AGENT' in request.META:
    3943            for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
    4044                if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
     45                    logger.warning('Forbidden (User agent): %s' % request.path,
     46                        extra={
     47                            'status_code': 403,
     48                            'request': request
     49                        }
     50                    )
    4151                    return http.HttpResponseForbidden('<h1>Forbidden</h1>')
    4252
    4353        # Check for a redirect based on settings.APPEND_SLASH
  • django/middleware/csrf.py

    diff -r d861e2426ce4 django/middleware/csrf.py
    a b  
    66"""
    77
    88import itertools
     9import logging
    910import re
    1011import random
    1112
     
    2021
    2122_HTML_TYPES = ('text/html', 'application/xhtml+xml')
    2223
     24logger = logging.getLogger('django.request')
     25
    2326# Use the system (hardware-based) random number generator if it exists.
    2427if hasattr(random, 'SystemRandom'):
    2528    randrange = random.SystemRandom().randrange
     
    169172                # we can use strict Referer checking.
    170173                referer = request.META.get('HTTP_REFERER')
    171174                if referer is None:
     175                    logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
     176                        extra={
     177                            'status_code': 403,
     178                            'request': request,
     179                        }
     180                    )
    172181                    return reject(REASON_NO_REFERER)
    173182
    174183                # The following check ensures that the referer is HTTPS,
    175184                # the domains match and the ports match - the same origin policy.
    176185                good_referer = 'https://%s/' % request.get_host()
    177186                if not referer.startswith(good_referer):
    178                     return reject(REASON_BAD_REFERER %
    179                                   (referer, good_referer))
     187                    reason = REASON_BAD_REFERER % (referer, good_referer)
     188                    logger.warning('Forbidden (%s): %s' % (reason, request.path),
     189                        extra={
     190                            'status_code': 403,
     191                            'request': request,
     192                        }
     193                    )
     194                    return reject(reason)
    180195
    181196            # If the user didn't already have a CSRF cookie, then fall back to
    182197            # the Django 1.1 method (hash of session ID), so a request is not
     
    190205                    # No CSRF cookie and no session cookie. For POST requests,
    191206                    # we insist on a CSRF cookie, and in this way we can avoid
    192207                    # all CSRF attacks, including login CSRF.
     208                    logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
     209                        extra={
     210                            'status_code': 403,
     211                            'request': request,
     212                        }
     213                    )
    193214                    return reject(REASON_NO_COOKIE)
    194215            else:
    195216                csrf_token = request.META["CSRF_COOKIE"]
     
    199220            if request_csrf_token != csrf_token:
    200221                if cookie_is_new:
    201222                    # probably a problem setting the CSRF cookie
     223                    logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path),
     224                        extra={
     225                            'status_code': 403,
     226                            'request': request,
     227                        }
     228                    )
    202229                    return reject(REASON_NO_CSRF_COOKIE)
    203230                else:
     231                    logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path),
     232                        extra={
     233                            'status_code': 403,
     234                            'request': request,
     235                        }
     236                    )
    204237                    return reject(REASON_BAD_TOKEN)
    205238
    206239        return accept()
  • new file django/utils/dictconfig.py

    diff -r d861e2426ce4 django/utils/dictconfig.py
    - +  
     1# This is a copy of the Python logging.config.dictconfig module,
     2# reproduced with permission. It is provided here for backwards
     3# compatibility for Python versions prior to 2.7.
     4#
     5# Copyright 2009-2010 by Vinay Sajip. All Rights Reserved.
     6#
     7# Permission to use, copy, modify, and distribute this software and its
     8# documentation for any purpose and without fee is hereby granted,
     9# provided that the above copyright notice appear in all copies and that
     10# both that copyright notice and this permission notice appear in
     11# supporting documentation, and that the name of Vinay Sajip
     12# not be used in advertising or publicity pertaining to distribution
     13# of the software without specific, written prior permission.
     14# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
     15# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
     16# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
     17# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     18# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     19# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     20
     21import logging.handlers
     22import re
     23import sys
     24import types
     25
     26IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
     27
     28def valid_ident(s):
     29    m = IDENTIFIER.match(s)
     30    if not m:
     31        raise ValueError('Not a valid Python identifier: %r' % s)
     32    return True
     33
     34#
     35# This function is defined in logging only in recent versions of Python
     36#
     37try:
     38    from logging import _checkLevel
     39except ImportError:
     40    def _checkLevel(level):
     41        if isinstance(level, int):
     42            rv = level
     43        elif str(level) == level:
     44            if level not in logging._levelNames:
     45                raise ValueError('Unknown level: %r' % level)
     46            rv = logging._levelNames[level]
     47        else:
     48            raise TypeError('Level not an integer or a '
     49                            'valid string: %r' % level)
     50        return rv
     51
     52# The ConvertingXXX classes are wrappers around standard Python containers,
     53# and they serve to convert any suitable values in the container. The
     54# conversion converts base dicts, lists and tuples to their wrapped
     55# equivalents, whereas strings which match a conversion format are converted
     56# appropriately.
     57#
     58# Each wrapper should have a configurator attribute holding the actual
     59# configurator to use for conversion.
     60
     61class ConvertingDict(dict):
     62    """A converting dictionary wrapper."""
     63
     64    def __getitem__(self, key):
     65        value = dict.__getitem__(self, key)
     66        result = self.configurator.convert(value)
     67        #If the converted value is different, save for next time
     68        if value is not result:
     69            self[key] = result
     70            if type(result) in (ConvertingDict, ConvertingList,
     71                                ConvertingTuple):
     72                result.parent = self
     73                result.key = key
     74        return result
     75
     76    def get(self, key, default=None):
     77        value = dict.get(self, key, default)
     78        result = self.configurator.convert(value)
     79        #If the converted value is different, save for next time
     80        if value is not result:
     81            self[key] = result
     82            if type(result) in (ConvertingDict, ConvertingList,
     83                                ConvertingTuple):
     84                result.parent = self
     85                result.key = key
     86        return result
     87
     88    def pop(self, key, default=None):
     89        value = dict.pop(self, key, default)
     90        result = self.configurator.convert(value)
     91        if value is not result:
     92            if type(result) in (ConvertingDict, ConvertingList,
     93                                ConvertingTuple):
     94                result.parent = self
     95                result.key = key
     96        return result
     97
     98class ConvertingList(list):
     99    """A converting list wrapper."""
     100    def __getitem__(self, key):
     101        value = list.__getitem__(self, key)
     102        result = self.configurator.convert(value)
     103        #If the converted value is different, save for next time
     104        if value is not result:
     105            self[key] = result
     106            if type(result) in (ConvertingDict, ConvertingList,
     107                                ConvertingTuple):
     108                result.parent = self
     109                result.key = key
     110        return result
     111
     112    def pop(self, idx=-1):
     113        value = list.pop(self, idx)
     114        result = self.configurator.convert(value)
     115        if value is not result:
     116            if type(result) in (ConvertingDict, ConvertingList,
     117                                ConvertingTuple):
     118                result.parent = self
     119        return result
     120
     121class ConvertingTuple(tuple):
     122    """A converting tuple wrapper."""
     123    def __getitem__(self, key):
     124        value = tuple.__getitem__(self, key)
     125        result = self.configurator.convert(value)
     126        if value is not result:
     127            if type(result) in (ConvertingDict, ConvertingList,
     128                                ConvertingTuple):
     129                result.parent = self
     130                result.key = key
     131        return result
     132
     133class BaseConfigurator(object):
     134    """
     135    The configurator base class which defines some useful defaults.
     136    """
     137
     138    CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
     139
     140    WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
     141    DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
     142    INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
     143    DIGIT_PATTERN = re.compile(r'^\d+$')
     144
     145    value_converters = {
     146        'ext' : 'ext_convert',
     147        'cfg' : 'cfg_convert',
     148    }
     149
     150    # We might want to use a different one, e.g. importlib
     151    importer = __import__
     152
     153    def __init__(self, config):
     154        self.config = ConvertingDict(config)
     155        self.config.configurator = self
     156
     157    def resolve(self, s):
     158        """
     159        Resolve strings to objects using standard import and attribute
     160        syntax.
     161        """
     162        name = s.split('.')
     163        used = name.pop(0)
     164        try:
     165            found = self.importer(used)
     166            for frag in name:
     167                used += '.' + frag
     168                try:
     169                    found = getattr(found, frag)
     170                except AttributeError:
     171                    self.importer(used)
     172                    found = getattr(found, frag)
     173            return found
     174        except ImportError:
     175            e, tb = sys.exc_info()[1:]
     176            v = ValueError('Cannot resolve %r: %s' % (s, e))
     177            v.__cause__, v.__traceback__ = e, tb
     178            raise v
     179
     180    def ext_convert(self, value):
     181        """Default converter for the ext:// protocol."""
     182        return self.resolve(value)
     183
     184    def cfg_convert(self, value):
     185        """Default converter for the cfg:// protocol."""
     186        rest = value
     187        m = self.WORD_PATTERN.match(rest)
     188        if m is None:
     189            raise ValueError("Unable to convert %r" % value)
     190        else:
     191            rest = rest[m.end():]
     192            d = self.config[m.groups()[0]]
     193            #print d, rest
     194            while rest:
     195                m = self.DOT_PATTERN.match(rest)
     196                if m:
     197                    d = d[m.groups()[0]]
     198                else:
     199                    m = self.INDEX_PATTERN.match(rest)
     200                    if m:
     201                        idx = m.groups()[0]
     202                        if not self.DIGIT_PATTERN.match(idx):
     203                            d = d[idx]
     204                        else:
     205                            try:
     206                                n = int(idx) # try as number first (most likely)
     207                                d = d[n]
     208                            except TypeError:
     209                                d = d[idx]
     210                if m:
     211                    rest = rest[m.end():]
     212                else:
     213                    raise ValueError('Unable to convert '
     214                                     '%r at %r' % (value, rest))
     215        #rest should be empty
     216        return d
     217
     218    def convert(self, value):
     219        """
     220        Convert values to an appropriate type. dicts, lists and tuples are
     221        replaced by their converting alternatives. Strings are checked to
     222        see if they have a conversion format and are converted if they do.
     223        """
     224        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
     225            value = ConvertingDict(value)
     226            value.configurator = self
     227        elif not isinstance(value, ConvertingList) and isinstance(value, list):
     228            value = ConvertingList(value)
     229            value.configurator = self
     230        elif not isinstance(value, ConvertingTuple) and\
     231                 isinstance(value, tuple):
     232            value = ConvertingTuple(value)
     233            value.configurator = self
     234        elif isinstance(value, basestring): # str for py3k
     235            m = self.CONVERT_PATTERN.match(value)
     236            if m:
     237                d = m.groupdict()
     238                prefix = d['prefix']
     239                converter = self.value_converters.get(prefix, None)
     240                if converter:
     241                    suffix = d['suffix']
     242                    converter = getattr(self, converter)
     243                    value = converter(suffix)
     244        return value
     245
     246    def configure_custom(self, config):
     247        """Configure an object with a user-supplied factory."""
     248        c = config.pop('()')
     249        if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
     250            c = self.resolve(c)
     251        props = config.pop('.', None)
     252        # Check for valid identifiers
     253        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
     254        result = c(**kwargs)
     255        if props:
     256            for name, value in props.items():
     257                setattr(result, name, value)
     258        return result
     259
     260    def as_tuple(self, value):
     261        """Utility function which converts lists to tuples."""
     262        if isinstance(value, list):
     263            value = tuple(value)
     264        return value
     265
     266class DictConfigurator(BaseConfigurator):
     267    """
     268    Configure logging using a dictionary-like object to describe the
     269    configuration.
     270    """
     271
     272    def configure(self):
     273        """Do the configuration."""
     274
     275        config = self.config
     276        if 'version' not in config:
     277            raise ValueError("dictionary doesn't specify a version")
     278        if config['version'] != 1:
     279            raise ValueError("Unsupported version: %s" % config['version'])
     280        incremental = config.pop('incremental', False)
     281        EMPTY_DICT = {}
     282        logging._acquireLock()
     283        try:
     284            if incremental:
     285                handlers = config.get('handlers', EMPTY_DICT)
     286                # incremental handler config only if handler name
     287                # ties in to logging._handlers (Python 2.7)
     288                if sys.version_info[:2] == (2, 7):
     289                    for name in handlers:
     290                        if name not in logging._handlers:
     291                            raise ValueError('No handler found with '
     292                                             'name %r'  % name)
     293                        else:
     294                            try:
     295                                handler = logging._handlers[name]
     296                                handler_config = handlers[name]
     297                                level = handler_config.get('level', None)
     298                                if level:
     299                                    handler.setLevel(_checkLevel(level))
     300                            except StandardError, e:
     301                                raise ValueError('Unable to configure handler '
     302                                                 '%r: %s' % (name, e))
     303                loggers = config.get('loggers', EMPTY_DICT)
     304                for name in loggers:
     305                    try:
     306                        self.configure_logger(name, loggers[name], True)
     307                    except StandardError, e:
     308                        raise ValueError('Unable to configure logger '
     309                                         '%r: %s' % (name, e))
     310                root = config.get('root', None)
     311                if root:
     312                    try:
     313                        self.configure_root(root, True)
     314                    except StandardError, e:
     315                        raise ValueError('Unable to configure root '
     316                                         'logger: %s' % e)
     317            else:
     318                disable_existing = config.pop('disable_existing_loggers', True)
     319
     320                logging._handlers.clear()
     321                del logging._handlerList[:]
     322
     323                # Do formatters first - they don't refer to anything else
     324                formatters = config.get('formatters', EMPTY_DICT)
     325                for name in formatters:
     326                    try:
     327                        formatters[name] = self.configure_formatter(
     328                                                            formatters[name])
     329                    except StandardError, e:
     330                        raise ValueError('Unable to configure '
     331                                         'formatter %r: %s' % (name, e))
     332                # Next, do filters - they don't refer to anything else, either
     333                filters = config.get('filters', EMPTY_DICT)
     334                for name in filters:
     335                    try:
     336                        filters[name] = self.configure_filter(filters[name])
     337                    except StandardError, e:
     338                        raise ValueError('Unable to configure '
     339                                         'filter %r: %s' % (name, e))
     340
     341                # Next, do handlers - they refer to formatters and filters
     342                # As handlers can refer to other handlers, sort the keys
     343                # to allow a deterministic order of configuration
     344                handlers = config.get('handlers', EMPTY_DICT)
     345                for name in sorted(handlers):
     346                    try:
     347                        handler = self.configure_handler(handlers[name])
     348                        handler.name = name
     349                        handlers[name] = handler
     350                    except StandardError, e:
     351                        raise ValueError('Unable to configure handler '
     352                                         '%r: %s' % (name, e))
     353                # Next, do loggers - they refer to handlers and filters
     354
     355                #we don't want to lose the existing loggers,
     356                #since other threads may have pointers to them.
     357                #existing is set to contain all existing loggers,
     358                #and as we go through the new configuration we
     359                #remove any which are configured. At the end,
     360                #what's left in existing is the set of loggers
     361                #which were in the previous configuration but
     362                #which are not in the new configuration.
     363                root = logging.root
     364                existing = root.manager.loggerDict.keys()
     365                #The list needs to be sorted so that we can
     366                #avoid disabling child loggers of explicitly
     367                #named loggers. With a sorted list it is easier
     368                #to find the child loggers.
     369                existing.sort()
     370                #We'll keep the list of existing loggers
     371                #which are children of named loggers here...
     372                child_loggers = []
     373                #now set up the new ones...
     374                loggers = config.get('loggers', EMPTY_DICT)
     375                for name in loggers:
     376                    if name in existing:
     377                        i = existing.index(name)
     378                        prefixed = name + "."
     379                        pflen = len(prefixed)
     380                        num_existing = len(existing)
     381                        i = i + 1 # look at the entry after name
     382                        while (i < num_existing) and\
     383                              (existing[i][:pflen] == prefixed):
     384                            child_loggers.append(existing[i])
     385                            i = i + 1
     386                        existing.remove(name)
     387                    try:
     388                        self.configure_logger(name, loggers[name])
     389                    except StandardError, e:
     390                        raise ValueError('Unable to configure logger '
     391                                         '%r: %s' % (name, e))
     392
     393                #Disable any old loggers. There's no point deleting
     394                #them as other threads may continue to hold references
     395                #and by disabling them, you stop them doing any logging.
     396                #However, don't disable children of named loggers, as that's
     397                #probably not what was intended by the user.
     398                for log in existing:
     399                    logger = root.manager.loggerDict[log]
     400                    if log in child_loggers:
     401                        logger.level = logging.NOTSET
     402                        logger.handlers = []
     403                        logger.propagate = True
     404                    elif disable_existing:
     405                        logger.disabled = True
     406
     407                # And finally, do the root logger
     408                root = config.get('root', None)
     409                if root:
     410                    try:
     411                        self.configure_root(root)
     412                    except StandardError, e:
     413                        raise ValueError('Unable to configure root '
     414                                         'logger: %s' % e)
     415        finally:
     416            logging._releaseLock()
     417
     418    def configure_formatter(self, config):
     419        """Configure a formatter from a dictionary."""
     420        if '()' in config:
     421            factory = config['()'] # for use in exception handler
     422            try:
     423                result = self.configure_custom(config)
     424            except TypeError, te:
     425                if "'format'" not in str(te):
     426                    raise
     427                #Name of parameter changed from fmt to format.
     428                #Retry with old name.
     429                #This is so that code can be used with older Python versions
     430                #(e.g. by Django)
     431                config['fmt'] = config.pop('format')
     432                config['()'] = factory
     433                result = self.configure_custom(config)
     434        else:
     435            fmt = config.get('format', None)
     436            dfmt = config.get('datefmt', None)
     437            result = logging.Formatter(fmt, dfmt)
     438        return result
     439
     440    def configure_filter(self, config):
     441        """Configure a filter from a dictionary."""
     442        if '()' in config:
     443            result = self.configure_custom(config)
     444        else:
     445            name = config.get('name', '')
     446            result = logging.Filter(name)
     447        return result
     448
     449    def add_filters(self, filterer, filters):
     450        """Add filters to a filterer from a list of names."""
     451        for f in filters:
     452            try:
     453                filterer.addFilter(self.config['filters'][f])
     454            except StandardError, e:
     455                raise ValueError('Unable to add filter %r: %s' % (f, e))
     456
     457    def configure_handler(self, config):
     458        """Configure a handler from a dictionary."""
     459        formatter = config.pop('formatter', None)
     460        if formatter:
     461            try:
     462                formatter = self.config['formatters'][formatter]
     463            except StandardError, e:
     464                raise ValueError('Unable to set formatter '
     465                                 '%r: %s' % (formatter, e))
     466        level = config.pop('level', None)
     467        filters = config.pop('filters', None)
     468        if '()' in config:
     469            c = config.pop('()')
     470            if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
     471                c = self.resolve(c)
     472            factory = c
     473        else:
     474            klass = self.resolve(config.pop('class'))
     475            #Special case for handler which refers to another handler
     476            if issubclass(klass, logging.handlers.MemoryHandler) and\
     477                'target' in config:
     478                try:
     479                    config['target'] = self.config['handlers'][config['target']]
     480                except StandardError, e:
     481                    raise ValueError('Unable to set target handler '
     482                                     '%r: %s' % (config['target'], e))
     483            elif issubclass(klass, logging.handlers.SMTPHandler) and\
     484                'mailhost' in config:
     485                config['mailhost'] = self.as_tuple(config['mailhost'])
     486            elif issubclass(klass, logging.handlers.SysLogHandler) and\
     487                'address' in config:
     488                config['address'] = self.as_tuple(config['address'])
     489            factory = klass
     490        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
     491        try:
     492            result = factory(**kwargs)
     493        except TypeError, te:
     494            if "'stream'" not in str(te):
     495                raise
     496            #The argument name changed from strm to stream
     497            #Retry with old name.
     498            #This is so that code can be used with older Python versions
     499            #(e.g. by Django)
     500            kwargs['strm'] = kwargs.pop('stream')
     501            result = factory(**kwargs)
     502        if formatter:
     503            result.setFormatter(formatter)
     504        if level is not None:
     505            result.setLevel(_checkLevel(level))
     506        if filters:
     507            self.add_filters(result, filters)
     508        return result
     509
     510    def add_handlers(self, logger, handlers):
     511        """Add handlers to a logger from a list of names."""
     512        for h in handlers:
     513            try:
     514                logger.addHandler(self.config['handlers'][h])
     515            except StandardError, e:
     516                raise ValueError('Unable to add handler %r: %s' % (h, e))
     517
     518    def common_logger_config(self, logger, config, incremental=False):
     519        """
     520        Perform configuration which is common to root and non-root loggers.
     521        """
     522        level = config.get('level', None)
     523        if level is not None:
     524            logger.setLevel(_checkLevel(level))
     525        if not incremental:
     526            #Remove any existing handlers
     527            for h in logger.handlers[:]:
     528                logger.removeHandler(h)
     529            handlers = config.get('handlers', None)
     530            if handlers:
     531                self.add_handlers(logger, handlers)
     532            filters = config.get('filters', None)
     533            if filters:
     534                self.add_filters(logger, filters)
     535
     536    def configure_logger(self, name, config, incremental=False):
     537        """Configure a non-root logger from a dictionary."""
     538        logger = logging.getLogger(name)
     539        self.common_logger_config(logger, config, incremental)
     540        propagate = config.get('propagate', None)
     541        if propagate is not None:
     542            logger.propagate = propagate
     543
     544    def configure_root(self, config, incremental=False):
     545        """Configure a root logger from a dictionary."""
     546        root = logging.getLogger()
     547        self.common_logger_config(root, config, incremental)
     548
     549dictConfigClass = DictConfigurator
     550
     551def dictConfig(config):
     552    """Configure logging using a dictionary."""
     553    dictConfigClass(config).configure()
  • new file django/utils/log.py

    diff -r d861e2426ce4 django/utils/log.py
    - +  
     1import logging
     2from django.core import mail
     3
     4# Make sure a NullHandler is available
     5# This was added in Python 2.7/3.2
     6try:
     7    from logging import NullHandler
     8except ImportError:
     9    class NullHandler(logging.Handler):
     10        def emit(self, record):
     11            pass
     12
     13# Make sure that dictConfig is available
     14# This was added in Python 2.7/3.2
     15try:
     16    from logging.config import dictConfig
     17except ImportError:
     18    from django.utils.dictconfig import dictConfig
     19
     20# Ensure the creation of the Django logger
     21logger = logging.getLogger('django')
     22
     23
     24class AdminEmailHandler(logging.Handler):
     25    """An exception log handler that emails log entries to site admins
     26
     27    If the request is passed as the first argument to the log record,
     28    request data will be provided in the
     29    """
     30    def emit(self, record):
     31        import traceback
     32        from django.conf import settings
     33
     34        try:
     35            request = record.request
     36
     37            subject = '%s (%s IP): %s' % (
     38                record.levelname,
     39                (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
     40                request.path
     41            )
     42            request_repr = repr(request)
     43        except:
     44            subject = 'Error: Unknown URL'
     45            request_repr = "Request repr() unavailable"
     46
     47        if record.exc_info:
     48            stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
     49        else:
     50            stack_trace = 'No stack trace available'
     51
     52        message = "%s\n\n%s" % (stack_trace, request_repr)
     53        mail.mail_admins(subject, message, fail_silently=True)
  • django/views/decorators/http.py

    diff -r d861e2426ce4 django/views/decorators/http.py
    a b  
    1010from calendar import timegm
    1111from datetime import timedelta
    1212from email.Utils import formatdate
     13import logging
    1314
    1415from django.utils.decorators import decorator_from_middleware, available_attrs
    1516from django.utils.http import parse_etags, quote_etag
    1617from django.middleware.http import ConditionalGetMiddleware
    1718from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
    1819
     20conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
    1921
    20 conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
     22logger = logging.getLogger('django.request')
     23
    2124
    2225def require_http_methods(request_method_list):
    2326    """
     
    3336    def decorator(func):
    3437        def inner(request, *args, **kwargs):
    3538            if request.method not in request_method_list:
     39                logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
     40                    extra={
     41                        'status_code': 405,
     42                        'request': request
     43                    }
     44                )
    3645                return HttpResponseNotAllowed(request_method_list)
    3746            return func(request, *args, **kwargs)
    3847        return wraps(func, assigned=available_attrs(func))(inner)
     
    111120                    if request.method in ("GET", "HEAD"):
    112121                        response = HttpResponseNotModified()
    113122                    else:
     123                        logger.warning('Precondition Failed: %s' % request.path,
     124                            extra={
     125                                'status_code': 412,
     126                                'request': request
     127                            }
     128                        )
    114129                        response = HttpResponse(status=412)
    115130                elif if_match and ((not res_etag and "*" in etags) or
    116131                        (res_etag and res_etag not in etags)):
     132                    logger.warning('Precondition Failed: %s' % request.path,
     133                        extra={
     134                            'status_code': 412,
     135                            'request': request
     136                        }
     137                    )
    117138                    response = HttpResponse(status=412)
    118139                elif (not if_none_match and if_modified_since and
    119140                        request.method == "GET" and
  • django/views/generic/simple.py

    diff -r d861e2426ce4 django/views/generic/simple.py
    a b  
     1import logging
     2
    13from django.template import loader, RequestContext
    24from django.http import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseGone
    35
     6logger = logging.getLogger('django.request')
     7
     8
    49def direct_to_template(request, template, extra_context=None, mimetype=None, **kwargs):
    510    """
    611    Render a given template with any extra URL parameters in the context as
     
    4651        klass = permanent and HttpResponsePermanentRedirect or HttpResponseRedirect
    4752        return klass(url % kwargs)
    4853    else:
     54        logger.warning('Gone: %s' % request.path,
     55                    extra={
     56                        'status_code': 410,
     57                        'request': request
     58                    })
    4959        return HttpResponseGone()
  • docs/index.txt

    diff -r d861e2426ce4 docs/index.txt
    a b  
    176176    * :doc:`Internationalization <topics/i18n/index>`
    177177    * :doc:`Jython support <howto/jython>`
    178178    * :doc:`"Local flavor" <ref/contrib/localflavor>`
     179    * :doc:`Logging <topics/logging>`
    179180    * :doc:`Messages <ref/contrib/messages>`
    180181    * :doc:`Pagination <topics/pagination>`
    181182    * :doc:`Redirects <ref/contrib/redirects>`
  • docs/ref/settings.txt

    diff -r d861e2426ce4 docs/ref/settings.txt
    a b  
    10081008
    10091009.. setting:: LOGIN_REDIRECT_URL
    10101010
     1011LOGGING
     1012-------
     1013
     1014Default: A logging configuration dictionary.
     1015
     1016A data structure containing configuration information. The contents of
     1017this data structure will be passed as the argument to the
     1018configuration method described in :setting:`LOGGING_CONFIG`.
     1019
     1020The default logging configuration passes HTTP 500 server errors to an
     1021email log handler; all other log messages are given to a NullHandler.
     1022
     1023.. versionadded:: 1.3
     1024
     1025LOGGING_CONFIG
     1026--------------
     1027
     1028Default: ``'django.utils.log.dictConfig'``
     1029
     1030A path to a callable that will be used to configure logging in the
     1031Django project. Points at a instance of Python's `dictConfig`_
     1032configuration method by default.
     1033
     1034If you set :setting:`LOGGING_CONFIG` to ``None``, the logging
     1035configuration process will be skipped.
     1036
     1037.. _dictConfig: http://docs.python.org/library/logging.html#logging.dictConfig
     1038
     1039.. versionadded:: 1.3
     1040
    10111041LOGIN_REDIRECT_URL
    10121042------------------
    10131043
  • docs/releases/1.3.txt

    diff -r d861e2426ce4 docs/releases/1.3.txt
    a b  
    8484What's new in Django 1.3
    8585========================
    8686
     87Logging
     88~~~~~~~
     89
     90Django 1.3 adds framework-level support for Python's logging module.
     91This means you can now esaily configure and control logging as part of
     92your Django project. A number of logging handlers and logging calls
     93have been added to Django's own code as well -- most notably, the
     94error emails sent on a HTTP 500 server error are now handled as a
     95logging activity. See :doc:`the documentation on Django's logging
     96interface </topics/logging>` for more details.
  • docs/topics/index.txt

    diff -r d861e2426ce4 docs/topics/index.txt
    a b  
    2020   conditional-view-processing
    2121   email
    2222   i18n/index
     23   logging
    2324   pagination
    2425   serialization
    2526   settings
  • new file docs/topics/logging.txt

    diff -r d861e2426ce4 docs/topics/logging.txt
    - +  
     1=======
     2Logging
     3=======
     4
     5.. versionadded:: 1.3
     6
     7.. module:: django.utils.log
     8   :synopsis: Logging tools for Django applications
     9
     10A quick logging primer
     11======================
     12
     13Django uses Python's builtin logging module to perform system logging.
     14The usage of the logging module is discussed in detail in `Python's
     15own documentation`_. However, if you've never used Python's logging
     16framework (or even if you have), here's a quick primer.
     17
     18.. _Python's own documentation: http://docs.python.org/library/logging.html
     19
     20The cast of players
     21-------------------
     22
     23A Python logging configuration consists of four parts:
     24
     25    * :ref:`topic-logging-parts-loggers`
     26    * :ref:`topic-logging-parts-handlers`
     27    * :ref:`topic-logging-parts-filters`
     28    * :ref:`topic-logging-parts-formatters`
     29
     30.. _topic-logging-parts-loggers:
     31
     32Loggers
     33~~~~~~~
     34
     35A logger is the entry point into the logging system. Each logger is
     36a named bucket to which messages can be written for processing.
     37
     38A logger is configured to have *log level*. This log level describes
     39the severity of the messages that the logger will handle. Python
     40defines the following log levels:
     41
     42    * ``DEBUG``: Low level system information for debugging purposes
     43
     44    * ``INFO``: General system information
     45
     46    * ``WARNING``: Information describing a minor problem that has
     47      occurred.
     48
     49    * ``ERROR``: Information describing a major problem that has
     50      occurred.
     51
     52    * ``CRITICAL``: Information describing a critical problem that has
     53      occurred.
     54
     55Each message that is written to the logger is a *Log Record*. Each log
     56record also has a *log level* indicating the severity of that specific
     57message. A log record can also contain useful metadata that describes
     58the event that is being logged. This can include details such as a
     59stack trace or an error code.
     60
     61When a message is given to the logger, the log level of the message is
     62compare to the log level of the logger. If the log level of the
     63message meets or exceeds the log level of the logger itself, the
     64message will undergo further processing. If it doesn't, the message
     65will be ignored.
     66
     67Once a logger has determined that a message needs to be processed,
     68it is passed to a *Handler*.
     69
     70.. _topic-logging-parts-handlers:
     71
     72Handlers
     73~~~~~~~~
     74
     75The handler is the engine that determines what happens to each message
     76in a logger. It describes a particular logging behavior, such as
     77writing a message to the screen, to a file, or to a network socket.
     78
     79Like loggers, handlers also have a log level. If the log level of a
     80log record doesn't meet or exceed the level of the handler, the
     81handler will ignore the message.
     82
     83A logger can have multiple handlers, and each handler can have a
     84different log level. In this way, it is possible to provide different
     85forms of notification depending on the importance of a message. For
     86example, you could install one handler that forwards ``ERROR`` and
     87``CRITICIAL`` messages to a paging service, while a second handler
     88logs all messages (including ``ERROR`` and ``CRITICAL`` messages) to a
     89file for later analysis.
     90
     91.. _topic-logging-parts-filters:
     92
     93Filters
     94~~~~~~~
     95
     96A filter is used to provide additional control over which log records
     97are passed from logger to handler.
     98
     99By default, any log message that meets log level requirements will be
     100handled. However, by installing a filter, you can place additional
     101criteria on the logging process. For example, you could install a
     102filter that only allows ``ERROR`` messages from a particular source to
     103be emitted.
     104
     105Filters can also be used to modify the logging record prior to being
     106emitted. For example, you could write a filter that downgrades
     107``ERROR`` log records to ``WARNING`` records if a particular set of
     108criteria are met.
     109
     110Filters can be installed on loggers or on handlers; multiple filters
     111can be used in a chain to perform multiple filtering actions.
     112
     113.. _topic-logging-parts-formatters:
     114
     115Formatters
     116~~~~~~~~~~
     117
     118Ultimately, a log record needs to be rendered as text. Formatters
     119describe the exact format of that text. A formatter usually consists
     120of a Python formatting string; however, you can also write custom
     121formatters to implement specific formatting behavior.
     122
     123Using logging
     124=============
     125
     126Once you have configured your loggers, handlers, filters and
     127formatters, you need to place logging calls into your code. Using the
     128logging framework is very simple. Here's an example::
     129
     130    # import the logging library
     131    import logging
     132
     133    # Get an instance of a logger
     134    logger = logging.getLogger(__name__)
     135
     136    def my_view(request, arg1, arg):
     137        ...
     138        if bad_mojo:
     139            # Log an error message
     140            logger.error('Something went wrong!')
     141
     142And that's it! Every time the ``bad_mojo`` condition is activated, an
     143error log record will be written.
     144
     145Naming loggers
     146~~~~~~~~~~~~~~
     147
     148The call to :meth:`logging.getLogger()` obtains (creating, if
     149necessary) an instance of a logger. The logger instance is identified
     150by a name. This name is used to identify the logger for configuration
     151purposes.
     152
     153By convention, the logger name is usually ``__name__``, the name of
     154the python module that contains the logger. This allows you to filter
     155and handle logging calls on a per-module basis. However, if you have
     156some other way of organizing your logging messages, you can provide
     157any dot-separated name to identify your logger::
     158
     159    # Get an instance of a specfic named logger
     160    logger = logging.getLogger('project.interesting.stuff')
     161
     162The dotted paths of logger names define a hierarchy. The
     163``project.interesting`` logger is considered to be a parent of the
     164``project.interesting.stuff`` logger; the ``project`` logger
     165is a parent of the ``project.interesting`` logger.
     166
     167Why is the hierarchy important? Well, because loggers can be set to
     168*propagate* their logging calls to their parents. In this way, you can
     169define a single set of handlers at the root of a logger tree, and
     170capture all logging calls in the subtree of loggers. A logging handler
     171defined in the ``project`` namespace will catch all logging messages
     172issued on the ``project.interesting`` and
     173``project.interesting.stuff`` loggers.
     174
     175This propagation can be controlled on a per-logger basis. If
     176you don't want a particular logger to propagate to it's parents, you
     177can turn off this behavior.
     178
     179Making logging calls
     180~~~~~~~~~~~~~~~~~~~~
     181
     182The logger instance contains an entry method for each of the default
     183log levels:
     184
     185    * ``logger.critical()``
     186    * ``logger.error()``
     187    * ``logger.warning()``
     188    * ``logger.info()``
     189    * ``logger.debug()``
     190
     191There are two other logging calls available:
     192
     193    * ``logger.log()``: manually a logging message with a specific
     194      log level.
     195
     196    * ``logger.exception()``: create a ``ERRORR`` level logging
     197      message wrapping the current exception stack frame.
     198
     199Configuring logging
     200===================
     201
     202Of course, it isn't enough to just put logging calls into your code.
     203You also need to configure the loggers, handlers, filters and
     204formatters to ensure that logging output is output in a useful way.
     205
     206Python's logging library provides several techniques to configure
     207logging, ranging from a programatic interface to configuration files.
     208By default, Django uses the `dictConfig format`_.
     209
     210.. note::
     211    ``logging.dictConfig`` is a builtin library in Python 2.7. In
     212    order to make this library available for users of earlier Python
     213    versions, Django includes a copy as part of ``django.utils.log``.
     214    If you have Python 2.7, the system native library will be used; if
     215    you have Python 2.6 or earlier, Django's copy will be used.
     216
     217In order to configure logging, you use :setting:`LOGGING` to define a
     218dictionary of logging settings. These settings describes the loggers,
     219handlers, filters and formatters that you want in your logging setup,
     220and the log levels and other properties that you want those components
     221to have.
     222
     223Logging is configured immediately after settings have been loaded.
     224Since the loading of settings is one of the first things that Django
     225does, you can be certain that loggers are always ready for use in your
     226project code.
     227
     228.. _dictConfig format: http://docs.python.org/library/logging.html#configuration-dictionary-schema
     229
     230.. _a third-party library: http://bitbucket.org/vinay.sajip/dictconfig
     231
     232An example
     233----------
     234
     235The full documentation for `dictConfig format`_ is the best source of
     236information about logging configuration dictionaries. However, to give
     237you a taste of what is possible, here is an example of a fairly
     238complex logging setup, configured using :meth:`logging.dictConfig`::
     239
     240    LOGGING = {
     241        'version': 1,
     242        'disable_existing_loggers': False,
     243        'formatters': {
     244            'explicit': {
     245                'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
     246            },
     247            'simple': {
     248                'format': '%(levelname)s %(message)s'
     249            },
     250        },
     251        'filters': {
     252            'special': {
     253                '()': 'project.logging.SpecialFilter',
     254                'foo': 'bar',
     255            }
     256        },
     257        'handlers': {
     258            'null': {
     259                'level':'DEBUG',
     260                'class':'django.utils.log.NullHandler',
     261            },
     262            'console':{
     263                'level':'DEBUG',
     264                'class':'logging.StreamHandler',
     265                'formatter': 'simple'
     266            },
     267            'mail_admins': {
     268                'level': 'ERROR',
     269                'class': 'django.utils.log.AdminEmailHandler'
     270                'filters': ['special']
     271            }
     272        },
     273        'loggers': {
     274            'django': {
     275                'handlers':['null'],
     276                'propagate': True,
     277                'level':'INFO',
     278            },
     279            'django.request': {
     280                'handlers': ['mail_admins'],
     281                'level': 'ERROR',
     282                'propagate': False,
     283            },
     284            'myproject.custom': {
     285                'handlers: ['console', 'mail_admins'],
     286                'level': 'INFO',
     287                'filters': ['special']
     288            }
     289        }
     290    }
     291
     292This logging configuration does the following things:
     293
     294    * Identifies the configuration as being in 'dictConfig version 1'
     295      format. At present, this is the only dictConfig format version.
     296
     297    * Disables all existing logging configurations.
     298
     299    * Defines two formatters:
     300
     301        * ``simple``, that just outputs the log level name (e.g.,
     302          ``DEBUG``) and the log message.
     303
     304          The `format` string is a normal Python formatting string
     305          describing the details that are to be output on each logging
     306          line. The full list of detail that can be output can be
     307          found in the `formatter documentation`_.
     308
     309        * ``verbose``, that outputs the log level name, the log
     310          message, plus the time, process, thread and module that
     311          generate the log message.
     312
     313
     314    * Defines one filter -- :class:`project.logging.SpecialFilter`,
     315      using the alias ``special``. If this filter required additional
     316      arguments at time of construction, they can be provided as
     317      additional keys in the filter configuration dictionary. In this
     318      case, the argument ``foo`` will be given a value of ``bar`` when
     319      instantiating the :class:`SpecialFilter`.
     320
     321    * Defines three handlers:
     322
     323        * ``null``, a NullHandler, which will pass any `DEBUG` or
     324          higher message to ``/dev/null``.
     325
     326        * ``console``, a StreamHandler, which will print any `DEBUG`
     327          message to stdout. This handler uses the `simple` output
     328          format.
     329
     330        * ``mail_admins``, an AdminEmailHandler, which will email any
     331          `ERROR` level message to the site admins. This handler uses
     332          the ``special`` filter.
     333
     334    * Configures three loggers:
     335
     336        * ``django``, which passes all messages at ``INFO`` or higher
     337          to the ``null`` handler.
     338
     339        * ``django.request``, which passes all ``ERROR`` messages to
     340          the ``mail_admins`` handler. In addition, this logger is
     341          marked to *not* propagate messages. This means that log
     342          messages written to ``django.request`` will not be handled
     343          by the ``django`` logger.
     344
     345        * ``myproject.custom``, which passes all messages at ``INFO``
     346          or higher that also pass the ``special`` filter to two
     347          handlers -- the ``console``, and ``mail_admins``. This
     348          means that all ``INFO`` level messages (or higher) will be
     349          printed to the console; ``ERROR`` and ``CRITICIAL``
     350          messages will also be output via e-mail.
     351
     352.. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects
     353
     354Custom logging configuration
     355----------------------------
     356
     357If you don't want to use Python's dictConfig format to configure your
     358logger, you can specify your own configuration scheme.
     359
     360The :setting:`LOGGING_CONFIG` setting defines the callable that will
     361be used to configure Django's loggers. By default, it points at
     362Python's :meth:`logging.dictConfig()` method. However, if you want to
     363use a different configuration process, you can use any other callable
     364that takes a single argument. The contents of :setting:`LOGGING` will
     365be provided as the value of that argument when logging is configured.
     366
     367Disabling logging configuration
     368-------------------------------
     369
     370If you don't want to configure logging at all (or you want to manually
     371configure logging using your own approach), you can set
     372:setting:`LOGGING_CONFIG` to ``None``. This will disable the
     373configuration process.
     374
     375Django's logging extensions
     376===========================
     377
     378Django provides a number of utilities to handle the unique
     379requirements of logging in webserver environment.
     380
     381Loggers
     382-------
     383
     384Django provides three built-in loggers.
     385
     386``django``
     387~~~~~~~~~~
     388
     389``django`` is the catch-all logger. No messages are posted directly to
     390this logger.
     391
     392``django.requests``
     393~~~~~~~~~~~~~~~~~~~
     394
     395Log messages related to the handling of requests. 5XX responses are
     396raised as ``ERROR`` messages; 4XX responses are raised as ``WARNING``
     397messages.
     398
     399Messages to this logger have the following extra context:
     400
     401    * ``status_code``: The HTTP response code associated with the
     402      request.
     403
     404    * ``request``: The request object that generated the logging
     405      message.
     406
     407``django.db.backends``
     408~~~~~~~~~~~~~~~~~~~~~~
     409
     410Messages relating to the interaction of code with the database.
     411For example, every SQL statement executed by a request is logged
     412at the ``DEBUG`` level to this logger.
     413
     414Messages to this logger have the following extra context:
     415
     416    * ``duration``: The time taken to execute the SQL statement.
     417    * ``sql``: The SQL statement that was executed.
     418    * ``params``: The parameters that were used in the SQL call.
     419
     420Handlers
     421--------
     422
     423Django provides one log handler in addition to those provided by the
     424Python logging module.
     425
     426.. class:: AdminEmailHandler()
     427
     428    This handler sends an email to the site admins for each log
     429    message it receives.
     430
     431    If the log record contains a 'request' attribute, the full details
     432    of the request will be included in the email.
     433
     434    If the log record contains stack trace information, that stack
     435    trace will be included in the email.
Back to Top