Opened 11 months ago

Last modified 4 weeks ago

#29186 assigned Bug

"django.request" logging breaks "logging.handlers.SocketHandler"

Reported by: direx Owned by: HyunTae Hwang
Component: Core (Other) Version: 2.0
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When setting up logging with Python's default SocketHandler then the log messages produced by django.request cause an exception in the logging system. This happens everywhere where Django passes an extra={'request': request} dictionary to log messages. The reason for this is that the request object cannot be pickled.

Steps to reproduce (example):

  1. ./manage.py startproject
  2. Add this logging config in settings:
    LOGGING = {    
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'socket_handler': {
                'class': 'logging.handlers.SocketHandler',
                'host': '127.0.0.1',
                'port': 9020,
            }   
        },  
        'loggers': {
            'django.request': {
                'handlers': ['socket_handler'],
                'level': 'INFO',
                'propagate': False,
            },  
        }   
    }
    
  3. ./manage.py migrate
  4. ./manage.py runserver
  5. wget http://127.0.0.1:8000/invalid -O /dev//null

The exception is this one:

--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.6/logging/handlers.py", line 633, in emit
    s = self.makePickle(record)
  File "/usr/lib/python3.6/logging/handlers.py", line 605, in makePickle
    s = pickle.dumps(d, 1)
  File "/usr/lib/python3.6/copyreg.py", line 65, in _reduce_ex
    raise TypeError("can't pickle %s objects" % base.__name__)
TypeError: can't pickle BufferedReader objects
Call stack:
  File "/usr/lib/python3.6/threading.py", line 884, in _bootstrap
    self._bootstrap_inner()
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib/python3.6/socketserver.py", line 639, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.6/socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.6/socketserver.py", line 696, in __init__
    self.handle()
  File "/home/direx/virtualenv/django-2.0/lib/python3.6/site-packages/django/core/servers/basehttp.py", line 154, in handle
    handler.run(self.server.get_app())
  File "/usr/lib/python3.6/wsgiref/handlers.py", line 137, in run
    self.result = application(self.environ, self.start_response)
  File "/home/direx/virtualenv/django-2.0/lib/python3.6/site-packages/django/contrib/staticfiles/handlers.py", line 66, in __call__
    return self.application(environ, start_response)
  File "/home/direx/virtualenv/django-2.0/lib/python3.6/site-packages/django/core/handlers/wsgi.py", line 146, in __call__
    response = self.get_response(request)
  File "/home/direx/virtualenv/django-2.0/lib/python3.6/site-packages/django/core/handlers/base.py", line 93, in get_response
    extra={'status_code': 404, 'request': request},
Message: 'Not Found: %s'
Arguments: ('/invalid',)

Of course these steps are only an example. This bug does not only apply to 404 errors, but also to CSRF verfication errors for instance. In fact all places where the request object is passed in as an extra logger argument.

I see a few possible solutions for this issue:

  1. Remove the request object from the extra log message dict. Right now I am not even sure why this is required.
  2. Make the entire request object pickleable (probably not an easy task)
  3. Pass in a reduced (pickable) version of the request object in the extra dict
  4. Ship a compatible version of SocketHandler

BTW: socket logging is explicitly mentioned in the Django docs:

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

This bug also applies to older Django versions.

Change History (4)

comment:1 Changed 11 months ago by Tim Graham

Component: UncategorizedCore (Other)
Triage Stage: UnreviewedAccepted

I'm not sure what the best solution is, but I don't think removing request from extra is acceptable as that would be backwards incompatible for logging handlers using that information.

comment:2 Changed 11 months ago by direx

Just for the record: A custom SocketHandler which works around this problem could look like this:

# -*- coding: utf-8 -*-
from logging.handlers import SocketHandler as _SocketHandler

class DjangoSocketHandler(_SocketHandler):
    def emit(self, record):
        if hasattr(record, 'request'):
            record.request = None
        return super().emit(record)

I don't know if you guys want to ship this as a workaround and update the documentation accordingly. This could also be a documentation-only fix where this code is added as an example for socket logging.

On the other hand an actual fix would be nice of course. I know this is not an easy task and since the majority of people probably won't be using socket logging, a documented and supported workaround (such as the code above) might be sufficient.

comment:3 Changed 5 weeks ago by HyunTae Hwang

Owner: changed from nobody to HyunTae Hwang
Status: newassigned

comment:4 Changed 5 weeks ago by HyunTae Hwang

I made a Pull Request. https://github.com/django/django/pull/10758

Respect to direx and Tim, I think django framework has to replace the SocketHandler to its own SocketHandler that replace the WSGIRequest object with pickle-able dictionary.

Django framework should respect the design philosophy of Python. So it’s unfair changing SocketHandler.

And it’s unfair blocking SocketHandler or removing request object when it passed into handlers.

I wonder this approach is okay. Please check this PR.

Last edited 4 weeks ago by HyunTae Hwang (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top