Kaydet (Commit) 24acca41 authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

Fixed #12012 -- Added support for logging. Thanks to Vinay Sajip for his draft…

Fixed #12012 -- Added support for logging. Thanks to Vinay Sajip for his draft patch, and to the many people who gave feedback during development of the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13981 bcc190cf-cafb-0310-a4f2-bffc1f526a37
üst 667d832e
...@@ -16,6 +16,7 @@ from django.utils import importlib ...@@ -16,6 +16,7 @@ from django.utils import importlib
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
class LazySettings(LazyObject): class LazySettings(LazyObject):
""" """
A lazy proxy for either global Django settings or a custom settings object. A lazy proxy for either global Django settings or a custom settings object.
...@@ -114,6 +115,16 @@ class Settings(object): ...@@ -114,6 +115,16 @@ class Settings(object):
os.environ['TZ'] = self.TIME_ZONE os.environ['TZ'] = self.TIME_ZONE
time.tzset() time.tzset()
# Settings are configured, so we can set up the logger if required
if self.LOGGING_CONFIG:
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
class UserSettingsHolder(object): class UserSettingsHolder(object):
""" """
Holder for user configured settings. Holder for user configured settings.
......
...@@ -498,6 +498,34 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackS ...@@ -498,6 +498,34 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackS
# Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within # Default values of MESSAGE_LEVEL and MESSAGE_TAGS are defined within
# django.contrib.messages to avoid imports in this settings file. # django.contrib.messages to avoid imports in this settings file.
###########
# LOGGING #
###########
# The callable to use to configure logging
LOGGING_CONFIG = 'django.utils.log.dictConfig'
# The default logging configuration. This sends an email to
# the site admins on every HTTP 500 error. All other log
# records are sent to the bit bucket.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request':{
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
########### ###########
# TESTING # # TESTING #
########### ###########
......
...@@ -94,3 +94,26 @@ INSTALLED_APPS = ( ...@@ -94,3 +94,26 @@ INSTALLED_APPS = (
# Uncomment the next line to enable admin documentation: # Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs', # 'django.contrib.admindocs',
) )
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request':{
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
import logging
import sys import sys
from django import http from django import http
...@@ -5,6 +6,9 @@ from django.core import signals ...@@ -5,6 +6,9 @@ from django.core import signals
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
from django.utils.importlib import import_module from django.utils.importlib import import_module
logger = logging.getLogger('django.request')
class BaseHandler(object): class BaseHandler(object):
# Changes that are always applied to a response (in this order). # Changes that are always applied to a response (in this order).
response_fixes = [ response_fixes = [
...@@ -118,6 +122,11 @@ class BaseHandler(object): ...@@ -118,6 +122,11 @@ class BaseHandler(object):
return response return response
except http.Http404, e: except http.Http404, e:
logger.warning('Not Found: %s' % request.path,
extra={
'status_code': 404,
'request': request
})
if settings.DEBUG: if settings.DEBUG:
from django.views import debug from django.views import debug
return debug.technical_404_response(request, e) return debug.technical_404_response(request, e)
...@@ -131,6 +140,11 @@ class BaseHandler(object): ...@@ -131,6 +140,11 @@ class BaseHandler(object):
finally: finally:
receivers = signals.got_request_exception.send(sender=self.__class__, request=request) receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
except exceptions.PermissionDenied: except exceptions.PermissionDenied:
logger.warning('Forbidden (Permission denied): %s' % request.path,
extra={
'status_code': 403,
'request': request
})
return http.HttpResponseForbidden('<h1>Permission denied</h1>') return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except SystemExit: except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701 # Allow sys.exit() to actually exit. See tickets #1023 and #4701
...@@ -155,7 +169,6 @@ class BaseHandler(object): ...@@ -155,7 +169,6 @@ class BaseHandler(object):
available would be an error. available would be an error.
""" """
from django.conf import settings from django.conf import settings
from django.core.mail import mail_admins
if settings.DEBUG_PROPAGATE_EXCEPTIONS: if settings.DEBUG_PROPAGATE_EXCEPTIONS:
raise raise
...@@ -164,14 +177,14 @@ class BaseHandler(object): ...@@ -164,14 +177,14 @@ class BaseHandler(object):
from django.views import debug from django.views import debug
return debug.technical_500_response(request, *exc_info) return debug.technical_500_response(request, *exc_info)
# When DEBUG is False, send an error message to the admins. logger.error('Internal Server Error: %s' % request.path,
subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path) exc_info=exc_info,
try: extra={
request_repr = repr(request) 'status_code': 500,
except: 'request':request
request_repr = "Request repr() unavailable" }
message = "%s\n\n%s" % (self._get_traceback(exc_info), request_repr) )
mail_admins(subject, message, fail_silently=True)
# If Http500 handler is not installed, re-raise last exception # If Http500 handler is not installed, re-raise last exception
if resolver.urlconf_module is None: if resolver.urlconf_module is None:
raise exc_info[1], None, exc_info[2] raise exc_info[1], None, exc_info[2]
...@@ -179,11 +192,6 @@ class BaseHandler(object): ...@@ -179,11 +192,6 @@ class BaseHandler(object):
callback, param_dict = resolver.resolve500() callback, param_dict = resolver.resolve500()
return callback(request, **param_dict) return callback(request, **param_dict)
def _get_traceback(self, exc_info=None):
"Helper function to return the traceback as a string"
import traceback
return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info())))
def apply_response_fixes(self, request, response): def apply_response_fixes(self, request, response):
""" """
Applies each of the functions in self.response_fixes to the request and Applies each of the functions in self.response_fixes to the request and
......
import logging
import os import os
from pprint import pformat from pprint import pformat
import sys
from warnings import warn from warnings import warn
from django import http from django import http
...@@ -9,6 +11,9 @@ from django.core.urlresolvers import set_script_prefix ...@@ -9,6 +11,9 @@ from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures from django.utils import datastructures
from django.utils.encoding import force_unicode, smart_str, iri_to_uri from django.utils.encoding import force_unicode, smart_str, iri_to_uri
logger = logging.getLogger('django.request')
# NOTE: do *not* import settings (or any module which eventually imports # NOTE: do *not* import settings (or any module which eventually imports
# settings) until after ModPythonHandler has been called; otherwise os.environ # settings) until after ModPythonHandler has been called; otherwise os.environ
# won't be set up correctly (with respect to settings). # won't be set up correctly (with respect to settings).
...@@ -200,6 +205,13 @@ class ModPythonHandler(BaseHandler): ...@@ -200,6 +205,13 @@ class ModPythonHandler(BaseHandler):
try: try:
request = self.request_class(req) request = self.request_class(req)
except UnicodeDecodeError: except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
exc_info=sys.exc_info(),
extra={
'status_code': 400,
'request': request
}
)
response = http.HttpResponseBadRequest() response = http.HttpResponseBadRequest()
else: else:
response = self.get_response(request) response = self.get_response(request)
......
from threading import Lock import logging
from pprint import pformat from pprint import pformat
import sys
from threading import Lock
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
...@@ -12,6 +14,9 @@ from django.core.urlresolvers import set_script_prefix ...@@ -12,6 +14,9 @@ from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures from django.utils import datastructures
from django.utils.encoding import force_unicode, iri_to_uri from django.utils.encoding import force_unicode, iri_to_uri
logger = logging.getLogger('django.request')
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
STATUS_CODE_TEXT = { STATUS_CODE_TEXT = {
100: 'CONTINUE', 100: 'CONTINUE',
...@@ -236,6 +241,13 @@ class WSGIHandler(base.BaseHandler): ...@@ -236,6 +241,13 @@ class WSGIHandler(base.BaseHandler):
try: try:
request = self.request_class(environ) request = self.request_class(environ)
except UnicodeDecodeError: except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
exc_info=sys.exc_info(),
extra={
'status_code': 400,
'request': request
}
)
response = http.HttpResponseBadRequest() response = http.HttpResponseBadRequest()
else: else:
response = self.get_response(request) response = self.get_response(request)
......
import datetime import datetime
import decimal import decimal
import logging
from time import time from time import time
from django.utils.hashcompat import md5_constructor from django.utils.hashcompat import md5_constructor
logger = logging.getLogger('django.db.backends')
class CursorDebugWrapper(object): class CursorDebugWrapper(object):
def __init__(self, cursor, db): def __init__(self, cursor, db):
self.cursor = cursor self.cursor = cursor
...@@ -15,11 +18,15 @@ class CursorDebugWrapper(object): ...@@ -15,11 +18,15 @@ class CursorDebugWrapper(object):
return self.cursor.execute(sql, params) return self.cursor.execute(sql, params)
finally: finally:
stop = time() stop = time()
duration = stop - start
sql = self.db.ops.last_executed_query(self.cursor, sql, params) sql = self.db.ops.last_executed_query(self.cursor, sql, params)
self.db.queries.append({ self.db.queries.append({
'sql': sql, 'sql': sql,
'time': "%.3f" % (stop - start), 'time': "%.3f" % duration,
}) })
logger.debug('(%.3f) %s; args=%s' % (duration, sql, params),
extra={'duration':duration, 'sql':sql, 'params':params}
)
def executemany(self, sql, param_list): def executemany(self, sql, param_list):
start = time() start = time()
...@@ -27,10 +34,14 @@ class CursorDebugWrapper(object): ...@@ -27,10 +34,14 @@ class CursorDebugWrapper(object):
return self.cursor.executemany(sql, param_list) return self.cursor.executemany(sql, param_list)
finally: finally:
stop = time() stop = time()
duration = stop - start
self.db.queries.append({ self.db.queries.append({
'sql': '%s times: %s' % (len(param_list), sql), 'sql': '%s times: %s' % (len(param_list), sql),
'time': "%.3f" % (stop - start), 'time': "%.3f" % duration,
}) })
logger.debug('(%.3f) %s; args=%s' % (duration, sql, param_list),
extra={'duration':duration, 'sql':sql, 'params':param_list}
)
def __getattr__(self, attr): def __getattr__(self, attr):
if attr in self.__dict__: if attr in self.__dict__:
......
import logging
import re import re
from django.conf import settings from django.conf import settings
...@@ -7,6 +8,9 @@ from django.utils.http import urlquote ...@@ -7,6 +8,9 @@ from django.utils.http import urlquote
from django.core import urlresolvers from django.core import urlresolvers
from django.utils.hashcompat import md5_constructor from django.utils.hashcompat import md5_constructor
logger = logging.getLogger('django.request')
class CommonMiddleware(object): class CommonMiddleware(object):
""" """
"Common" middleware for taking care of some basic operations: "Common" middleware for taking care of some basic operations:
...@@ -38,6 +42,12 @@ class CommonMiddleware(object): ...@@ -38,6 +42,12 @@ class CommonMiddleware(object):
if 'HTTP_USER_AGENT' in request.META: if 'HTTP_USER_AGENT' in request.META:
for user_agent_regex in settings.DISALLOWED_USER_AGENTS: for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
if user_agent_regex.search(request.META['HTTP_USER_AGENT']): if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
logger.warning('Forbidden (User agent): %s' % request.path,
extra={
'status_code': 403,
'request': request
}
)
return http.HttpResponseForbidden('<h1>Forbidden</h1>') return http.HttpResponseForbidden('<h1>Forbidden</h1>')
# Check for a redirect based on settings.APPEND_SLASH # Check for a redirect based on settings.APPEND_SLASH
......
...@@ -6,6 +6,7 @@ against request forgeries from other sites. ...@@ -6,6 +6,7 @@ against request forgeries from other sites.
""" """
import itertools import itertools
import logging
import re import re
import random import random
...@@ -20,6 +21,8 @@ _POST_FORM_RE = \ ...@@ -20,6 +21,8 @@ _POST_FORM_RE = \
_HTML_TYPES = ('text/html', 'application/xhtml+xml') _HTML_TYPES = ('text/html', 'application/xhtml+xml')
logger = logging.getLogger('django.request')
# Use the system (hardware-based) random number generator if it exists. # Use the system (hardware-based) random number generator if it exists.
if hasattr(random, 'SystemRandom'): if hasattr(random, 'SystemRandom'):
randrange = random.SystemRandom().randrange randrange = random.SystemRandom().randrange
...@@ -169,14 +172,26 @@ class CsrfViewMiddleware(object): ...@@ -169,14 +172,26 @@ class CsrfViewMiddleware(object):
# we can use strict Referer checking. # we can use strict Referer checking.
referer = request.META.get('HTTP_REFERER') referer = request.META.get('HTTP_REFERER')
if referer is None: if referer is None:
logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
extra={
'status_code': 403,
'request': request,
}
)
return reject(REASON_NO_REFERER) return reject(REASON_NO_REFERER)
# The following check ensures that the referer is HTTPS, # The following check ensures that the referer is HTTPS,
# the domains match and the ports match - the same origin policy. # the domains match and the ports match - the same origin policy.
good_referer = 'https://%s/' % request.get_host() good_referer = 'https://%s/' % request.get_host()
if not referer.startswith(good_referer): if not referer.startswith(good_referer):
return reject(REASON_BAD_REFERER % reason = REASON_BAD_REFERER % (referer, good_referer)
(referer, good_referer)) logger.warning('Forbidden (%s): %s' % (reason, request.path),
extra={
'status_code': 403,
'request': request,
}
)
return reject(reason)
# If the user didn't already have a CSRF cookie, then fall back to # If the user didn't already have a CSRF cookie, then fall back to
# the Django 1.1 method (hash of session ID), so a request is not # the Django 1.1 method (hash of session ID), so a request is not
...@@ -190,6 +205,12 @@ class CsrfViewMiddleware(object): ...@@ -190,6 +205,12 @@ class CsrfViewMiddleware(object):
# No CSRF cookie and no session cookie. For POST requests, # No CSRF cookie and no session cookie. For POST requests,
# we insist on a CSRF cookie, and in this way we can avoid # we insist on a CSRF cookie, and in this way we can avoid
# all CSRF attacks, including login CSRF. # all CSRF attacks, including login CSRF.
logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
extra={
'status_code': 403,
'request': request,
}
)
return reject(REASON_NO_COOKIE) return reject(REASON_NO_COOKIE)
else: else:
csrf_token = request.META["CSRF_COOKIE"] csrf_token = request.META["CSRF_COOKIE"]
...@@ -199,8 +220,20 @@ class CsrfViewMiddleware(object): ...@@ -199,8 +220,20 @@ class CsrfViewMiddleware(object):
if request_csrf_token != csrf_token: if request_csrf_token != csrf_token:
if cookie_is_new: if cookie_is_new:
# probably a problem setting the CSRF cookie # probably a problem setting the CSRF cookie
logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path),
extra={
'status_code': 403,
'request': request,
}
)
return reject(REASON_NO_CSRF_COOKIE) return reject(REASON_NO_CSRF_COOKIE)
else: else:
logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path),
extra={
'status_code': 403,
'request': request,
}
)
return reject(REASON_BAD_TOKEN) return reject(REASON_BAD_TOKEN)
return accept() return accept()
......
This diff is collapsed.
import logging
from django.core import mail
# Make sure a NullHandler is available
# This was added in Python 2.7/3.2
try:
from logging import NullHandler
except ImportError:
class NullHandler(logging.Handler):
def emit(self, record):
pass
# Make sure that dictConfig is available
# This was added in Python 2.7/3.2
try:
from logging.config import dictConfig
except ImportError:
from django.utils.dictconfig import dictConfig
# Ensure the creation of the Django logger
# with a null handler. This ensures we don't get any
# 'No handlers could be found for logger "django"' messages
logger = logging.getLogger('django')
if not logger.handlers:
logger.addHandler(NullHandler())
class AdminEmailHandler(logging.Handler):
"""An exception log handler that emails log entries to site admins
If the request is passed as the first argument to the log record,
request data will be provided in the
"""
def emit(self, record):
import traceback
from django.conf import settings
try:
request = record.request
subject = '%s (%s IP): %s' % (
record.levelname,
(request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
request.path
)
request_repr = repr(request)
except:
subject = 'Error: Unknown URL'
request_repr = "Request repr() unavailable"
if record.exc_info:
stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
else:
stack_trace = 'No stack trace available'
message = "%s\n\n%s" % (stack_trace, request_repr)
mail.mail_admins(subject, message, fail_silently=True)
...@@ -10,15 +10,18 @@ except ImportError: ...@@ -10,15 +10,18 @@ except ImportError:
from calendar import timegm from calendar import timegm
from datetime import timedelta from datetime import timedelta
from email.Utils import formatdate from email.Utils import formatdate
import logging
from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.decorators import decorator_from_middleware, available_attrs
from django.utils.http import parse_etags, quote_etag from django.utils.http import parse_etags, quote_etag
from django.middleware.http import ConditionalGetMiddleware from django.middleware.http import ConditionalGetMiddleware
from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
conditional_page = decorator_from_middleware(ConditionalGetMiddleware) conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
logger = logging.getLogger('django.request')
def require_http_methods(request_method_list): def require_http_methods(request_method_list):
""" """
Decorator to make a view only accept particular request methods. Usage:: Decorator to make a view only accept particular request methods. Usage::
...@@ -33,6 +36,12 @@ def require_http_methods(request_method_list): ...@@ -33,6 +36,12 @@ def require_http_methods(request_method_list):
def decorator(func): def decorator(func):
def inner(request, *args, **kwargs): def inner(request, *args, **kwargs):
if request.method not in request_method_list: if request.method not in request_method_list:
logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
extra={
'status_code': 405,
'request': request
}
)
return HttpResponseNotAllowed(request_method_list) return HttpResponseNotAllowed(request_method_list)
return func(request, *args, **kwargs) return func(request, *args, **kwargs)
return wraps(func, assigned=available_attrs(func))(inner) return wraps(func, assigned=available_attrs(func))(inner)
...@@ -111,9 +120,21 @@ def condition(etag_func=None, last_modified_func=None): ...@@ -111,9 +120,21 @@ def condition(etag_func=None, last_modified_func=None):
if request.method in ("GET", "HEAD"): if request.method in ("GET", "HEAD"):
response = HttpResponseNotModified() response = HttpResponseNotModified()
else: else:
logger.warning('Precondition Failed: %s' % request.path,
extra={
'status_code': 412,
'request': request
}
)
response = HttpResponse(status=412) response = HttpResponse(status=412)
elif if_match and ((not res_etag and "*" in etags) or elif if_match and ((not res_etag and "*" in etags) or
(res_etag and res_etag not in etags)): (res_etag and res_etag not in etags)):
logger.warning('Precondition Failed: %s' % request.path,
extra={
'status_code': 412,
'request': request
}
)
response = HttpResponse(status=412) response = HttpResponse(status=412)
elif (not if_none_match and if_modified_since and elif (not if_none_match and if_modified_since and
request.method == "GET" and request.method == "GET" and
......
import logging
from django.template import loader, RequestContext from django.template import loader, RequestContext
from django.http import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseGone from django.http import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, HttpResponseGone
logger = logging.getLogger('django.request')
def direct_to_template(request, template, extra_context=None, mimetype=None, **kwargs): def direct_to_template(request, template, extra_context=None, mimetype=None, **kwargs):
""" """
Render a given template with any extra URL parameters in the context as Render a given template with any extra URL parameters in the context as
...@@ -46,4 +51,9 @@ def redirect_to(request, url, permanent=True, query_string=False, **kwargs): ...@@ -46,4 +51,9 @@ def redirect_to(request, url, permanent=True, query_string=False, **kwargs):
klass = permanent and HttpResponsePermanentRedirect or HttpResponseRedirect klass = permanent and HttpResponsePermanentRedirect or HttpResponseRedirect
return klass(url % kwargs) return klass(url % kwargs)
else: else:
logger.warning('Gone: %s' % request.path,
extra={
'status_code': 410,
'request': request
})
return HttpResponseGone() return HttpResponseGone()
...@@ -176,6 +176,7 @@ Other batteries included ...@@ -176,6 +176,7 @@ Other batteries included
* :doc:`Internationalization <topics/i18n/index>` * :doc:`Internationalization <topics/i18n/index>`
* :doc:`Jython support <howto/jython>` * :doc:`Jython support <howto/jython>`
* :doc:`"Local flavor" <ref/contrib/localflavor>` * :doc:`"Local flavor" <ref/contrib/localflavor>`
* :doc:`Logging <topics/logging>`
* :doc:`Messages <ref/contrib/messages>` * :doc:`Messages <ref/contrib/messages>`
* :doc:`Pagination <topics/pagination>` * :doc:`Pagination <topics/pagination>`
* :doc:`Redirects <ref/contrib/redirects>` * :doc:`Redirects <ref/contrib/redirects>`
......
...@@ -1008,6 +1008,36 @@ See :ref:`using-translations-in-your-own-projects`. ...@@ -1008,6 +1008,36 @@ See :ref:`using-translations-in-your-own-projects`.
.. setting:: LOGIN_REDIRECT_URL .. setting:: LOGIN_REDIRECT_URL
LOGGING
-------
Default: A logging configuration dictionary.
A data structure containing configuration information. The contents of
this data structure will be passed as the argument to the
configuration method described in :setting:`LOGGING_CONFIG`.
The default logging configuration passes HTTP 500 server errors to an
email log handler; all other log messages are given to a NullHandler.
.. versionadded:: 1.3
LOGGING_CONFIG
--------------
Default: ``'django.utils.log.dictConfig'``
A path to a callable that will be used to configure logging in the
Django project. Points at a instance of Python's `dictConfig`_
configuration method by default.
If you set :setting:`LOGGING_CONFIG` to ``None``, the logging
configuration process will be skipped.
.. _dictConfig: http://docs.python.org/library/logging.html#logging.dictConfig
.. versionadded:: 1.3
LOGIN_REDIRECT_URL LOGIN_REDIRECT_URL
------------------ ------------------
......
...@@ -109,3 +109,13 @@ encouraged you redeploy your Django instances using :doc:`mod_wsgi ...@@ -109,3 +109,13 @@ encouraged you redeploy your Django instances using :doc:`mod_wsgi
What's new in Django 1.3 What's new in Django 1.3
======================== ========================
Logging
~~~~~~~
Django 1.3 adds framework-level support for Python's logging module.
This means you can now esaily configure and control logging as part of
your Django project. A number of logging handlers and logging calls
have been added to Django's own code as well -- most notably, the
error emails sent on a HTTP 500 server error are now handled as a
logging activity. See :doc:`the documentation on Django's logging
interface </topics/logging>` for more details.
...@@ -20,6 +20,7 @@ Introductions to all the key parts of Django you'll need to know: ...@@ -20,6 +20,7 @@ Introductions to all the key parts of Django you'll need to know:
conditional-view-processing conditional-view-processing
email email
i18n/index i18n/index
logging
pagination pagination
serialization serialization
settings settings
......
This diff is collapsed.
...@@ -10,7 +10,6 @@ class Advertisment(models.Model): ...@@ -10,7 +10,6 @@ class Advertisment(models.Model):
__test__ = {'API_TESTS': """ __test__ = {'API_TESTS': """
>>> from models.publication import Publication >>> from models.publication import Publication
>>> from models.article import Article >>> from models.article import Article
>>> from django.contrib.auth.views import Site
>>> p = Publication(title="FooBar") >>> p = Publication(title="FooBar")
>>> p.save() >>> p.save()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment