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