Ticket #12012: t12012-alpha1.diff

File t12012-alpha1.diff, 43.3 KB (added by russellm, 5 years ago)

First draft of logging patch for trunk

  • django/conf/__init__.py

    diff -r e943e3ecbf20 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
     119        # First find the logging configuration function ...
     120        logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
     121        logging_config_module = importlib.import_module(logging_config_path)
     122        logging_config_func = getattr(logging_config_module, logging_config_func_name)
     123
     124        # ... then invoke it with the logging settings
     125        logging_config_func(self.LOGGING)
     126
    117127class UserSettingsHolder(object):
    118128    """
    119129    Holder for user configured settings.
  • django/conf/global_settings.py

    diff -r e943e3ecbf20 django/conf/global_settings.py
    a b  
    499499# django.contrib.messages to avoid imports in this settings file.
    500500
    501501###########
     502# LOGGING #
     503###########
     504
     505LOGGING_CONFIG = 'django.utils.log.dictConfig'
     506LOGGING = {
     507    'version': 1,
     508    'disable_existing_loggers': False,
     509    'handlers': {
     510        'null': {
     511            'level':'DEBUG',
     512            'class':'django.utils.log.NullHandler',
     513        },
     514        'mail_admins': {
     515            'level': 'ERROR',
     516            'class': 'django.utils.log.AdminEmailHandler'
     517        }
     518    },
     519    'loggers': {
     520        'django': {
     521            'handlers':['null'],
     522            'propagate': True,
     523            'level':'INFO',
     524        },
     525        'django.request':{
     526            'handlers': ['mail_admins'],
     527            'level': 'ERROR',
     528            'propagate': True,
     529        },
     530    }
     531}
     532
     533###########
    502534# TESTING #
    503535###########
    504536
  • django/conf/project_template/settings.py

    diff -r e943e3ecbf20 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        'console':{
     103            'level':'DEBUG',
     104            'class':'logging.StreamHandler',
     105        },
     106        'mail_admins': {
     107            'level': 'ERROR',
     108            'class': 'django.utils.log.AdminEmailHandler'
     109        }
     110    },
     111    'loggers': {
     112        'django': {
     113            'handlers':['console'],
     114            'propagate': True,
     115            'level':'INFO',
     116        },
     117        'django.request':{
     118            'handlers': ['mail_admins'],
     119            'level': 'ERROR',
     120            'propagate': True,
     121        },
     122    }
     123}
  • django/core/handlers/base.py

    diff -r e943e3ecbf20 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('404 Not Found: %s' % request.path, extra={'request': request})
    121126                if settings.DEBUG:
    122127                    from django.views import debug
    123128                    return debug.technical_404_response(request, e)
     
    131136                        finally:
    132137                            receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
    133138            except exceptions.PermissionDenied:
     139                logger.warning('403 Forbidden (Permission denied): %s' % request.path, extra={'request': request})
    134140                return http.HttpResponseForbidden('<h1>Permission denied</h1>')
    135141            except SystemExit:
    136142                # Allow sys.exit() to actually exit. See tickets #1023 and #4701
     
    155161        available would be an error.
    156162        """
    157163        from django.conf import settings
    158         from django.core.mail import mail_admins
    159164
    160165        if settings.DEBUG_PROPAGATE_EXCEPTIONS:
    161166            raise
     
    164169            from django.views import debug
    165170            return debug.technical_500_response(request, *exc_info)
    166171
    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)
     172        logger.error('500 Internal Server Error: %s' % request.path,
     173            exc_info=exc_info,
     174            extra={'request':request}
     175        )
     176
    175177        # If Http500 handler is not installed, re-raise last exception
    176178        if resolver.urlconf_module is None:
    177179            raise exc_info[1], None, exc_info[2]
     
    179181        callback, param_dict = resolver.resolve500()
    180182        return callback(request, **param_dict)
    181183
    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 
    187184    def apply_response_fixes(self, request, response):
    188185        """
    189186        Applies each of the functions in self.response_fixes to the request and
  • django/core/handlers/modpython.py

    diff -r e943e3ecbf20 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('400 Bad Request (UnicodeDecodeError): %s' % request.path,
     209                    exc_info=sys.exc_info(),
     210                    extra={'request': request}
     211                )
    203212                response = http.HttpResponseBadRequest()
    204213            else:
    205214                response = self.get_response(request)
  • django/core/handlers/wsgi.py

    diff -r e943e3ecbf20 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('400 Bad Request (UnicodeDecodeError): %s' % request.path,
     245                    exc_info=sys.exc_info(),
     246                    extra={'request': request}
     247                )
    239248                response = http.HttpResponseBadRequest()
    240249            else:
    241250                response = self.get_response(request)
  • django/db/backends/util.py

    diff -r e943e3ecbf20 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/db/models/loading.py

    diff -r e943e3ecbf20 django/db/models/loading.py
    a b  
    5555        try:
    5656            if self.loaded:
    5757                return
     58            # First, try to invoke the startup sequence for every app.
     59            # Only do this once, and at the top level of nesting.
     60            if not self.nesting_level:
     61                for app_name in settings.INSTALLED_APPS:
     62                    app_module = import_module(app_name)
     63                    if module_has_submodule(app_module, 'startup'):
     64                        import_module('.startup', app_name)
     65            # Now try to load every app
    5866            for app_name in settings.INSTALLED_APPS:
    5967                if app_name in self.handled:
    6068                    continue
    6169                self.load_app(app_name, True)
     70            # Check to see if we've imported everything.
     71            # If some apps have been postponed, try to load them again.
    6272            if not self.nesting_level:
    6373                for app_name in self.postponed:
    6474                    self.load_app(app_name)
  • django/middleware/common.py

    diff -r e943e3ecbf20 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('403 Forbidden (User agent): %s' % request.path,
     46                        extra={'request': request}
     47                    )
    4148                    return http.HttpResponseForbidden('<h1>Forbidden</h1>')
    4249
    4350        # Check for a redirect based on settings.APPEND_SLASH
  • django/middleware/csrf.py

    diff -r e943e3ecbf20 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('403 Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
     176                        extra={
     177                            'request': request,
     178                            'reason': REASON_NO_REFERER,
     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('403 Forbidden (%s): %s' % (reason, request.path),
     189                        extra={
     190                            'request': request,
     191                            'reason': reason,
     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('403 Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
     209                        extra={
     210                            'request': request,
     211                            'reason': REASON_NO_COOKIE,
     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('403 Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path),
     224                        extra={
     225                            'request': request,
     226                            'reason': REASON_NO_CSRF_COOKIE,
     227                        }
     228                    )
    202229                    return reject(REASON_NO_CSRF_COOKIE)
    203230                else:
     231                    logger.warning('403 Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path),
     232                        extra={
     233                            'request': request,
     234                            'reason': REASON_BAD_TOKEN,
     235                        }
     236                    )
    204237                    return reject(REASON_BAD_TOKEN)
    205238
    206239        return accept()
  • new file django/utils/dictconfig.py

    diff -r e943e3ecbf20 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        found = self.importer(used)
     165        for frag in name:
     166            used += '.' + frag
     167            try:
     168                found = getattr(found, frag)
     169            except AttributeError:
     170                self.importer(used)
     171                found = getattr(found, frag)
     172        return found
     173
     174    def ext_convert(self, value):
     175        """Default converter for the ext:// protocol."""
     176        return self.resolve(value)
     177
     178    def cfg_convert(self, value):
     179        """Default converter for the cfg:// protocol."""
     180        rest = value
     181        m = self.WORD_PATTERN.match(rest)
     182        if m is None:
     183            raise ValueError("Unable to convert %r" % value)
     184        else:
     185            rest = rest[m.end():]
     186            d = self.config[m.groups()[0]]
     187            #print d, rest
     188            while rest:
     189                m = self.DOT_PATTERN.match(rest)
     190                if m:
     191                    d = d[m.groups()[0]]
     192                else:
     193                    m = self.INDEX_PATTERN.match(rest)
     194                    if m:
     195                        idx = m.groups()[0]
     196                        if not self.DIGIT_PATTERN.match(idx):
     197                            d = d[idx]
     198                        else:
     199                            try:
     200                                n = int(idx) # try as number first (most likely)
     201                                d = d[n]
     202                            except TypeError:
     203                                d = d[idx]
     204                if m:
     205                    rest = rest[m.end():]
     206                else:
     207                    raise ValueError('Unable to convert '
     208                                     '%r at %r' % (value, rest))
     209        #rest should be empty
     210        return d
     211
     212    def convert(self, value):
     213        """
     214        Convert values to an appropriate type. dicts, lists and tuples are
     215        replaced by their converting alternatives. Strings are checked to
     216        see if they have a conversion format and are converted if they do.
     217        """
     218        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
     219            value = ConvertingDict(value)
     220            value.configurator = self
     221        elif not isinstance(value, ConvertingList) and isinstance(value, list):
     222            value = ConvertingList(value)
     223            value.configurator = self
     224        elif not isinstance(value, ConvertingTuple) and\
     225                 isinstance(value, tuple):
     226            value = ConvertingTuple(value)
     227            value.configurator = self
     228        elif isinstance(value, basestring): # str for py3k
     229            m = self.CONVERT_PATTERN.match(value)
     230            if m:
     231                d = m.groupdict()
     232                prefix = d['prefix']
     233                converter = self.value_converters.get(prefix, None)
     234                if converter:
     235                    suffix = d['suffix']
     236                    converter = getattr(self, converter)
     237                    value = converter(suffix)
     238        return value
     239
     240    def configure_custom(self, config):
     241        """Configure an object with a user-supplied factory."""
     242        c = config.pop('()')
     243        if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
     244            c = self.resolve(c)
     245        props = config.pop('.', None)
     246        # Check for valid identifiers
     247        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
     248        result = c(**kwargs)
     249        if props:
     250            for name, value in props.items():
     251                setattr(result, name, value)
     252        return result
     253
     254    def as_tuple(self, value):
     255        """Utility function which converts lists to tuples."""
     256        if isinstance(value, list):
     257            value = tuple(value)
     258        return value
     259
     260class DictConfigurator(BaseConfigurator):
     261    """
     262    Configure logging using a dictionary-like object to describe the
     263    configuration.
     264    """
     265
     266    def configure(self):
     267        """Do the configuration."""
     268
     269        config = self.config
     270        if 'version' not in config:
     271            raise ValueError("dictionary doesn't specify a version")
     272        if config['version'] != 1:
     273            raise ValueError("Unsupported version: %s" % config['version'])
     274        incremental = config.pop('incremental', False)
     275        EMPTY_DICT = {}
     276        logging._acquireLock()
     277        try:
     278            if incremental:
     279                handlers = config.get('handlers', EMPTY_DICT)
     280                # incremental handler config only if handler name
     281                # ties in to logging._handlers (Python 2.7)
     282                if sys.version_info[:2] == (2, 7):
     283                    for name in handlers:
     284                        if name not in logging._handlers:
     285                            raise ValueError('No handler found with '
     286                                             'name %r'  % name)
     287                        else:
     288                            try:
     289                                handler = logging._handlers[name]
     290                                handler_config = handlers[name]
     291                                level = handler_config.get('level', None)
     292                                if level:
     293                                    handler.setLevel(_checkLevel(level))
     294                            except StandardError, e:
     295                                raise ValueError('Unable to configure handler '
     296                                                 '%r: %s' % (name, e))
     297                loggers = config.get('loggers', EMPTY_DICT)
     298                for name in loggers:
     299                    try:
     300                        self.configure_logger(name, loggers[name], True)
     301                    except StandardError, e:
     302                        raise ValueError('Unable to configure logger '
     303                                         '%r: %s' % (name, e))
     304                root = config.get('root', None)
     305                if root:
     306                    try:
     307                        self.configure_root(root, True)
     308                    except StandardError, e:
     309                        raise ValueError('Unable to configure root '
     310                                         'logger: %s' % e)
     311            else:
     312                disable_existing = config.pop('disable_existing_loggers', True)
     313
     314                logging._handlers.clear()
     315                del logging._handlerList[:]
     316
     317                # Do formatters first - they don't refer to anything else
     318                formatters = config.get('formatters', EMPTY_DICT)
     319                for name in formatters:
     320                    try:
     321                        formatters[name] = self.configure_formatter(
     322                                                            formatters[name])
     323                    except StandardError, e:
     324                        raise ValueError('Unable to configure '
     325                                         'formatter %r: %s' % (name, e))
     326                # Next, do filters - they don't refer to anything else, either
     327                filters = config.get('filters', EMPTY_DICT)
     328                for name in filters:
     329                    try:
     330                        filters[name] = self.configure_filter(filters[name])
     331                    except StandardError, e:
     332                        raise ValueError('Unable to configure '
     333                                         'filter %r: %s' % (name, e))
     334
     335                # Next, do handlers - they refer to formatters and filters
     336                # As handlers can refer to other handlers, sort the keys
     337                # to allow a deterministic order of configuration
     338                handlers = config.get('handlers', EMPTY_DICT)
     339                for name in sorted(handlers):
     340                    try:
     341                        handler = self.configure_handler(handlers[name])
     342                        handler.name = name
     343                        handlers[name] = handler
     344                    except StandardError, e:
     345                        raise ValueError('Unable to configure handler '
     346                                         '%r: %s' % (name, e))
     347                # Next, do loggers - they refer to handlers and filters
     348
     349                #we don't want to lose the existing loggers,
     350                #since other threads may have pointers to them.
     351                #existing is set to contain all existing loggers,
     352                #and as we go through the new configuration we
     353                #remove any which are configured. At the end,
     354                #what's left in existing is the set of loggers
     355                #which were in the previous configuration but
     356                #which are not in the new configuration.
     357                root = logging.root
     358                existing = root.manager.loggerDict.keys()
     359                #The list needs to be sorted so that we can
     360                #avoid disabling child loggers of explicitly
     361                #named loggers. With a sorted list it is easier
     362                #to find the child loggers.
     363                existing.sort()
     364                #We'll keep the list of existing loggers
     365                #which are children of named loggers here...
     366                child_loggers = []
     367                #now set up the new ones...
     368                loggers = config.get('loggers', EMPTY_DICT)
     369                for name in loggers:
     370                    if name in existing:
     371                        i = existing.index(name)
     372                        prefixed = name + "."
     373                        pflen = len(prefixed)
     374                        num_existing = len(existing)
     375                        i = i + 1 # look at the entry after name
     376                        while (i < num_existing) and\
     377                              (existing[i][:pflen] == prefixed):
     378                            child_loggers.append(existing[i])
     379                            i = i + 1
     380                        existing.remove(name)
     381                    try:
     382                        self.configure_logger(name, loggers[name])
     383                    except StandardError, e:
     384                        raise ValueError('Unable to configure logger '
     385                                         '%r: %s' % (name, e))
     386
     387                #Disable any old loggers. There's no point deleting
     388                #them as other threads may continue to hold references
     389                #and by disabling them, you stop them doing any logging.
     390                #However, don't disable children of named loggers, as that's
     391                #probably not what was intended by the user.
     392                for log in existing:
     393                    logger = root.manager.loggerDict[log]
     394                    if log in child_loggers:
     395                        logger.level = logging.NOTSET
     396                        logger.handlers = []
     397                        logger.propagate = True
     398                    elif disable_existing:
     399                        logger.disabled = True
     400
     401                # And finally, do the root logger
     402                root = config.get('root', None)
     403                if root:
     404                    try:
     405                        self.configure_root(root)
     406                    except StandardError, e:
     407                        raise ValueError('Unable to configure root '
     408                                         'logger: %s' % e)
     409        finally:
     410            logging._releaseLock()
     411
     412    def configure_formatter(self, config):
     413        """Configure a formatter from a dictionary."""
     414        if '()' in config:
     415            factory = config['()'] # for use in exception handler
     416            try:
     417                result = self.configure_custom(config)
     418            except TypeError, te:
     419                if "'format'" not in str(te):
     420                    raise
     421                #Name of parameter changed from fmt to format.
     422                #Retry with old name.
     423                #This is so that code can be used with older Python versions
     424                #(e.g. by Django)
     425                config['fmt'] = config.pop('format')
     426                config['()'] = factory
     427                result = self.configure_custom(config)
     428        else:
     429            fmt = config.get('format', None)
     430            dfmt = config.get('datefmt', None)
     431            result = logging.Formatter(fmt, dfmt)
     432        return result
     433
     434    def configure_filter(self, config):
     435        """Configure a filter from a dictionary."""
     436        if '()' in config:
     437            result = self.configure_custom(config)
     438        else:
     439            name = config.get('name', '')
     440            result = logging.Filter(name)
     441        return result
     442
     443    def add_filters(self, filterer, filters):
     444        """Add filters to a filterer from a list of names."""
     445        for f in filters:
     446            try:
     447                filterer.addFilter(self.config['filters'][f])
     448            except StandardError, e:
     449                raise ValueError('Unable to add filter %r: %s' % (f, e))
     450
     451    def configure_handler(self, config):
     452        """Configure a handler from a dictionary."""
     453        formatter = config.pop('formatter', None)
     454        if formatter:
     455            try:
     456                formatter = self.config['formatters'][formatter]
     457            except StandardError, e:
     458                raise ValueError('Unable to set formatter '
     459                                 '%r: %s' % (formatter, e))
     460        level = config.pop('level', None)
     461        filters = config.pop('filters', None)
     462        if '()' in config:
     463            c = config.pop('()')
     464            if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
     465                c = self.resolve(c)
     466            factory = c
     467        else:
     468            klass = self.resolve(config.pop('class'))
     469            #Special case for handler which refers to another handler
     470            if issubclass(klass, logging.handlers.MemoryHandler) and\
     471                'target' in config:
     472                try:
     473                    config['target'] = self.config['handlers'][config['target']]
     474                except StandardError, e:
     475                    raise ValueError('Unable to set target handler '
     476                                     '%r: %s' % (config['target'], e))
     477            elif issubclass(klass, logging.handlers.SMTPHandler) and\
     478                'mailhost' in config:
     479                config['mailhost'] = self.as_tuple(config['mailhost'])
     480            elif issubclass(klass, logging.handlers.SysLogHandler) and\
     481                'address' in config:
     482                config['address'] = self.as_tuple(config['address'])
     483            factory = klass
     484        kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
     485        try:
     486            result = factory(**kwargs)
     487        except TypeError, te:
     488            if "'stream'" not in str(te):
     489                raise
     490            #The argument name changed from strm to stream
     491            #Retry with old name.
     492            #This is so that code can be used with older Python versions
     493            #(e.g. by Django)
     494            kwargs['strm'] = kwargs.pop('stream')
     495            result = factory(**kwargs)
     496        if formatter:
     497            result.setFormatter(formatter)
     498        if level is not None:
     499            result.setLevel(_checkLevel(level))
     500        if filters:
     501            self.add_filters(result, filters)
     502        return result
     503
     504    def add_handlers(self, logger, handlers):
     505        """Add handlers to a logger from a list of names."""
     506        for h in handlers:
     507            try:
     508                logger.addHandler(self.config['handlers'][h])
     509            except StandardError, e:
     510                raise ValueError('Unable to add handler %r: %s' % (h, e))
     511
     512    def common_logger_config(self, logger, config, incremental=False):
     513        """
     514        Perform configuration which is common to root and non-root loggers.
     515        """
     516        level = config.get('level', None)
     517        if level is not None:
     518            logger.setLevel(_checkLevel(level))
     519        if not incremental:
     520            #Remove any existing handlers
     521            for h in logger.handlers[:]:
     522                logger.removeHandler(h)
     523            handlers = config.get('handlers', None)
     524            if handlers:
     525                self.add_handlers(logger, handlers)
     526            filters = config.get('filters', None)
     527            if filters:
     528                self.add_filters(logger, filters)
     529
     530    def configure_logger(self, name, config, incremental=False):
     531        """Configure a non-root logger from a dictionary."""
     532        logger = logging.getLogger(name)
     533        self.common_logger_config(logger, config, incremental)
     534        propagate = config.get('propagate', None)
     535        if propagate is not None:
     536            logger.propagate = propagate
     537
     538    def configure_root(self, config, incremental=False):
     539        """Configure a root logger from a dictionary."""
     540        root = logging.getLogger()
     541        self.common_logger_config(root, config, incremental)
     542
     543dictConfigClass = DictConfigurator
     544
     545def dictConfig(config):
     546    """Configure logging using a dictionary."""
     547    dictConfigClass(config).configure()
  • new file django/utils/log.py

    diff -r e943e3ecbf20 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 = 'Error (%s IP): %s' % (
     38                (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
     39                request.path
     40            )
     41            request_repr = repr(request)
     42        except:
     43            subject = 'Error: Unknown URL'
     44            request_repr = "Request repr() unavailable"
     45
     46        if record.exc_info:
     47            stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
     48        else:
     49            stack_trace = 'No stack trace available'
     50
     51        message = "%s\n\n%s" % (stack_trace, request_repr)
     52        mail.mail_admins(subject, message, fail_silently=True)
  • django/views/decorators/http.py

    diff -r e943e3ecbf20 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('405 Method Not Allowed (%s): %s' % (request.method, request.path),
     40                    extra={'request': request}
     41                )
    3642                return HttpResponseNotAllowed(request_method_list)
    3743            return func(request, *args, **kwargs)
    3844        return wraps(func, assigned=available_attrs(func))(inner)
     
    111117                    if request.method in ("GET", "HEAD"):
    112118                        response = HttpResponseNotModified()
    113119                    else:
     120                        logger.warning('412 Precondition Failed: %s' % request.path,
     121                            extra={'request': request}
     122                        )
    114123                        response = HttpResponse(status=412)
    115124                elif if_match and ((not res_etag and "*" in etags) or
    116125                        (res_etag and res_etag not in etags)):
     126                    logger.warning('412 Precondition Failed: %s' % request.path,
     127                        extra={'request': request}
     128                    )
    117129                    response = HttpResponse(status=412)
    118130                elif (not if_none_match and if_modified_since and
    119131                        request.method == "GET" and
  • django/views/generic/simple.py

    diff -r e943e3ecbf20 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('410 Gone: %s' % request.path, extra={'request': request})
    4955        return HttpResponseGone()
Back to Top