Commit 0bc5cd62 authored by Flavio Curella's avatar Flavio Curella Committed by Tim Graham
Browse files

Fixed #25684 -- Made runserver use logging for request/response output.

Thanks andreif for the contributing to the patch.
parent 3792e291
Loading
Loading
Loading
Loading
+28 −34
Original line number Diff line number Diff line
@@ -9,13 +9,13 @@ been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!

from __future__ import unicode_literals

import logging
import socket
import sys
from wsgiref import simple_server

from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import ISO_8859_1, UTF_8
from django.core.management.color import color_style
from django.core.wsgi import get_wsgi_application
from django.utils import six
from django.utils.encoding import uri_to_iri
@@ -24,6 +24,8 @@ from django.utils.six.moves import socketserver

__all__ = ('WSGIServer', 'WSGIRequestHandler')

logger = logging.getLogger('django.server')


def get_internal_wsgi_application():
    """
@@ -80,7 +82,7 @@ class WSGIServer(simple_server.WSGIServer, object):

    def handle_error(self, request, client_address):
        if is_broken_pipe_error():
            sys.stderr.write("- Broken pipe from %s\n" % (client_address,))
            logger.info("- Broken pipe from %s\n", client_address)
        else:
            super(WSGIServer, self).handle_error(request, client_address)

@@ -94,47 +96,39 @@ class ServerHandler(simple_server.ServerHandler, object):


class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):

    def __init__(self, *args, **kwargs):
        self.style = color_style()
        super(WSGIRequestHandler, self).__init__(*args, **kwargs)

    def address_string(self):
        # Short-circuit parent method to not call socket.getfqdn
        return self.client_address[0]

    def log_message(self, format, *args):

        msg = "[%s] " % self.log_date_time_string()
        try:
            msg += "%s\n" % (format % args)
        except UnicodeDecodeError:
            # e.g. accessing the server via SSL on Python 2
            msg += "\n"

        # Utilize terminal colors, if available
        if args[1][0] == '2':
            # Put 2XX first, since it should be the common case
            msg = self.style.HTTP_SUCCESS(msg)
        elif args[1][0] == '1':
            msg = self.style.HTTP_INFO(msg)
        elif args[1] == '304':
            msg = self.style.HTTP_NOT_MODIFIED(msg)
        elif args[1][0] == '3':
            msg = self.style.HTTP_REDIRECT(msg)
        elif args[1] == '404':
            msg = self.style.HTTP_NOT_FOUND(msg)
        elif args[1][0] == '4':
        extra = {
            'request': self.request,
            'server_time': self.log_date_time_string(),
        }
        if args[1][0] == '4':
            # 0x16 = Handshake, 0x03 = SSL 3.0 or TLS 1.x
            if args[0].startswith(str('\x16\x03')):
                msg = ("You're accessing the development server over HTTPS, "
                    "but it only supports HTTP.\n")
            msg = self.style.HTTP_BAD_REQUEST(msg)
                extra['status_code'] = 500
                logger.error(
                    "You're accessing the development server over HTTPS, but "
                    "it only supports HTTP.\n", extra=extra,
                )
                return

        if args[1].isdigit() and len(args[1]) == 3:
            status_code = int(args[1])
            extra['status_code'] = status_code

            if status_code >= 500:
                level = logger.error
            elif status_code >= 400:
                level = logger.warning
            else:
                level = logger.info
        else:
            # Any 5XX, or any other response
            msg = self.style.HTTP_SERVER_ERROR(msg)
            level = logger.info

        sys.stderr.write(msg)
        level(format, *args, extra=extra)

    def get_environ(self):
        # Strip all headers with underscores in the name before constructing
+50 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ from copy import copy
from django.conf import settings
from django.core import mail
from django.core.mail import get_connection
from django.core.management.color import color_style
from django.utils.deprecation import RemovedInNextVersionWarning
from django.utils.module_loading import import_string
from django.views.debug import ExceptionReporter
@@ -28,12 +29,23 @@ DEFAULT_LOGGING = {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'formatters': {
        'django.server': {
            '()': 'django.utils.log.ServerFormatter',
            'format': '[%(server_time)s] %(message)s',
        }
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        },
        'django.server': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'django.server',
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
@@ -45,6 +57,11 @@ DEFAULT_LOGGING = {
            'handlers': ['console', 'mail_admins'],
            'level': 'INFO',
        },
        'django.server': {
            'handlers': ['django.server'],
            'level': 'INFO',
            'propagate': False,
        },
        'py.warnings': {
            'handlers': ['console'],
        },
@@ -155,3 +172,36 @@ class RequireDebugFalse(logging.Filter):
class RequireDebugTrue(logging.Filter):
    def filter(self, record):
        return settings.DEBUG


class ServerFormatter(logging.Formatter):
    def __init__(self, *args, **kwargs):
        self.style = color_style()
        super(ServerFormatter, self).__init__(*args, **kwargs)

    def format(self, record):
        args = record.args
        msg = record.msg

        if len(args) == 0:
            msg = self.style.HTTP_BAD_REQUEST(msg)
        else:
            if args[1][0] == '2':
                # Put 2XX first, since it should be the common case
                msg = self.style.HTTP_SUCCESS(msg)
            elif args[1][0] == '1':
                msg = self.style.HTTP_INFO(msg)
            elif args[1] == '304':
                msg = self.style.HTTP_NOT_MODIFIED(msg)
            elif args[1][0] == '3':
                msg = self.style.HTTP_REDIRECT(msg)
            elif args[1] == '404':
                msg = self.style.HTTP_NOT_FOUND(msg)
            elif args[1][0] == '4':
                msg = self.style.HTTP_BAD_REQUEST(msg)
            else:
                # Any 5XX, or any other response
                msg = self.style.HTTP_SERVER_ERROR(msg)

        record.msg = msg
        return super(ServerFormatter, self).format(record)
+8 −0
Original line number Diff line number Diff line
@@ -827,6 +827,14 @@ with its own :ref:`runserver<staticfiles-runserver>` command.
If :djadmin:`migrate` was not previously executed, the table that stores the
history of migrations is created at first run of ``runserver``.

Logging of each request and response of the server is sent to the
:ref:`django-server-logger` logger.

.. versionchanged:: 1.10

    In older versions, log messages were written to ``sys.stderr`` instead of
    being handled through Python logging.

.. django-admin-option:: --noreload

Use the ``--noreload`` option to disable the use of the auto-reloader. This
+30 −0
Original line number Diff line number Diff line
@@ -397,6 +397,36 @@ Dropped support for PostgreSQL 9.1
Upstream support for PostgreSQL 9.1 ends in September 2016. As a consequence,
Django 1.10 sets PostgreSQL 9.2 as the minimum version it officially supports.

``runserver`` output goes through logging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Request and response handling of the ``runserver`` command is sent to the
:ref:`django-server-logger` logger instead of to ``sys.stderr``. If you
disable Django's logging configuration or override it with your own, you'll
need to add the appropriate logging configuration if you want to see that
output::

    'formatters': {
        'django.server': {
            '()': 'django.utils.log.ServerFormatter',
            'format': '[%(server_time)s] %(message)s',
        }
    },
    'handlers': {
        'django.server': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'django.server',
        },
    },
    'loggers': {
        'django.server': {
            'handlers': ['django.server'],
            'level': 'INFO',
            'propagate': False,
        }
    }

Miscellaneous
~~~~~~~~~~~~~

+23 −0
Original line number Diff line number Diff line
@@ -484,6 +484,24 @@ Messages to this logger have the following extra context:
* ``request``: The request object that generated the logging
  message.

.. _django-server-logger:

``django.server``
~~~~~~~~~~~~~~~~~

.. versionadded:: 1.10

Log messages related to the handling of requests received by the server invoked
by the :djadmin:`runserver` command. HTTP 5XX responses are logged as ``ERROR``
messages, 4XX responses are logged as ``WARNING`` messages, and everything else
is logged as ``INFO``.

Messages to this logger have the following extra context:

* ``status_code``: The HTTP response code associated with the request.

* ``request``: The request object that generated the logging message.

.. _django-template-logger:

``django.template``
@@ -729,6 +747,11 @@ When :setting:`DEBUG` is ``False``:
* The ``django`` logger send messages with ``ERROR`` or ``CRITICAL`` level to
  :class:`AdminEmailHandler`.

Independent of the value of :setting:`DEBUG`:

* The :ref:`django-server-logger` logger sends all messages at the ``INFO``
  level or higher to the console.

.. versionchanged:: 1.9

    Django's default logging configuration changed. See :ref:`the release notes
Loading