getlogbook / logbook

A cool logging replacement for Python.
http://logbook.readthedocs.org
Other
1.48k stars 165 forks source link

LogRecord instance doesn't initialize #226

Closed qianlei90 closed 8 years ago

qianlei90 commented 8 years ago

Sorry for open a new issue, the following content is deprecated. Just ignore this issue.


Hi, all:

I use zeromq and logbook in my code and I found a bug in Logbook(maybe)

Reproduce

Here is a sample code:

  • receiver
from logbook.queues import ZeroMQSubscriber
sub = ZeroMQSubscriber('tcp://*:20002', multi=True)
print sub.recv()

Now receiver is waiting for message.

  • sender
import zmq
c = zmq.Context()
s = c.socket(zmq.PUSH)
s.connect('tcp://127.0.0.1:20002')
s.send_json({'test': 'test string'})

Then the exception raised in receiver:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/python/lib/python2.7/site-packages/logbook/queues.py", line 478, in recv
    return LogRecord.from_dict(json.loads(rv))
  File "/opt/python/lib/python2.7/site-packages/logbook/base.py", line 499, in from_dict
    rv.update_from_dict(d)
  File "/opt/python/lib/python2.7/site-packages/logbook/base.py", line 515, in update_from_dict
    self.extra = defaultdict(lambda: u'', self.extra)
AttributeError: 'LogRecord' object has no attribute 'extra'

Analysis

So I got into the source code of Logbook, and found the problem.

  • queues.py
class ZeroMQSubscriber(SubscriberBase):

    ...

    def recv(self, timeout=None):

        ...

        return LogRecord.from_dict(json.loads(rv))
  • base.py
class LogRecord(object):

    ...

    def __init__(self, channel, level, msg, args=None, kwargs=None,
                 exc_info=None, extra=None, frame=None, dispatcher=None,
                 frame_correction=0):

        ...

        self.extra = defaultdict(lambda: u'', extra or ()) # ----------> Notice here

        ...

   ...

    @classmethod
    def from_dict(cls, d):
        rv = object.__new__(cls) # ----------> here
        rv.update_from_dict(d)
        return rv

    def update_from_dict(self, d):

        ...

        self.extra = defaultdict(lambda: u'', self.extra) # ----------> and here
        return self 

the __new__ method does not do any initialization, it just returns an instance of LogRecord. self.extra is initialized in __init__ method and this method is not called by __new__. So the new instance we just created has no attritube extra.

Solutions maybe available

  • add rv.__init__(...) after rv = object.__new__(cls)
  • move extra out of __init__
  • override __new__ in class LogRecord. I think this is the best solution
  • other better solution

I hope I do not miss anything.