-
Notifications
You must be signed in to change notification settings - Fork 4
/
logcat-color
executable file
·272 lines (223 loc) · 10.6 KB
/
logcat-color
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python
"""
recat
Copyright 2015, Doat Media LTD.
Licensed under the Apache License, Version 2.0
logcat-color
Copyright 2012, Marshall Culpepper
Licensed under the Apache License, Version 2.0
Portions Copyright 2009, The Android Open Source Project
Thanks to Jeff Sharkey, the author of coloredlogcat.py,
the original inspiration of logcat-color
"""
import asyncore
import errno
import fcntl
import optparse
import os
import struct
import sys
import termios
from colorama import Fore, Back, Style
from subprocess import check_call, Popen, PIPE
from logcatcolor.config import LogcatColorConfig
from logcatcolor.mapping import MappingFetcher
from logcatcolor.profile import Profile
from logcatcolor.reader import LogcatReader
from logcatcolor.retrace import Retracer
class LogcatColor(object):
def __init__(self, args=None):
self.parse_args(args)
self.width = self.get_term_width()
self.config = LogcatColorConfig(self.options)
self.profile = None
if len(self.args) >= 1:
self.profile = Profile.get_profile(self.args[0])
if self.profile:
self.args = self.args[1:]
if not self.profile:
self.logcat_args.extend(self.args)
self.format = None
if self.options.format:
self.format = self.options.format
elif self.profile and self.profile.format:
self.format = self.profile.format
self.layout = self.format
if self.options.plain:
self.layout = "raw"
def get_term_width(self):
out_fd = self.output.fileno()
if os.isatty(out_fd):
# unpack the current terminal width / height
data = fcntl.ioctl(out_fd, termios.TIOCGWINSZ, '1234')
height, width = struct.unpack('hh', data)
else:
# store a large width when the output of this script is being piped
width = 2000
return width
def parse_args(self, args=None):
parser = optparse.OptionParser()
# logcat-color options
parser.add_option("--config", dest="config", default=None,
help="path to logcat-color config file (default: ~/.logcat-config)")
parser.add_option("--plain", action="store_true", dest="plain", default=False,
help="apply profiles and filters, but don't colorize / format output (useful for logging to a file)")
parser.add_option("--no-wrap", action="store_false", dest="wrap", default=None,
help="don't wrap console text into a column (makes for better copy/paste)")
parser.add_option("--stay-connected", action="store_true", default=None, dest="stay_connected",
help="keep logcat-color running when the device disconnects, and automatically wait for the device to reconnect")
parser.add_option("-i", "--input", metavar="FILE", dest="input", default=None,
help="read input from FILE, instead of starting adb. this is equivalent to piping FILE to logcat-color. (default: start adb, and read from it's stdout)")
parser.add_option("-o", "--output", metavar="FILE", dest="output", default=None,
help="write output to FILE (default: stdout)")
parser.add_option("-m", "--mapping", dest="mapping", default=None,
help="On-the-fly deobfuscation using retrace (need to supply a mapping file)")
parser.add_option("-t", "--tag-prefixes", dest="tag_prefixes", default=[],
help="prefixes of tags which ought to be deobfuscated")
# ADB options
parser.add_option("-d", "--device", action="store_const", dest="adb_device", const="device",
help="connect to the only plugged-in device")
parser.add_option("-e", "--emulator", action="store_const", dest="adb_device", const="emulator",
help="connect to the only running emulator")
parser.add_option("-s", "--serial-number", dest="adb_device",
help="connect to a specific device by it's serial number")
# Logcat options
# See http://developer.android.com/guide/developing/tools/logcat.html
# We can't support -d / -s since we use them for ADB above, but we
# provide long-form options in case they are needed
parser.add_option("-b", "--buffer", action="append", dest="buffers",
help="loads an alternate log buffer for viewing, such as event or radio")
parser.add_option("-c", "--clear", action="append_const", dest="logcat_args", const="-c",
help="clears (flushes) the entire log and exits")
parser.add_option("--dump", action="append_const", dest="logcat_args", const="-d",
help="dumps the log to the screen and exits")
parser.add_option("-f", "--file", dest="file", default=None,
help="writes log message output to <file>. the default is stdout")
parser.add_option("-g", "--print-size", action="append_const", dest="logcat_args", const="-g",
help="prints the size of the specified log buffer and exits")
parser.add_option("-n", "--max-rotated-logs", dest="max_rotated_logs", type="int",
help="sets the maximum number of rotated logs. requires the -r option (default: 4)")
parser.add_option("-r", "--rotate", dest="rotate_kbytes", type="int",
help="rotates the log file every <rotate_kbytes> of output. requires the -f option (default: 16)")
parser.add_option("--silent", action="append_const", dest="logcat_args", const="-s",
help="sets the default filter spec to silent")
parser.add_option("-v", "--format", dest="format", default=None,
help="sets the output format for log messages. possible formats: brief, process, tag, raw, time, threadtime, long (default: brief)")
(options, args) = parser.parse_args(args)
self.options = options
self.args = args
if options.config and not os.path.isfile(options.config):
parser.error("Config file does not exist: %s" % options.config)
self.input = sys.stdin
if options.input:
self.input = open(options.input, "r")
self.output = sys.stdout
if options.output:
self.output = open(options.output, "w")
self.adb_device = options.adb_device
self.logcat_args = options.logcat_args or []
self.mapping = options.mapping
self.tag_prefixes = options.tag_prefixes
if options.buffers:
for buf in options.buffers:
self.logcat_args.extend(["-b", buf])
if options.file:
self.logcat_args.extend(["-f", options.file])
if options.max_rotated_logs:
self.logcat_args.extend(["-n", options.max_rotated_logs])
if options.rotate_kbytes:
self.logcat_args.extend(["-r", options.rotate_kbytes])
def get_adb_args(self):
adb = "adb" # Let the system find adb on the PATH
if "ADB" in os.environ:
adb = os.environ["ADB"]
config_adb = self.config.get_adb()
if config_adb:
adb = config_adb
adb_args = [adb]
if not self.adb_device and self.profile:
emulator = self.profile.emulator
if emulator:
self.adb_device = \
emulator if type(emulator) is str else "emulator"
device = self.profile.device
if device:
self.adb_device = device if type(device) is str else "device"
if self.adb_device == "emulator":
adb_args.append("-e")
elif self.adb_device == "device":
adb_args.append("-d")
elif self.adb_device:
adb_args.extend(["-s", self.adb_device])
return adb_args
def get_logcat_args(self):
logcat_args = self.logcat_args[:]
if self.format:
# put format in front in case custom filters are used
logcat_args[0:0] = ["-v", self.format]
if self.profile:
buffers = self.profile.buffers
if buffers:
for b in buffers: logcat_args.extend(["-b", b])
return logcat_args
def start_logcat(self):
adb_command = self.get_adb_args()
adb_command.append("logcat")
adb_command.extend(self.get_logcat_args())
try:
self.input = Popen(adb_command, stdin=PIPE, stdout=PIPE, stderr=PIPE).stdout
except OSError, e:
if e.errno == errno.ENOENT:
print >>sys.stderr, \
'Error, adb could not be found using: "%s"\n' \
'To fix this: \n' \
' 1) Add the directory containing adb to your PATH\n' \
' 2) Set the ADB environment variable\n' \
' 3) Set "adb" in ~/.logcat-color' % adb_command[0]
else:
print >>sys.stderr, 'Could not run ADB: %s' % str(e)
sys.exit(e.errno)
def init_reader(self):
source = None
mapping_fetcher = MappingFetcher('', self.mapping)
mapping_file = mapping_fetcher.get_mapping_file()
retracer = None
if mapping_file:
retracer = Retracer(mapping_file, self.tag_prefixes)
# If not obfuscated or failed fetching obfuscation file, fail back to original input
if not source:
source = self.input
reader = LogcatReader(self.input, self.config, profile=self.profile,
format=self.format, layout=self.layout, writer=self.output,
width=self.width, tag_prefixes=self.tag_prefixes, retracer=retracer)
def start(self):
# if someone is piping, use stdin as input. if not, invoke adb logcat
# if self.input.isatty():
self.start_logcat()
self.init_reader()
def loop(self):
try:
self.start()
while True:
asyncore.loop()
if not self.config.get_stay_connected():
break
self.wait_for_device()
self.start_logcat()
self.init_reader()
except KeyboardInterrupt, e:
pass
WAIT_FOR_DEVICE = Fore.WHITE + Back.BLACK + Style.DIM + \
"--- Waiting for device" + Style.RESET_ALL + \
Fore.BLUE + Back.BLACK + Style.DIM + " %s" + Style.RESET_ALL + \
Fore.WHITE + Back.BLACK + Style.DIM + "---" + Style.RESET_ALL
def wait_for_device(self):
command = self.get_adb_args()
command.append("wait-for-device")
device_str = ""
if self.adb_device:
device_str = "\"%s\" " % self.adb_device
print self.WAIT_FOR_DEVICE % device_str
check_call(command)
if __name__ == "__main__":
LogcatColor().loop()