Skip to content

Instantly share code, notes, and snippets.

@samthor
Last active January 29, 2020 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samthor/d5876b6f43fe97526a8aad24ec959366 to your computer and use it in GitHub Desktop.
Save samthor/d5876b6f43fe97526a8aad24ec959366 to your computer and use it in GitHub Desktop.
Better logcat
#!/usr/bin/env python
import logging
import subprocess
import re
import sys
import datetime
RE_SPACES = re.compile('\s+')
RE_LOGCAT = re.compile('^(\w+)/(\w+)\((\d+?)\): (.*)$') # Android 9 or older?
RE_LOGCAT_NEW = re.compile('\s+(\d+)\s+([A-Z])\s+([^:]+): (.*)$')
WIDTH_ALIGN = 32
class colors:
E = '\033[91m'
W = '\033[93m'
I = '\033[92m'
V = '\033[96m'
D = '\033[95m'
def to_string(raw):
# needed for Python 2 vs 3: subprocess returns bytes in 3, str in 2
if type(raw) == str:
return raw
return raw.decode('utf-8') # str(b) doesn't work (it includes literal "b'...'")
def package_pids():
m = {}
output = to_string(subprocess.check_output(['adb', 'shell', 'ps']))
for line in output.split('\n'):
line = str(line)
line = RE_SPACES.sub('\t', line)
parts = line.split()
if len(parts) > 8:
pid, package_id = parts[1], parts[8]
try:
pid = int(pid)
except:
continue
m[pid] = package_id
return m
def ralign(text, width=WIDTH_ALIGN):
text = text.rjust(width)
if len(text) > width:
text = text[-WIDTH_ALIGN:]
if text[3] != '.':
text = '...' + text[3:]
else:
text = '..' + text[2:]
return text
def render(level, tag, pid, package, text):
starter = hasattr(colors, level) and getattr(colors, level) or ''
out = sys.stdout
out.write(ralign(package))
out.write(starter + '\033[1m ')
out.write(level)
out.write(' ')
out.write(tag)
out.write('\033[0m ')
out.write(text)
out.write('\n')
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--plain', dest='plain', action='store_true')
parser.add_argument('-s', '--system', dest='system', action='store_true',
help='show system processes')
parser.add_argument('-i', '--info', dest='info', action='store_true',
help='show level minimum info')
parser.add_argument('packages', metavar='package', type=str, nargs='*',
help='packages to filter to')
parser.set_defaults(style=True)
args = parser.parse_args()
procmap = {}
waituntil = datetime.datetime.now()
p = subprocess.Popen(['adb', 'logcat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
for line in p.stdout:
line = to_string(line)
match = RE_LOGCAT.match(line)
if match:
level, tag, pid, text = match.groups()
else:
match = RE_LOGCAT_NEW.search(line)
if not match:
continue
pid, level, tag, text = match.groups()
pid = int(pid)
# pid not found, update procmap
if pid not in procmap:
now = datetime.datetime.now()
if now > waituntil:
procmap = package_pids()
waituntil = now + datetime.timedelta(seconds=20)
package = procmap.get(pid, '')
# optionally filter debug lines
if args.info:
if level == 'D' or level == 'V':
continue
# optionally filter system processes
if not args.system:
if not package or package.startswith('/') or not '.' in package:
continue
# optionally filter to desired packages
if len(args.packages):
for p in args.packages:
if p in package:
break
else:
continue
# render: pretty or not
if args.plain:
parts = [level, tag, str(pid), package, text]
print('\t'.join(parts))
continue
render(level, tag, pid, package, text)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass # ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment