summaryrefslogtreecommitdiff
path: root/debuglog.py
blob: 7b34270ec2e0c65bdf60e8a7f6002e4c4ce6cd1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import collections
import json


class DebugLog(object):

    '''Debug logging class.

    The Python standard library contains the library called logging,
    which is fine for general purpose logging. For logs meant to help
    developers analyse problems, they are less than ideal.

    * Log files are essentially free-form text, with only minimal
      structure (timestamps), and that is unreliable to parse when
      log messages may contain newlines.
    * It is not very efficient.
    * Typically, a log grows quite large, making it use a lot of disk
      space. This is wasteful when most developers would only need
      the end of the log, at the point of a crash, to solve most problems.
      (But sometimes the whole log from the whole runtime would be good.)

    This class provides a different kind of logging. The log file is only
    written when requested, not continously, and is stored in a ring buffer,
    meaning only the last messages are written, with earlier ones forgotten.
    This is fine for crash logs. The logs are written in a format that is
    easily machine parseable (JSON).

    '''

    DEFAULT_RING_BUFFER_SIZE = 1000

    def __init__(self):
        self._max_buffer_size = self.DEFAULT_RING_BUFFER_SIZE
        self.clear()

    def set_ring_buffer_size(self, num_entries):
        self._max_buffer_size = num_entries
        while len(self._ring_buffer) > self._max_buffer_size:
            self._ring_buffer.popleft()

    def add_to_preamble(self, **kwargs):
        self._preamble.append(kwargs)

    def log(self, **kwargs):
        self._ring_buffer.append(kwargs)

    def write_ring_buffer(self, filename_or_handle):

        def write(f):
            f.write('[\n')
            first = True
            for q in [self._preamble, self._ring_buffer]:
                for entry in q:
                    if first:
                        first = False
                    else:
                        f.write(',\n')
                    json.dump(entry, f, indent=4)
            f.write('\n]\n')

        if hasattr(filename_or_handle, 'write'):
            write(filename_or_handle)
        else:
            with open(filename_or_handle, 'a') as f:
                write(f)

    def clear(self):
        self._preamble = collections.deque([], self._max_buffer_size)
        self._ring_buffer = collections.deque([], self._max_buffer_size)


if __name__ == '__main__':
    x = DebugLog()
    x.add_to_preamble(version='1.2')
    x.log(msg='yoyoyo', url='foobar')
    x.log(msg='blahblah', data=123)
    x.write_ring_buffer('/dev/stdout')