import datetime import re BUFFER_BEGIN = re.compile("^--------- beginning of (.*)$") BUFFER_SWITCH = re.compile("^--------- switch to (.*)$") HEADER = re.compile("^\\[ (\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d) +(.+?): *(\\d+): *(\\d+) *([EWIDV])/(.*?) *\\]$") HEADER_TYPE2 = re.compile("^(\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d) *(\\d+) *(\\d+) *([EWIDV]) ([^ :]*?): (.*?)$") CHATTY_IDENTICAL = re.compile("^.* identical (\\d+) lines$") STATE_BEGIN = 0 STATE_BUFFER = 1 STATE_HEADER = 2 STATE_TEXT = 3 STATE_BLANK = 4 class LogLine(object): """Represents a line of android logs.""" def __init__(self, buf=None, timestamp=None, uid=None, pid=None, tid=None, level=None, tag=None, text=""): self.buf = buf self.timestamp = timestamp self.uid = uid self.pid = pid self.tid = tid self.level = level self.tag = tag self.text = text self.process = None def __str__(self): return "{%s} {%s} {%s} {%s} {%s} {%s}/{%s}: {%s}" % (self.buf, self.timestamp, self.uid, self.pid, self.tid, self.level, self.tag, self.text) def __eq__(self, other): return ( self.buf == other.buf and self.timestamp == other.timestamp and self.uid == other.uid and self.pid == other.pid and self.tid == other.tid and self.level == other.level and self.tag == other.tag and self.text == other.text ) def clone(self): logLine = LogLine(self.buf, self.timestamp, self.uid, self.pid, self.tid, self.level, self.tag, self.text) logLine.process = self.process return logLine def memory(self): """Return an estimate of how much memory is used for the log. 32 bytes of header + 8 bytes for the pointer + the length of the tag and the text. This ignores the overhead of the list of log lines itself.""" return 32 + 8 + len(self.tag) + 1 + len(self.text) + 1 def ParseLogcat(f, processes, duration=None): previous = None for logLine in ParseLogcatInner(f, processes, duration): if logLine.tag == "chatty" and logLine.level == "I": m = CHATTY_IDENTICAL.match(logLine.text) if m: for i in range(int(m.group(1))): clone = previous.clone() clone.timestamp = logLine.timestamp yield clone continue previous = logLine yield logLine def ParseLogcatInner(f, processes, duration=None): """Parses a file object containing log text and returns a list of LogLine objects.""" result = [] buf = None timestamp = None uid = None pid = None tid = None level = None tag = None state = STATE_BEGIN logLine = None previous = None if duration: endTime = datetime.datetime.now() + datetime.timedelta(seconds=duration) # TODO: use a nonblocking / timeout read so we stop if there are # no logs coming out (haha joke, right!) for line in f: if duration and endTime <= datetime.datetime.now(): break if len(line) > 0 and line[-1] == '\n': line = line[0:-1] m = BUFFER_BEGIN.match(line) if m: if logLine: yield logLine logLine = None buf = m.group(1) state = STATE_BUFFER continue m = BUFFER_SWITCH.match(line) if m: if logLine: yield logLine logLine = None buf = m.group(1) state = STATE_BUFFER continue m = HEADER.match(line) if m: if logLine: yield logLine logLine = LogLine( buf=buf, timestamp=m.group(1), uid=m.group(2), pid=m.group(3), tid=m.group(4), level=m.group(5), tag=m.group(6) ) previous = logLine logLine.process = processes.FindPid(logLine.pid, logLine.uid) state = STATE_HEADER continue m = HEADER_TYPE2.match(line) if m: if logLine: yield logLine logLine = LogLine( buf=buf, timestamp=m.group(1), uid="0", pid=m.group(2), tid=m.group(3), level=m.group(4), tag=m.group(5), text=m.group(6) ) previous = logLine logLine.process = processes.FindPid(logLine.pid, logLine.uid) state = STATE_BEGIN continue if not len(line): if state == STATE_BLANK: if logLine: logLine.text += "\n" state = STATE_BLANK continue if logLine: if state == STATE_HEADER: logLine.text += line elif state == STATE_TEXT: logLine.text += "\n" logLine.text += line elif state == STATE_BLANK: if len(logLine.text): logLine.text += "\n" logLine.text += "\n" logLine.text += line state = STATE_TEXT if logLine: yield logLine # vim: set ts=2 sw=2 sts=2 tw=100 nocindent autoindent smartindent expandtab: