1
2import datetime
3import re
4
5BUFFER_BEGIN = re.compile("^--------- beginning of (.*)$")
6BUFFER_SWITCH = re.compile("^--------- switch to (.*)$")
7HEADER = re.compile("^\\[ (\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d) +(.+?): *(\\d+): *(\\d+) *([EWIDV])/(.*?) *\\]$")
8CHATTY_IDENTICAL = re.compile("^.* identical (\\d+) lines$")
9
10STATE_BEGIN = 0
11STATE_BUFFER = 1
12STATE_HEADER = 2
13STATE_TEXT = 3
14STATE_BLANK = 4
15
16class LogLine(object):
17  """Represents a line of android logs."""
18  def __init__(self, buf=None, timestamp=None, uid=None, pid=None, tid=None, level=None,
19      tag=None, text=""):
20    self.buf = buf
21    self.timestamp = timestamp
22    self.uid = uid
23    self.pid = pid
24    self.tid = tid
25    self.level = level
26    self.tag = tag
27    self.text = text
28    self.process = None
29
30  def __str__(self):
31    return "{%s} {%s} {%s} {%s} {%s} {%s}/{%s}: {%s}" % (self.buf, self.timestamp, self.uid,
32        self.pid, self.tid, self.level, self.tag, self.text)
33
34  def __eq__(self, other):
35      return (
36            self.buf == other.buf
37            and self.timestamp == other.timestamp
38            and self.uid == other.uid
39            and self.pid == other.pid
40            and self.tid == other.tid
41            and self.level == other.level
42            and self.tag == other.tag
43            and self.text == other.text
44          )
45
46  def clone(self):
47    logLine = LogLine(self.buf, self.timestamp, self.uid, self.pid, self.tid, self.level,
48        self.tag, self.text)
49    logLine.process = self.process
50    return logLine
51
52  def memory(self):
53    """Return an estimate of how much memory is used for the log.
54      32 bytes of header + 8 bytes for the pointer + the length of the tag and the text.
55      This ignores the overhead of the list of log lines itself."""
56    return 32 + 8 + len(self.tag) + 1 + len(self.text) + 1
57
58
59def ParseLogcat(f, processes, duration=None):
60  previous = None
61  for logLine in ParseLogcatInner(f, processes, duration):
62    if logLine.tag == "chatty" and logLine.level == "I":
63      m = CHATTY_IDENTICAL.match(logLine.text)
64      if m:
65        for i in range(int(m.group(1))):
66          clone = previous.clone()
67          clone.timestamp = logLine.timestamp
68          yield clone
69        continue
70    previous = logLine
71    yield logLine
72
73
74def ParseLogcatInner(f, processes, duration=None):
75  """Parses a file object containing log text and returns a list of LogLine objects."""
76  result = []
77
78  buf = None
79  timestamp = None
80  uid = None
81  pid = None
82  tid = None
83  level = None
84  tag = None
85
86  state = STATE_BEGIN
87  logLine = None
88  previous = None
89
90  if duration:
91    endTime = datetime.datetime.now() + datetime.timedelta(seconds=duration)
92
93  # TODO: use a nonblocking / timeout read so we stop if there are
94  # no logs coming out (haha joke, right!)
95  for line in f:
96    if duration and endTime <= datetime.datetime.now():
97      break
98
99    if len(line) > 0 and line[-1] == '\n':
100      line = line[0:-1]
101
102    m = BUFFER_BEGIN.match(line)
103    if m:
104      if logLine:
105        yield logLine
106        logLine = None
107      buf = m.group(1)
108      state = STATE_BUFFER
109      continue
110
111    m = BUFFER_SWITCH.match(line)
112    if m:
113      if logLine:
114        yield logLine
115        logLine = None
116      buf = m.group(1)
117      state = STATE_BUFFER
118      continue
119
120    m = HEADER.match(line)
121    if m:
122      if logLine:
123        yield logLine
124      logLine = LogLine(
125            buf=buf,
126            timestamp=m.group(1),
127            uid=m.group(2),
128            pid=m.group(3),
129            tid=m.group(4),
130            level=m.group(5),
131            tag=m.group(6)
132          )
133      previous = logLine
134      logLine.process = processes.FindPid(logLine.pid, logLine.uid)
135      state = STATE_HEADER
136      continue
137
138    if not len(line):
139      if state == STATE_BLANK:
140        if logLine:
141          logLine.text += "\n"
142      state = STATE_BLANK
143      continue
144
145    if logLine:
146      if state == STATE_HEADER:
147        logLine.text += line
148      elif state == STATE_TEXT:
149        logLine.text += "\n"
150        logLine.text += line
151      elif state == STATE_BLANK:
152        if len(logLine.text):
153          logLine.text += "\n"
154        logLine.text += "\n"
155        logLine.text += line
156    state = STATE_TEXT
157
158  if logLine:
159    yield logLine
160
161
162# vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab:
163