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