├── arts
├── demo.png
├── hide-msg-demo.png
├── highlight-demo.png
├── pycharm-build.jpg
├── separate-demo.png
├── trans-msg-demo.png
└── trans-tag-demo.png
├── debug.sh
├── MANIFEST.in
├── .gitignore
├── .idea
├── vcs.xml
├── modules.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── dictionaries
│ └── Jacksgong.xml
├── demo-conf
├── demo-config.yml
├── filedownloader.yml
└── demo-log-file.log
├── .travis.yml
├── okcat
├── logseparator.py
├── helper.py
├── trans.py
├── terminalcolor.py
├── logregex.py
├── logfile_parser.py
├── confloader.py
├── __init__.py
├── logprocessor.py
└── adb.py
├── CHANGELOG.md
├── setup.py
├── README-zh.md
├── README.md
└── LICENSE.txt
/arts/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/demo.png
--------------------------------------------------------------------------------
/debug.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | pip uninstall okcat --yes
3 | python3 setup.py install
4 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # Include the license file
2 | include LICENSE.txt
3 | include README.md
4 |
--------------------------------------------------------------------------------
/arts/hide-msg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/hide-msg-demo.png
--------------------------------------------------------------------------------
/arts/highlight-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/highlight-demo.png
--------------------------------------------------------------------------------
/arts/pycharm-build.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/pycharm-build.jpg
--------------------------------------------------------------------------------
/arts/separate-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/separate-demo.png
--------------------------------------------------------------------------------
/arts/trans-msg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/trans-msg-demo.png
--------------------------------------------------------------------------------
/arts/trans-tag-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacksgong/okcat/HEAD/arts/trans-tag-demo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/workspace.xml
2 | /.idea/misc.xml
3 | /.idea/okcat.iml
4 | /.idea/modules.xml
5 | *.pyc
6 | *.egg-info
7 | /dist
8 | /build
9 | .DS_Store
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo-conf/demo-config.yml:
--------------------------------------------------------------------------------
1 | log-line-regex: 'date,time,process,thread,level,tag,message = "(\S*) *(\S*) *(\d*) *(\d*) ([A-Z]) ([^:]*): (.*?)$"'
2 |
3 | trans-msg-map:
4 | 'Unknown': '未知'
5 |
6 | trans-tag-map:
7 | 'SystemServiceManager': '[System Service Manager]'
8 | 'RescueParty': '[恢复部分]'
9 |
10 | highlight-list:
11 | - 'complete'
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | python:
4 | - "2.7"
5 | - "3.3"
6 | - "3.4"
7 | - "3.5"
8 | - "3.6"
9 | - "3.7"
10 | - "3.8"
11 | - "3.9"
12 |
13 | install:
14 | # - travis_retry pip install okcat
15 | - bash debug.sh
16 | script:
17 | - okcat help
18 | - cd demo-conf
19 | - okcat -y=demo-config demo-log-file.log
20 |
--------------------------------------------------------------------------------
/.idea/dictionaries/Jacksgong.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | dalvik
5 | dest
6 | dreamtobe
7 | ducktype
8 | dumpsys
9 | gids
10 | jacksgong
11 | jakewharton
12 | logcat
13 | okcat
14 | pids
15 | popen
16 | proc
17 | prog
18 | sharkey
19 | stdin
20 | vdiwef
21 |
22 |
23 |
--------------------------------------------------------------------------------
/okcat/logseparator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | import re
19 |
20 | __author__ = 'JacksGong'
21 |
22 |
23 | class LogSeparator:
24 | separator_rex_list = list()
25 | pre_separate_key = None
26 |
27 | def __init__(self, separator_rex_list):
28 | for regex_string in separator_rex_list:
29 | self.separator_rex_list.append(re.compile(r'%s' % regex_string))
30 |
31 | def process(self, msg):
32 | key = None
33 | for regex in self.separator_rex_list:
34 | matched_obj = regex.match(msg)
35 | if matched_obj is not None:
36 | key = matched_obj.groups()[0]
37 | break
38 |
39 | if self.pre_separate_key is None:
40 | if key is None:
41 | key = "unknown"
42 | self.pre_separate_key = key
43 | return key
44 | elif key is not None and self.pre_separate_key != key:
45 | return key
46 | else:
47 | return None
48 |
--------------------------------------------------------------------------------
/okcat/helper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | import re
19 | import sys
20 |
21 | from os import environ, getcwd
22 |
23 | from os.path import exists
24 |
25 | __author__ = 'JacksGong'
26 |
27 | LOG_LEVELS = 'VDIWEF'
28 | LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))])
29 |
30 | NO_HOME_PATH = re.compile(r'~/(.*)')
31 | HOME_PATH = environ['HOME']
32 |
33 |
34 | # get the home case path
35 | def handle_home_case(path):
36 | path = path.strip()
37 | if path.startswith('~/'):
38 | path = HOME_PATH + '/' + NO_HOME_PATH.match(path).groups()[0]
39 | return path
40 |
41 |
42 | def is_path(path):
43 | if path.startswith('/') or path.startswith('~/') or path.startswith('./'):
44 | return True
45 |
46 | if exists(path):
47 | return True
48 | return False
49 |
50 |
51 | def get_conf_path(conf_name):
52 | if not conf_name.endswith('.yml'):
53 | conf_name = conf_name + '.yml'
54 |
55 | cur_path_yml = '%s/%s' % (getcwd(), conf_name)
56 | if exists(cur_path_yml):
57 | result = cur_path_yml
58 | else:
59 | result = '~/.okcat/' + conf_name
60 |
61 | print('using config on %s' % result)
62 | return result
63 |
64 |
65 | def print_unicode(line):
66 | if sys.version_info >= (3, 0):
67 | print(bytes.decode(line))
68 | else:
69 | print(line)
70 |
71 | def line_rstrip(line):
72 | if sys.version_info >= (3, 0):
73 | return line.rstrip()
74 | else:
75 | try:
76 | return line.decode('utf-8').rstrip()
77 | except UnicodeDecodeError:
78 | return line.rstrip()
--------------------------------------------------------------------------------
/okcat/trans.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | """
5 | Copyright (C) 2017 Jacksgong(jacksgong.com)
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | """
19 | from okcat.terminalcolor import colorize, allocate_color, BLACK
20 |
21 | __author__ = 'JacksGong'
22 |
23 |
24 | class Trans:
25 | trans_msg_map = None
26 | trans_tag_map = None
27 | hide_msg_list = None
28 |
29 | def __init__(self, trans_msg_map, trans_tag_map, hide_msg_list):
30 | self.trans_msg_map = trans_msg_map
31 | self.trans_tag_map = trans_tag_map
32 | self.hide_msg_list = hide_msg_list
33 |
34 | def trans_msg(self, msg):
35 | if self.trans_msg_map is None:
36 | return msg
37 |
38 | for key in self.trans_msg_map:
39 | if msg.startswith(key):
40 | value = self.trans_msg_map[key]
41 | return u'| %s | %s' % (colorize(value, fg=allocate_color(value)), msg)
42 |
43 | return msg
44 |
45 | def trans_tag(self, tag, msg):
46 | if self.trans_tag_map is None or tag is None:
47 | return msg
48 |
49 | for key in self.trans_tag_map:
50 | if key in tag:
51 | prefix = self.trans_tag_map[key]
52 | return u'%s %s' % (colorize(prefix, bg=allocate_color(prefix)), msg)
53 |
54 | return msg
55 |
56 | def hide_msg(self, msg):
57 | if self.hide_msg_list is None:
58 | return msg
59 |
60 | # print("get hide msg list: %s and len(%d)" % (self.hide_msg_list, len(msg)))
61 | # if msg.__len__() > 100:
62 | # return msg
63 |
64 | for gray_msg in self.hide_msg_list:
65 | if msg.startswith(gray_msg):
66 | return colorize(msg, fg=BLACK)
67 |
68 | return msg
69 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.4.0
2 |
3 | 2024-02-27
4 |
5 | - Feature: Support `ignore-tag-list` and `ignore-msg-list` configuration to ignore messages by defined
6 |
7 | # 1.3.0
8 |
9 | 2020-08-13
10 |
11 | - Fix: fix on the special timestamp format stackoverflow issue temporary
12 | - Fix: fix decode utf-8 failed
13 |
14 | # 1.1.9
15 |
16 | 2019-08-23
17 |
18 | - Feature: Implement 'from' keyword from config yaml file means append rather than overwrite.
19 |
20 | # 1.1.7
21 |
22 | 2019-01-11
23 |
24 | - Fix: Fix wrong config field on the `log-line-regex` or `adb-log-line-regex` from 'data' to the correct one 'date'
25 |
26 | # 1.1.6
27 |
28 | 2018-06-26
29 |
30 | - Fix: fix encode issue on python3 - by [Ryfthink](https://github.com/Ryfthink)
31 |
32 | # 1.1.5
33 |
34 | 2018-06-24
35 |
36 | - Fix: some dvices adb lost connection, closes #9 - by [Ryfthink](https://github.com/Ryfthink)
37 |
38 | # 1.1.4
39 |
40 | 2018-05-24
41 |
42 | - Feat: support 'from' keyword to let yml config file extends from exist yml file
43 |
44 | # 1.1.3
45 |
46 | 2017-12-01
47 |
48 | - Feat: support combine and parse multiple log-files once time
49 |
50 | # 1.1.2
51 |
52 | 2017-11-17
53 |
54 | - Fix: fix unicode decode error on setup on windows system closes #4
55 |
56 | # 1.1.1
57 |
58 | 2017-10-10
59 |
60 | - Feat: show tips instead of crash when user don't provide config-file name to parse log file. Closes #2
61 |
62 | # 1.1.0
63 |
64 | 2017-09-27
65 |
66 | - Fix: fix import file failed on python 3.x
67 |
68 | # 1.0.9
69 |
70 | 2017-09-16
71 |
72 | - Fix: missing parentheses in call to 'print' error occurred on python 3.x Closes #1
73 |
74 | # 1.0.8
75 |
76 | 2017-09-04
77 |
78 | - Feat: handle the case of adb connection is lost when using adb logcat
79 |
80 | # 1.0.7
81 |
82 | 2017-09-03
83 |
84 | - Enhance: print each line when it has been parsed immediately rather than waiting for parsing whole file to handle case of large log file
85 |
86 | # 1.0.6
87 |
88 | 2017-09-1
89 |
90 | - Fix: cover the case of there is no 'level' keyword on `log-line-regex` case.
91 | - Enhance: add `help` param on okcat, such as `okcat help`.
92 | - Enhance: support `--hide-same-tags` param
93 | - Fix: output all log when `log-line-regex` can't parse
94 | - Fix: handle case of only message is valid
95 | - Fix: fix print non-match content when the log can't match regex
96 | - Fix: fix the default adb regex may wrong for some special case
97 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | from setuptools import setup, find_packages
19 |
20 | # Get the long description from the README file
21 | # noinspection PyArgumentList
22 | setup(
23 | name="OkCat",
24 | version="1.4.0",
25 | packages=find_packages(exclude=['demo-conf', 'arts']),
26 |
27 | # Project uses reStructuredText, so ensure that the docutils get
28 | # installed or upgraded on the target machine
29 | install_requires=['PyYAML>=3.12'],
30 |
31 | # metadata for upload to PyPI
32 | author="Jacksgong",
33 | author_email="igzhenjie@gmail.com",
34 | description="An powerful log processor",
35 | long_description='More detail please move to https://github.com/Jacksgong/okcat',
36 | license="Apache2",
37 | keywords="okcat log 'log processor' 'log filter'",
38 | url="https://github.com/Jacksgong/okcat",
39 |
40 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
41 | classifiers=[
42 | # How mature is this project? Common values are
43 | # 3 - Alpha
44 | # 4 - Beta
45 | # 5 - Production/Stable
46 | 'Development Status :: 5 - Production/Stable',
47 |
48 | # Pick your license as you wish (should match "license" above)
49 | 'License :: OSI Approved :: Apache Software License',
50 |
51 | # Specify the Python versions you support here. In particular, ensure
52 | # that you indicate whether you support Python 2, Python 3 or both.
53 | 'Programming Language :: Python :: 2.7',
54 | 'Programming Language :: Python :: 3',
55 | 'Programming Language :: Python :: 3.3',
56 | 'Programming Language :: Python :: 3.4',
57 | 'Programming Language :: Python :: 3.5',
58 | 'Programming Language :: Python :: 3.6',
59 | 'Programming Language :: Python :: 3.7',
60 | 'Programming Language :: Python :: 3.8',
61 | 'Programming Language :: Python :: 3.9',
62 | ],
63 | entry_points={
64 | 'console_scripts': [
65 | 'okcat=okcat:main'
66 | ]
67 | }
68 | )
69 |
--------------------------------------------------------------------------------
/demo-conf/filedownloader.yml:
--------------------------------------------------------------------------------
1 | # we will filter out logs with the provided package (name)
2 | # this 'package' keyword is just using for android adb logcat
3 | package: com.liulishuo.filedownloader.demo
4 |
5 | # this 'log-line-regex' is just a regex for one line log
6 | # now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'
7 | # you don't have to provide all keyword, but you have to provide at least the 'message'
8 | # such as: 'message="(\S*)"'
9 | log-line-regex: 'date,time,level,tag,process,thread,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
10 |
11 | # on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat
12 | # in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal
13 | # but if you want to customize the regex log from adb logcat, it's free to define it such below
14 | # adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
15 |
16 | # separator regex list
17 | # you can provide multiple regex to separate serial logs
18 | separator-regex-list:
19 | # on this case, if one line log match 'call start Url\[([^\]]*)\]' regex we will separate logs with \n and output a indie line with the '([^\]]*)' value as the title of separate
20 | - 'call start Url\[([^\]]*)\]'
21 |
22 | # tag keyword list
23 | # this list keyword is using for filter out which log need to be output
24 | # all provided keyword will be using for compare with each line tag, if a line with tag not contain any keyword on 'tag-keyword-list' it will be ignore to output
25 | tag-keyword-list:
26 | - 'FileDownloader'
27 |
28 | # translate message map
29 | # if a message on a line start with provide keyword on the 'trans-msg-map' we will add the value of the keyword on the start of the message, and the word of value will be colored to highlight it
30 | trans-msg-map:
31 | # such as this case:
32 | # origin message: 'filedownloader:lifecycle:over xxx'
33 | # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'
34 | 'filedownloader:lifecycle:over': 'Task OVER'
35 | 'fetch data with': 'Start Fetch'
36 |
37 | # translate tag map
38 | # if a tag on a line contain provide keyword on the 'trans-tag-map' we will add the value of the keyword on the start of the message, and the background of the value word will be colored to highlight it
39 | trans-tag-map:
40 | # such as this case:
41 | # origin message: 'FileDownloader.DownloadTaskHunter xxx'
42 | # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'
43 | 'DownloadTaskHunter': '[Status Change]'
44 | 'ConnectTask': '[Request]'
45 |
46 | # hide message list
47 | # if a message on a line start with provide value on the 'hide-msg-list` and the length of the message is less than 100 word, it would be colored with gray to hide
48 | hide-msg-list:
49 | # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case
50 | - 'notify progress'
51 | - '~~~callback'
52 |
53 | # highlight list
54 | # if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it
55 | highlight-list:
56 | - 'Path['
57 | - 'Url['
58 | - 'Tag['
59 | - 'range['
60 |
--------------------------------------------------------------------------------
/okcat/terminalcolor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | """
5 | Copyright (C) 2017 Jacksgong(jacksgong.com)
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | """
19 |
20 | __author__ = 'jacks.gong'
21 |
22 |
23 | class BashColors:
24 | def __init__(self):
25 | pass
26 |
27 | HEADER = '\033[95m'
28 | BLUE = '\033[94m'
29 | GREEN = '\033[92m'
30 | WARNING = '\033[93m'
31 | FAIL = '\033[91m'
32 | END = '\033[0m'
33 | BOLD = '\033[1m'
34 | UNDERLINE = '\033[4m'
35 |
36 |
37 | def print_header(msg):
38 | print(BashColors.HEADER + msg + BashColors.END)
39 |
40 |
41 | def print_exit(msg):
42 | print(BashColors.FAIL + msg + BashColors.END)
43 |
44 |
45 | def print_error(msg):
46 | print(BashColors.FAIL + msg + BashColors.END)
47 |
48 |
49 | def print_tips(msg):
50 | print(BashColors.UNDERLINE + msg + BashColors.END)
51 |
52 |
53 | def print_warn(msg):
54 | print(BashColors.WARNING + msg + BashColors.END)
55 |
56 |
57 | def print_key(msg):
58 | print(BashColors.GREEN + msg + BashColors.END)
59 |
60 |
61 | def print_blue(msg):
62 | print(BashColors.BLUE + msg + BashColors.END)
63 |
64 |
65 | def print_content_tips(msg):
66 | msg = BashColors.UNDERLINE + msg + BashColors.END
67 | print(msg)
68 | return msg + "\n"
69 |
70 |
71 | def print_content_header(msg):
72 | msg = BashColors.HEADER + msg + BashColors.END
73 | print_content(msg)
74 | return msg + "\n"
75 |
76 |
77 | def print_content(msg):
78 | print(msg)
79 | return msg
80 |
81 |
82 | # -------------- color -----------------------------
83 | RESET = '\033[0m'
84 | BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
85 |
86 |
87 | def termcolor(fg=None, bg=None):
88 | codes = []
89 | if fg is not None: codes.append('3%d' % fg)
90 | if bg is not None: codes.append('10%d' % bg)
91 | return '\033[%sm' % ';'.join(codes) if codes else ''
92 |
93 |
94 | def colorize(message, fg=None, bg=None):
95 | if fg is None:
96 | if bg == BLACK:
97 | fg = WHITE
98 | else:
99 | fg = BLACK
100 |
101 | return termcolor(fg, bg) + message + RESET
102 |
103 |
104 | TAGTYPES = {
105 | 'V': colorize(' V ', fg=WHITE, bg=BLACK),
106 | 'D': colorize(' D ', fg=BLACK, bg=BLUE),
107 | 'I': colorize(' I ', fg=BLACK, bg=GREEN),
108 | 'W': colorize(' W ', fg=BLACK, bg=YELLOW),
109 | 'E': colorize(' E ', fg=BLACK, bg=RED),
110 | 'F': colorize(' F ', fg=BLACK, bg=RED),
111 | }
112 |
113 | # for random color
114 | KNOWN_TAGS = {
115 | 'dalvikvm': WHITE,
116 | 'Process': WHITE,
117 | 'ActivityManager': WHITE,
118 | 'ActivityThread': WHITE,
119 | 'AndroidRuntime': CYAN,
120 | 'jdwp': WHITE,
121 | 'StrictMode': WHITE,
122 | 'DEBUG': YELLOW,
123 | }
124 | LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
125 | LOG_FLOW_KEY_LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
126 |
127 |
128 | def allocate_color(_key, loop_color=LAST_USED):
129 | if _key not in KNOWN_TAGS:
130 | KNOWN_TAGS[_key] = loop_color[0]
131 |
132 | _color = KNOWN_TAGS[_key]
133 | if _color in LAST_USED:
134 | loop_color.remove(_color)
135 | loop_color.append(_color)
136 | return _color
137 |
--------------------------------------------------------------------------------
/okcat/logregex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 |
19 | import re
20 |
21 | from okcat.terminalcolor import print_warn
22 |
23 | __author__ = 'jacks.gong'
24 |
25 | # val = 'date,time,process,thread,level,tag,message = "(.\S*) (.\S*) (\d*) (\d*) ([V|I|D|W|E]) ([^:]*): (.*)"'
26 | REGEX_EXP_RE = re.compile(r"([^ =]*) *= *[\"|'](.*)[\"|']")
27 | ALL_SUPPORT_KEY = ["date", "time", "process", "thread", "level", "tag", "message"]
28 | DEPRECATED_DATE_KEY = "data"
29 |
30 | class LogRegex:
31 | key_order = list()
32 |
33 | regex = None
34 |
35 | def __init__(self, regex_exp):
36 | keys, regex = REGEX_EXP_RE.match(regex_exp).groups()
37 | process_key_order = keys.split(',')
38 |
39 | self.regex = re.compile(r"%s" % regex)
40 | for key in process_key_order:
41 | key = key.strip()
42 | if key in ALL_SUPPORT_KEY:
43 | self.key_order.append(key)
44 | elif key == DEPRECATED_DATE_KEY:
45 | print_warn("please change 'data' to 'date' because this wrong word has been fixed on the current version, for the temporary we treat it as 'date'")
46 | self.key_order.append('date')
47 | else:
48 | print_warn("not support key[%s] only support: %s" % (key, ALL_SUPPORT_KEY))
49 |
50 | print("find regex: " + self.key_order.__str__() + " with " + regex)
51 |
52 | def parse(self, line):
53 | date = None
54 | time = None
55 | process = None
56 | thread = None
57 | level = None
58 | tag = None
59 | message = None
60 |
61 | values = self.regex.match(line)
62 |
63 | if values is None:
64 | return date, time, level, tag, process, thread, message
65 |
66 | # print(values.groups().__str__())
67 | i = 0
68 | for value in values.groups():
69 | key = self.key_order[i]
70 | i += 1
71 | if key == "date":
72 | date = value
73 | elif key == "time":
74 | time = value
75 | elif key == "process":
76 | process = value
77 | elif key == "thread":
78 | thread = value
79 | elif key == "level":
80 | level = value
81 | elif key == "tag":
82 | tag = value
83 | elif key == "message":
84 | message = value
85 |
86 | return date, time, level, tag, process, thread, message
87 |
88 | contain_date = None
89 | contain_time = None
90 | contain_thread = None
91 | contain_tag = None
92 | contain_level = None
93 |
94 | def is_contain_date(self):
95 | if self.contain_date is None:
96 | self.contain_date = self.is_contain_key("date")
97 | return self.contain_date
98 |
99 | def is_contain_time(self):
100 | if self.contain_time is None:
101 | self.contain_time = self.is_contain_key("time")
102 | return self.contain_time
103 |
104 | def is_contain_thread(self):
105 | if self.contain_thread is None:
106 | self.contain_thread = self.is_contain_key("thread")
107 | return self.contain_thread
108 |
109 | def is_contain_tag(self):
110 | if self.contain_tag is None:
111 | self.contain_tag = self.is_contain_key("tag")
112 | return self.contain_tag
113 |
114 | def is_contain_level(self):
115 | if self.contain_level is None:
116 | self.contain_level = self.is_contain_key("level")
117 | return self.contain_level
118 |
119 | def is_contain_key(self, key):
120 | return key in self.key_order
121 |
--------------------------------------------------------------------------------
/okcat/logfile_parser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | import re
19 | from os.path import exists
20 |
21 | from okcat.confloader import ConfLoader
22 | from okcat.helper import get_conf_path, print_unicode
23 | from okcat.logprocessor import LogProcessor
24 | from okcat.terminalcolor import colorize, allocate_color
25 |
26 | TIME_REGEX = r'\d{2}-\d{2} .*\d{2}:\d{2}:\d{2}\.\d+'
27 |
28 |
29 | class LogFileParser:
30 | filePaths = []
31 | valid = False
32 | processor = None
33 | hideSameTags = None
34 | logStreams = []
35 | cacheLines = []
36 | lineTimes = []
37 |
38 | def __init__(self, file_paths, hide_same_tags):
39 | self.filePaths = file_paths
40 | self.hideSameTags = hide_same_tags
41 |
42 | def setup(self, yml_file_name):
43 | for path in self.filePaths:
44 | if not exists(path):
45 | exit("log path: %s is not exist!" % path)
46 | self.processor = LogProcessor(self.hideSameTags)
47 |
48 | loader = ConfLoader()
49 | loader.load(get_conf_path(yml_file_name))
50 |
51 | self.processor.setup_trans(trans_msg_map=loader.get_trans_msg_map(),
52 | trans_tag_map=loader.get_trans_tag_map(),
53 | hide_msg_list=loader.get_hide_msg_list())
54 | self.processor.setup_separator(separator_rex_list=loader.get_separator_regex_list())
55 | self.processor.setup_highlight(highlight_list=loader.get_highlight_list())
56 | self.processor.setup_condition(tag_keywords=loader.get_tag_keyword_list())
57 | log_line_regex = loader.get_log_line_regex()
58 | if log_line_regex is None:
59 | log_line_regex = 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): (.*?)$"'
60 | print("you don't provide 'log_line-regex' for parse each line on file, so we use this one as default:")
61 | print(log_line_regex + "\n")
62 | self.processor.setup_regex_parser(regex_exp=log_line_regex)
63 |
64 | def color_line(self, line):
65 | msg_key, line_buf, match_precondition = self.processor.process(line)
66 |
67 | if not match_precondition:
68 | return
69 |
70 | if msg_key is not None:
71 | print('')
72 | print_unicode(u''.join(colorize(msg_key + ": ", fg=allocate_color(msg_key))).encode('utf-8').lstrip())
73 |
74 | print_unicode(u''.join(line_buf).encode('utf-8').lstrip())
75 |
76 | def popup_cache_line(self, popup_index):
77 | need_read_stream = self.logStreams[popup_index]
78 | new_line = need_read_stream.readline()
79 | if new_line:
80 | match_result = re.search(TIME_REGEX, new_line)
81 | if match_result:
82 | self.lineTimes.insert(popup_index, match_result.group())
83 | self.cacheLines.insert(popup_index, new_line)
84 | else:
85 | self.color_line(new_line)
86 | self.popup_cache_line(popup_index)
87 | else:
88 | need_read_stream.close()
89 | self.logStreams.pop(popup_index)
90 |
91 | def process(self):
92 | origin_index = 0
93 | for path in self.filePaths:
94 | stream = open(path, "r")
95 | self.logStreams.append(stream)
96 | self.popup_cache_line(origin_index)
97 | origin_index += 1
98 |
99 | while self.cacheLines:
100 | min_index = self.lineTimes.index(min(self.lineTimes))
101 | self.lineTimes.pop(min_index)
102 | selected_line = self.cacheLines.pop(min_index)
103 | self.color_line(selected_line)
104 | self.popup_cache_line(min_index)
105 |
--------------------------------------------------------------------------------
/okcat/confloader.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | """
5 | Copyright (C) 2017 Jacksgong(jacksgong.com)
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | """
19 |
20 | import yaml
21 |
22 | from okcat.helper import handle_home_case, get_conf_path
23 |
24 | __author__ = 'JacksGong'
25 |
26 |
27 | class ConfLoader:
28 | yml_conf = None
29 | from_yml_conf = None
30 |
31 | def __init__(self):
32 | pass
33 |
34 | def load(self, yml_file_path):
35 | with open(handle_home_case(yml_file_path), 'r') as stream:
36 | try:
37 | self.yml_conf = yaml.load(stream, yaml.SafeLoader)
38 | from_conf_path = self.get_from()
39 | if from_conf_path is not None:
40 | self.from_yml_conf = ConfLoader()
41 | self.from_yml_conf.load(get_conf_path(from_conf_path))
42 |
43 | # print(u'find yml configuration on %s:' % yml_file_path)
44 | # self.dump()
45 |
46 | except yaml.YAMLError as exc:
47 | print(exc)
48 |
49 | def get_from(self):
50 | return self.get_value('from')
51 |
52 | def get_package(self):
53 | return self.get_value('package')
54 |
55 | def get_tag_keyword_list(self):
56 | return self.get_value('tag-keyword-list')
57 |
58 | def get_trans_msg_map(self):
59 | return self.get_value('trans-msg-map')
60 |
61 | def get_trans_tag_map(self):
62 | return self.get_value('trans-tag-map')
63 |
64 | def get_hide_msg_list(self):
65 | return self.get_value('hide-msg-list')
66 |
67 | def get_ignore_msg_list(self):
68 | return self.get_value('ignore-msg-list')
69 |
70 | def get_ignore_tag_list(self):
71 | return self.get_value('ignore-tag-list')
72 |
73 | def get_highlight_list(self):
74 | return self.get_value('highlight-list')
75 |
76 | def get_log_line_regex(self):
77 | return self.get_value('log-line-regex')
78 |
79 | def get_adb_log_line_regex(self):
80 | return self.get_value('adb-log-line-regex')
81 |
82 | def get_separator_regex_list(self):
83 | return self.get_value('separator-regex-list')
84 |
85 | def get_value(self, keyword):
86 | if keyword not in self.yml_conf or self.yml_conf[keyword] is None:
87 | if keyword != 'from' and self.from_yml_conf is not None:
88 | return self.from_yml_conf.get_value(keyword)
89 | else:
90 | return None
91 |
92 | origin = self.yml_conf[keyword]
93 | if self.from_yml_conf is not None and self.from_yml_conf.get_value(keyword) is not None:
94 | values = self.from_yml_conf.get_value(keyword)
95 | if type(origin) == list:
96 | origin.extend(values)
97 | if type(origin) == dict:
98 | origin.update(values)
99 |
100 | return origin
101 |
102 | def dump(self):
103 | print('from: %s' % self.get_from())
104 | print('package: %s' % self.get_package())
105 | print('log-line-regex: %s' % self.get_log_line_regex())
106 | print('adb-log-line-regex: %s' % self.get_adb_log_line_regex())
107 | self.dump_list('tag-keyword-list')
108 | self.dump_unicode_map('trans-msg-map')
109 | self.dump_unicode_map('trans-tag-map')
110 | self.dump_list('hide-msg-list')
111 | self.dump_list('ignore-msg-list')
112 | self.dump_list('ignore-tag-list')
113 | self.dump_list('highlight-list')
114 | self.dump_list('separator-regex-list')
115 |
116 | def dump_unicode_map(self, map_key):
117 | unicode_map = self.get_value(map_key)
118 | if unicode_map is None:
119 | print('%s: None' % map_key)
120 | else:
121 | print('%s:' % map_key)
122 | for key in unicode_map:
123 | print(u' "%s" : "%s"' % (key, unicode_map[key]))
124 |
125 | def dump_list(self, list_key):
126 | cur_list = self.get_value(list_key)
127 | if cur_list is None:
128 | print('%s: None' % list_key)
129 | else:
130 | print('%s: ' % list_key)
131 | for value in cur_list:
132 | print(' - %s' % value)
133 |
--------------------------------------------------------------------------------
/okcat/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | import argparse
19 | from sys import argv
20 |
21 | from okcat.adb import Adb
22 | from okcat.helper import LOG_LEVELS, is_path
23 | from okcat.logfile_parser import LogFileParser
24 | from okcat.terminalcolor import print_tips, print_blue, print_warn, print_header, print_exit
25 |
26 | __author__ = 'JacksGong'
27 | __version__ = '1.4.0'
28 | __description__ = 'This python script used for combine several Android projects to one project.'
29 |
30 |
31 | def main():
32 | print("-------------------------------------------------------")
33 | print(" OkCat v" + __version__)
34 | print("")
35 | print("Thanks for using okcat! Now, the doc is available on: ")
36 | print_blue(" https://github.com/Jacksgong/okcat")
37 | print("")
38 | print(" Have Fun!")
39 | print("-------------------------------------------------------")
40 |
41 | parser = argparse.ArgumentParser(description='Filter logcat by package name')
42 | parser.add_argument('package_or_path', nargs='*',
43 | help='This can be Application package name(s) or log file path(if the file from path is exist)')
44 | parser.add_argument('-y', '--yml_file_name', dest='yml', help='Using yml file you config on ~/.okcat folder')
45 | parser.add_argument('--hide-same-tags', dest='hide_same_tags', action='store_true',
46 | help='Do not display the same tag name')
47 |
48 | # following args are just for parser
49 | # parser.add_argument('-k', '--keyword', dest='keyword', action='append', help='You can filter you care about log by this keyword(s)')
50 |
51 | # following args are just for adb
52 | parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23,
53 | help='Width of log tag')
54 | parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS + LOG_LEVELS.lower(),
55 | default='V', help='Minimum level to be displayed')
56 | parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection')
57 | parser.add_argument('--current', dest='current_app', action='store_true',
58 | help='Filter logcat by current running app')
59 | parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)')
60 | parser.add_argument('-d', '--device', dest='use_device', action='store_true',
61 | help='Use first device for log input (adb -d option)')
62 | parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true',
63 | help='Use first emulator for log input (adb -e option)')
64 | parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true',
65 | help='Clear the entire log before running')
66 | parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)')
67 | parser.add_argument('-tk', '--tag_keywords', dest='tag_keywords', action='append',
68 | help='Filter output by specified tag keyword(s)')
69 | parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append',
70 | help='Filter output by ignoring specified tag(s)')
71 | parser.add_argument('-a', '--all', dest='all', action='store_true', default=False,
72 | help='Print all log messages')
73 |
74 | # help
75 | if len(argv) == 2 and argv[1] == 'help':
76 | exit()
77 |
78 | args = parser.parse_args()
79 |
80 | file_paths = []
81 | candidate_path = args.package_or_path
82 | for path in candidate_path:
83 | if is_path(path):
84 | file_paths.append(path)
85 |
86 | if file_paths:
87 | if args.yml is None:
88 | print("")
89 | print_exit("Please using '-y=conf-name' to provide config file to parse this log file.")
90 | print("The config file is very very simple! More detail about config file please move to : https://github.com/Jacksgong/okcat")
91 | print("")
92 | print("-------------------------------------------------------")
93 | exit()
94 |
95 | parser = LogFileParser(file_paths, args.hide_same_tags)
96 | parser.setup(args.yml)
97 | parser.process()
98 | else:
99 | is_interrupt_by_user = False
100 |
101 | _adb = Adb()
102 | _adb.setup(args)
103 | try:
104 | _adb.loop()
105 | except KeyboardInterrupt:
106 | is_interrupt_by_user = True
107 |
108 | if not is_interrupt_by_user:
109 | print_warn('ADB CONNECTION IS LOST.')
110 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # OkCat
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | [](https://github.com/Jacksgong/okcat)
9 | [](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)
10 | [](https://pypi.org/project/OkCat/1.4.0/)
11 | [](https://travis-ci.org/Jacksgong/okcat)
12 |
13 | 强大的日志处理组件。
14 |
15 | [English Doc](https://github.com/Jacksgong/okcat)
16 |
17 | - 你可以定义任意的日志正则表达式,来适配任意格式的日志,可以将其用于iOS、Android、后端等等。
18 | - ADB Logcat部分是基于JakeWharton的PID Cat,并且适配了各类OkCat的新特性 。
19 |
20 | Andrdoid工程师查看ADB Logcat最简单的使用:
21 |
22 | ```shell
23 | okcat 包名
24 | ```
25 |
26 | ## 特性
27 |
28 | > 最主要的特性是:你可以为不同的日志定义自己的正则表达式,以此适配各种类型的日志处理。
29 |
30 | - 高亮一些关键字
31 | 
32 | - 转译日志内容
33 | 
34 | - 转译Tag
35 | 
36 | - 隐藏一些日志
37 | 
38 | - 对连续的日志进行分割
39 | 
40 | - 忽略符合规则日志
41 | `以提供的字段开头的日志将会被直接忽略` 或者 `满足以提供的TAG的日志将会被直接忽略`
42 |
43 | ## 如何安装
44 |
45 | ```shell
46 | sudo pip install okcat
47 | ```
48 |
49 | 如果你还没有安装`pip`,你需要先安装`pip`:
50 |
51 | 1. `brew install python`
52 | 2. `sudo easy_install pip`
53 |
54 | 如果你想要升级:
55 |
56 | ```
57 | sudo pip install okcat --upgrade
58 | ```
59 |
60 | ## 如何使用
61 |
62 | ---
63 |
64 | #### 最简单的测试
65 |
66 | 1. 下载[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)在当前目录,或者移动到`~/.okcat/`目录中
67 | 3. 运行这个[Filedownloader-Demo](https://github.com/lingochamp/FileDownloader)项目中的demo项目,并运行到你的Android手机上,然后将手机连接电脑
68 | 4. 执行: `okcat -y=filedownloader`
69 | 5. 此时日志就会根据[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)的配置输出了
70 |
71 | 
72 |
73 | ---
74 |
75 | #### 1. 定义你的配置文件(`.yml`)
76 |
77 | 你可以在`~/.okcat/`目录下创建你的yaml格式的配置文件,如果`~/.okcat`文件夹不存在先创建该文件夹;当然也可以直接在执行命令的当前目录创建yaml格式的配置文件。
78 | 文件名字可以是任何你想要的名字,在执行`okcat`的时候可以通过`-y=文件名`的形式,告知okcat想要应用的是哪个文件名的配置文件,okcat会默认在当前目录找,找不到会在`~/.okcat`目录下进行查找。
79 |
80 | 下面是配置文件的案例,里面列出了目前支持的所有的配置,当然你不需要配置所有的特性,只需要配置你需要的即可。
81 |
82 | ```yml
83 | # 继承存在的其他yml的配置(不需要`.yml`后缀)
84 | from: exist-yml-file-name
85 |
86 | # 定义连线手机进行ADB处理时,需要过滤的包名;
87 | # 如果不使用Android的ADB功能,便不需要配置
88 | package: com.liulishuo.filedownloader.demo
89 |
90 | # 配置对于一行日志的正则表达式,目前支持正则出date、time、level、tag、process、thread、message
91 | # 不过不一定要全部提供,至少需要提供一个message
92 | # 如log-line-regex: 'message="(.\S*)"'
93 | log-line-regex: 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
94 |
95 | # 在Android的ADB的情况下,我们是使用adb logcat -v brief -v threadtime
96 | # 一般情况下不需要adb-log-line-regex配置,我们已经有很完善的这块的正则,但是如果对这个需要特别定制便可以使用以下定制
97 | # adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
98 |
99 | # 分割正则列表
100 | # 可以提供多个正则表达式,对日志进行分割
101 | separator-regex-list:
102 | # 对满足以下正则的那行日志开始进行分割,并且以([^\]]*)的内容作为分割的标题
103 | - 'call start Url\[([^\]]*)\]'
104 |
105 | # 标签关键字
106 | # 如果不提供tag-keyword-list将会显示所有日志
107 | # 如果如下提供了tag-keyword-list将会过滤日志,只显示tag中包含了这里列出关键字的日志
108 | tag-keyword-list:
109 | - 'FileDownloader'
110 |
111 | # 内容转译表
112 | # 如果日志message中由表中key开头,将会使用彩色的文字在该message开头加上表中的value
113 | trans-msg-map:
114 | # 如这个例子:
115 | # 原message: 'filedownloader:lifecycle:over xxx'
116 | # 转译后: '| 任务结束 | filedownloader:lifecycle:over xxx' 其中的'任务结束'会使用彩色的文字显示
117 | 'filedownloader:lifecycle:over': '任务结束'
118 | 'fetch date with': '开始拉取'
119 |
120 | # 标签转译表
121 | # 如果日志tag中包含表中key开头,将会使用彩色背景的文字在该message开头加上表中的value
122 | trans-tag-map:
123 | # 如这个例子:
124 | # 原输出: 'FileDownloader.DownloadTaskHunter xxx'
125 | # 转译后: 'FileDownloader.DownloadTaskHunter [状态切换] xxx' 其中'[状态切换]'会使用彩色背景
126 | 'DownloadTaskHunter': '[状态切换]'
127 | 'ConnectTask': '[请求]'
128 |
129 | # 隐藏消息列表
130 | # 对以以下内容开头并且message长度小于100的内功进行灰色显示处理,在视觉上进行隐藏
131 | hide-msg-list:
132 | # 这里案例因为心跳日志是非常频繁的日志,通常没有什么问题,因此将其着灰色
133 | - 'notify progress'
134 | - '~~~callback'
135 |
136 | # 高亮列表
137 | # 对message中的以下内容,背景进行彩色处理使其高亮
138 | highlight-list:
139 | - 'Path['
140 | - 'Url['
141 | - 'Tag['
142 | - 'range['
143 |
144 | # 忽略日志列表
145 | # 以提供的字段开头的日志将会被直接忽略
146 | ignore-msg-list:
147 | - 'log start with this will be ignored'
148 |
149 | # 忽略日志TAG列表
150 | # 当所在日志的 TAG 在该列表中时会被直接忽略
151 | ignore-tag-list:
152 | - 'tagToBeIgnored'
153 | ```
154 |
155 | #### 2. 执行
156 |
157 | > okcat的使用非常的简单。
158 |
159 | 如果你需要处理运行中App在Logcat的输出,只需要执行:
160 |
161 | ```shell
162 | okcat -y=your-conf-name
163 | ```
164 |
165 | 如果你需要解析任意格式的日志,只需要执行:
166 |
167 | ```shell
168 | okcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ...
169 | ```
170 |
171 | > 小技巧: 你在终端中使用`Command + K`来刷新当前回话中的所有内容,以此快速启动新的okcat解析,而不用再另外创建一个新的会话。
172 |
173 | ## 我的终端的风格配置
174 |
175 | 如果你想要适配和上面截图一样的终端风格,非常简单:
176 |
177 | - 首先,请使用[powerlevel9k](https://github.com/bhilburn/powerlevel9k)主题(正如Powerlevel9k文档提到的安装Powerlevel9k主题,并且安装Powerline字体).
178 | - 其次,请配置[iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron)色系.
179 | - 最后, 请配置ini的shell(如果你使用的是zsh,只需要添加下列代码到`~/.zshrc`文件中):
180 | ```
181 | POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
182 | POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)
183 | POWERLEVEL9K_TIME_FORMAT="%D{%H:%M:%S}"
184 | POWERLEVEL9K_NODE_VERSION_BACKGROUND='022'
185 | POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
186 | ```
187 |
188 | ## LICENSE
189 |
190 | ```
191 | Copyright (C) 2017 Jacksgong(jacksgong.com)
192 |
193 | Licensed under the Apache License, Version 2.0 (the "License");
194 | you may not use this file except in compliance with the License.
195 | You may obtain a copy of the License at
196 |
197 | http://www.apache.org/licenses/LICENSE-2.0
198 |
199 | Unless required by applicable law or agreed to in writing, software
200 | distributed under the License is distributed on an "AS IS" BASIS,
201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202 | See the License for the specific language governing permissions and
203 | limitations under the License.
204 | ```
205 |
--------------------------------------------------------------------------------
/okcat/logprocessor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | import re
19 |
20 | from okcat.helper import line_rstrip
21 | from okcat.logregex import LogRegex
22 | from okcat.logseparator import LogSeparator
23 | from okcat.terminalcolor import allocate_color, colorize, TAGTYPES, termcolor, BLACK, RESET
24 | from okcat.trans import Trans
25 |
26 | __author__ = 'JacksGong'
27 |
28 | TIME_WIDTH = 12
29 | THREAD_WIDTH = 12
30 | TAG_WIDTH = 23
31 |
32 | width = -1
33 | # noinspection PyBroadException
34 | try:
35 | # Get the current terminal width
36 | import fcntl, termios, struct
37 |
38 | h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0)))
39 | except:
40 | pass
41 |
42 | header_size = TAG_WIDTH + 1 + 3 + 1 # space, level, space
43 |
44 |
45 | def indent_wrap(message):
46 | return message
47 |
48 |
49 | def keywords_regex(content, keywords):
50 | return any(re.match(r'.*' + t + r'.*', content) for t in map(str.strip, keywords))
51 |
52 |
53 | class LogProcessor:
54 | hide_same_tags = None
55 | trans = None
56 | tag_keywords = None
57 | line_keywords = None
58 | separator = None
59 | regex_parser = None
60 | highlight_list = None
61 | # target_time = None
62 |
63 | ignore_msg_list = None
64 | ignore_tag_list = None
65 |
66 | # tmp
67 | last_msg_key = None
68 | last_tag = None
69 | pre_line_match = True
70 |
71 | def __init__(self, hide_same_tags):
72 | self.hide_same_tags = hide_same_tags
73 |
74 | def setup_trans(self, trans_msg_map, trans_tag_map, hide_msg_list):
75 | self.trans = Trans(trans_msg_map, trans_tag_map, hide_msg_list)
76 |
77 | def setup_ignore(self, ignore_msg_list, ignore_tag_list):
78 | self.ignore_msg_list = ignore_msg_list
79 | self.ignore_tag_list = ignore_tag_list
80 |
81 | def setup_separator(self, separator_rex_list):
82 | if separator_rex_list is not None:
83 | self.separator = LogSeparator(separator_rex_list)
84 |
85 | def setup_highlight(self, highlight_list):
86 | self.highlight_list = highlight_list
87 |
88 | def setup_condition(self, tag_keywords, line_keywords=None):
89 | self.tag_keywords = tag_keywords
90 | self.line_keywords = line_keywords
91 |
92 | def setup_regex_parser(self, regex_exp):
93 | self.regex_parser = LogRegex(regex_exp)
94 |
95 | def process(self, origin_line):
96 | origin_line = line_rstrip(origin_line)
97 |
98 | if len(origin_line.strip()) <= 0:
99 | return None, None, False
100 |
101 | if self.regex_parser is None:
102 | return None, None, False
103 |
104 | date, time, level, tag, process, thread, message = self.regex_parser.parse(origin_line)
105 | if message is None:
106 | message = origin_line
107 |
108 | return self.process_decode_content(origin_line, time, level, tag, process, thread, message)
109 |
110 | # noinspection PyUnusedLocal
111 | def process_decode_content(self, line, time, level, tag, process, thread, message):
112 |
113 | match_condition = True
114 |
115 | # filter
116 | if self.tag_keywords is not None and tag is not None:
117 | if not keywords_regex(tag, self.tag_keywords):
118 | match_condition = False
119 | self.pre_line_match = False
120 | else:
121 | self.pre_line_match = True
122 |
123 |
124 |
125 | if self.line_keywords is not None:
126 | if not keywords_regex(line, self.line_keywords):
127 | match_condition = False
128 | self.pre_line_match = False
129 | else:
130 | self.pre_line_match = True
131 |
132 | if match_condition and tag is None and not self.pre_line_match:
133 | match_condition = False
134 |
135 | # if 'special world' in line:
136 | # match_precondition = True
137 |
138 | if self.ignore_msg_list is not None:
139 | for ignore_msg in self.ignore_msg_list:
140 | if message.startswith(ignore_msg):
141 | match_condition = False
142 |
143 | if self.ignore_tag_list is not None:
144 | if tag in self.ignore_tag_list:
145 | match_condition = False
146 |
147 | if not match_condition:
148 | return None, None, None
149 |
150 | msgkey = None
151 | # the handled current line
152 | linebuf = ''
153 |
154 | # time
155 | if time is not None:
156 | time = time[-TIME_WIDTH:].rjust(TIME_WIDTH)
157 | linebuf += time
158 | linebuf += ' '
159 | elif self.regex_parser.is_contain_time():
160 | linebuf += ' ' * TIME_WIDTH
161 | linebuf += ' '
162 |
163 | # thread
164 | if thread is not None:
165 | thread = thread.strip()
166 | thread = thread[-THREAD_WIDTH:].rjust(THREAD_WIDTH)
167 | linebuf += thread
168 | linebuf += ' '
169 | elif self.regex_parser.is_contain_thread():
170 | linebuf += ' ' * THREAD_WIDTH
171 | linebuf += ' '
172 |
173 | # tag
174 | if tag is not None and (not self.hide_same_tags or tag != self.last_tag):
175 | self.last_tag = tag
176 | tag = tag.strip()
177 | color = allocate_color(tag)
178 | tag = tag.strip()
179 | tag = tag[-TAG_WIDTH:].rjust(TAG_WIDTH)
180 | linebuf += colorize(tag, fg=color)
181 | linebuf += ' '
182 | elif self.regex_parser.is_contain_tag():
183 | linebuf += ' ' * TAG_WIDTH
184 | linebuf += ' '
185 |
186 | # level
187 | if level is not None:
188 | if level in TAGTYPES:
189 | linebuf += TAGTYPES[level]
190 | else:
191 | linebuf += ' ' + level + ' '
192 | linebuf += ' '
193 | elif self.regex_parser.is_contain_level():
194 | linebuf += ' '
195 | linebuf += ' '
196 |
197 | # message
198 | # -separator
199 | if self.separator is not None:
200 | msgkey = self.separator.process(message)
201 |
202 | # -trans
203 | if self.trans is not None:
204 | message = self.trans.trans_msg(message)
205 | message = self.trans.hide_msg(message)
206 | message = self.trans.trans_tag(tag, message)
207 |
208 | if self.highlight_list is not None:
209 | for highlight in self.highlight_list:
210 | if highlight in message:
211 | message = message.replace(highlight,
212 | termcolor(fg=BLACK, bg=allocate_color(highlight)) + highlight + RESET)
213 |
214 | linebuf += message
215 |
216 | return msgkey, linebuf, match_condition
217 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OkCat
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | [](https://github.com/Jacksgong/okcat)
9 | [](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)
10 | [](https://pypi.org/project/OkCat/1.4.0/)
11 | [](https://travis-ci.org/Jacksgong/okcat)
12 |
13 | An powerful log processor.
14 |
15 | [中文文档](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)
16 |
17 | - The adb logcat handler is just update to JakeWharton's nice pidcat and I adapt it for more features.
18 | - You can using this log processor with define you own `log-line-regex` and it can work for any log: iOS, Android, Backend, etc.
19 |
20 | ## Features
21 |
22 | > The most important feature is you can define any regex for any kind of log.
23 |
24 | - highlight some keywords
25 | 
26 | - trans msgs to some words
27 | 
28 | - trans tags to some words
29 | 
30 | - hide msg on logs
31 | 
32 | - separate logs
33 | 
34 | - ignore msg on logs:
35 | `when you provide such list, the msg start with provided msg will be ignored to printed`
36 | - ignore tag on logs:
37 | `when you provide such list, the tag in the list will be ignored to printed`
38 |
39 | ## How to Install
40 |
41 | ```shell
42 | sudo pip install okcat
43 | ```
44 |
45 | If you has not installed `pip` yet, you need to install it first:
46 |
47 | 1. `brew install python`
48 | 2. `sudo easy_install pip`
49 |
50 | If you want to upgrade:
51 |
52 | ```shell
53 | sudo pip install okcat --upgrade
54 | ```
55 |
56 | ## How to Use
57 |
58 | ---
59 |
60 | #### Simplest test
61 |
62 | 1. Download: download [filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml) to the current folder or move to the `~/.okcat/` folder
63 | 2. Running: run the demo project on [Filedownloader](https://github.com/lingochamp/FileDownloader) repo to your Android phone and connect your Phone to computer
64 | 3. Execute: `okcat -y=filedownloader`
65 | 4. Done: now, you can checkout the colored logs on terminal, enjoy~
66 |
67 | 
68 |
69 | ---
70 |
71 | #### 1. Define your config file(`.yml`)
72 |
73 | You can create your own `.yaml` file as config file on `~/.okcat/` folder or the current folder you will execute `okcat` command, and the filename is free to choose, when you execute the okcat, we will ask you the configure file name you want to apply.
74 |
75 | the following is demo of config file, Of course, you don't have to provide all configs such below, if you think which one is needed, just config that one.
76 |
77 | ```yml
78 | # extends from exist yml file (provide only filename without `.yml` extension)
79 | # from: exist-yml-name
80 |
81 | # we will filter out logs with the provided package (name)
82 | # this 'package' keyword is just using for android adb logcat
83 | package: com.liulishuo.filedownloader.demo
84 |
85 | # this 'log-line-regex' is just a regex for one line log
86 | # now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'
87 | # you don't have to provide all keyword, but you have to provide at least the 'message'
88 | # such as: 'message="(\S*)"'
89 | log-line-regex: 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
90 |
91 | # on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat
92 | # in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal
93 | # but if you want to customize the regex log from adb logcat, it's free to define it such below
94 | # adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
95 |
96 | # separator regex list
97 | # you can provide multiple regex to separate serial logs
98 | separator-regex-list:
99 | # on this case, if one line log match 'call start Url\[([^\]]*)\]' regex we will separate logs with \n and output a indie line with the '([^\]]*)' value as the title of separate
100 | - 'call start Url\[([^\]]*)\]'
101 |
102 | # tag keyword list
103 | # this list keyword is using for filter out which log need to be output
104 | # all provided keyword will be using for compare with each line tag, if a line with tag not contain any keyword on 'tag-keyword-list' it will be ignore to output
105 | tag-keyword-list:
106 | - 'FileDownloader'
107 |
108 | # translate message map
109 | # if a message on a line start with provide keyword on the 'trans-msg-map' we will add the value of the keyword on the start of the message, and the word of value will be colored to highlight it
110 | trans-msg-map:
111 | # such as this case:
112 | # origin message: 'filedownloader:lifecycle:over xxx'
113 | # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'
114 | 'filedownloader:lifecycle:over': 'Task OVER'
115 | 'fetch data with': 'Start Fetch'
116 |
117 | # translate tag map
118 | # if a tag on a line contain provide keyword on the 'trans-tag-map' we will add the value of the keyword on the start of the message, and the background of the value word will be colored to highlight it
119 | trans-tag-map:
120 | # such as this case:
121 | # origin message: 'FileDownloader.DownloadTaskHunter xxx'
122 | # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'
123 | 'DownloadTaskHunter': '[Status Change]'
124 | 'ConnectTask': '[Request]'
125 |
126 | # hide message list
127 | # if a message on a line start with provide value on the 'hide-msg-list` and the length of the message is less than 100 word, it would be colored with gray to hide
128 | hide-msg-list:
129 | # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case
130 | - 'notify progress'
131 | - '~~~callback'
132 |
133 | # highlight list
134 | # if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it
135 | highlight-list:
136 | - 'Path['
137 | - 'Url['
138 | - 'Tag['
139 | - 'range['
140 |
141 | # ignore message list
142 | # when you provide such list, the msg start with provided msg will be ignored to printed
143 | ignore-msg-list:
144 | - 'log start with this will be ignored'
145 |
146 | # ignore tag list
147 | # when you provide such list, the tag in the list will be ignored to printed
148 | ignore-tag-list:
149 | - 'tagToBeIgnored'
150 | ```
151 |
152 | #### 2. Execute
153 |
154 | You can just parse logcat from running adb:
155 |
156 | ```shell
157 | okcat -y=your-conf-name
158 | ```
159 |
160 | You also can parse your log file through:
161 |
162 | ```shell
163 | okcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ...
164 | ```
165 |
166 | Simplest case for any developer:
167 |
168 | ```shell
169 | okcat your.package.name
170 | ```
171 |
172 | > Tips: You can use `command + k` on Terminal to flush all content on the session and start a new okcat parse instead of creating anthor new session.
173 |
174 | ## My Terminal Config
175 |
176 | If you want to adapter the same theme like screenshot above, it's very easy:
177 |
178 | - Firstly, please use [powerlevel9k](https://github.com/bhilburn/powerlevel9k) theme(Install the Powerlevel9k Theme and Powerline Fonts as the powerlevel9k repo readme doc said).
179 | - Secondly, please config the [iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron) color scheme.
180 | - Thirdly, please config your shell(If you are using zsh, just add following code to the `~/.zshrc` file):
181 | ```
182 | POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
183 | POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)
184 | POWERLEVEL9K_TIME_FORMAT="%D{%H:%M:%S}"
185 | POWERLEVEL9K_NODE_VERSION_BACKGROUND='022'
186 | POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
187 | ```
188 |
189 | ## Dev
190 |
191 | Import to PyCharm, and Set the Project Structure:
192 |
193 | 
194 |
195 | ## LICENSE
196 |
197 | ```
198 | Copyright (C) 2017 Jacksgong(jacksgong.com)
199 |
200 | Licensed under the Apache License, Version 2.0 (the "License");
201 | you may not use this file except in compliance with the License.
202 | You may obtain a copy of the License at
203 |
204 | http://www.apache.org/licenses/LICENSE-2.0
205 |
206 | Unless required by applicable law or agreed to in writing, software
207 | distributed under the License is distributed on an "AS IS" BASIS,
208 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
209 | See the License for the specific language governing permissions and
210 | limitations under the License.
211 | ```
212 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright (C) 2017 Jacksgong(jacksgong.com)
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/demo-conf/demo-log-file.log:
--------------------------------------------------------------------------------
1 | --------- beginning of system
2 | 06-26 15:18:59.546 1513 1513 I vold : Vold 3.0 (the awakening) firing up
3 | 06-26 15:18:59.546 1513 1513 V vold : Detected support for: ext4 vfat
4 | 06-26 15:18:59.551 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop5: No such device or address
5 | 06-26 15:18:59.581 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop1: No such device or address
6 | 06-26 15:18:59.621 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop6: No such device or address
7 | 06-26 15:18:59.662 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop2: No such device or address
8 | 06-26 15:18:59.700 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop4: No such device or address
9 | 06-26 15:18:59.810 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop0: No such device or address
10 | 06-26 15:18:59.841 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop7: No such device or address
11 | 06-26 15:18:59.880 1513 1513 W vold : Failed to LOOP_GET_STATUS64 /dev/block/loop3: No such device or address
12 | 06-26 15:18:59.915 1513 1513 D vold : VoldNativeService::start() completed OK
13 | 06-26 15:18:59.932 1513 1517 D vold : e4crypt_init_user0
14 | 06-26 15:18:59.933 1513 1517 D vold : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1
15 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/system/users/0
16 | 06-26 15:18:59.933 1513 1516 I vold : Found disk at /devices/pci0000:00/0000:00:08.0/virtio5/block/vdf but delaying scan due to secure keyguard
17 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/misc/profiles/cur/0
18 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/system_de/0
19 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/misc_de/0
20 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/vendor_de/0
21 | 06-26 15:18:59.933 1513 1517 D vold : Preparing: /data/user_de/0
22 | 06-26 15:18:59.934 1513 1517 V vold : /system/bin/vold_prepare_subdirs
23 | 06-26 15:18:59.935 1513 1517 V vold : prepare
24 | 06-26 15:18:59.935 1513 1517 V vold : 0
25 | 06-26 15:18:59.935 1513 1517 V vold : 1
26 | 06-26 15:18:59.949 1513 1517 D vold : e4crypt_unlock_user_key 0 serial=0 token_present=0
27 | 06-26 15:18:59.949 1513 1517 E vold : Failed to chmod /data/system_ce/0: No such file or directory
28 | 06-26 15:18:59.949 1513 1517 E vold : Failed to chmod /data/misc_ce/0: No such file or directory
29 | 06-26 15:18:59.949 1513 1517 E vold : Failed to chmod /data/media/0: No such file or directory
30 | 06-26 15:19:00.225 1513 1590 I Cryptfs : cryptfs_check_passwd
31 | 06-26 15:19:00.227 1513 1590 D Cryptfs : crypt_ftr->fs_size = 1638400
32 | 06-26 15:19:00.227 1513 1590 I Cryptfs : Using scrypt for cryptfs KDF
33 | 06-26 15:19:00.393 1572 1572 I android.hardware.wifi@1.0-service: Wifi Hal is booting up...
34 | 06-26 15:19:00.813 1513 1590 I Cryptfs : Extra parameters for dm_crypt: 1 allow_discards
35 | 06-26 15:19:00.822 1513 1516 D vold : Disk at 252:0 changed
36 | 06-26 15:19:00.978 1513 1590 I Cryptfs : Password matches
37 | 06-26 15:19:00.980 1513 1590 D Cryptfs : test_mount_encrypted_fs(): Master key saved
38 | 06-26 15:19:00.990 1513 1590 I vold : List of Keymaster HALs found:
39 | 06-26 15:19:00.990 1513 1590 I vold : Keymaster HAL #1: SoftwareKeymasterDevice from Google SecurityLevel: SOFTWARE HAL : android.hardware.keymaster@3.0::IKeymasterDevice instance default
40 | 06-26 15:19:00.990 1513 1590 I vold : Using SoftwareKeymasterDevice from Google for encryption. Security level: SOFTWARE, HAL: android.hardware.keymaster@3.0::IKeymasterDevice/default
41 | 06-26 15:19:00.990 1513 1590 D Cryptfs : Password is default - restarting filesystem
42 | 06-26 15:19:01.032 1513 1590 D Cryptfs : unmounting /data succeeded
43 | 06-26 15:19:01.036 1513 1590 I vold : [libfs_mgr]superblock s_max_mnt_count:65535,/dev/block/dm-0
44 | 06-26 15:19:01.037 1513 1590 I vold : [libfs_mgr]Filesystem on /dev/block/dm-0 was not cleanly shutdown; state flags: 0x1, incompat feature flags: 0x46
45 | 06-26 15:19:01.315 1513 1590 I vold : [libfs_mgr]check_fs(): mount(/dev/block/dm-0,/data,ext4)=0: Success
46 | 06-26 15:19:01.354 1513 1590 I vold : [libfs_mgr]check_fs(): unmount(/data) succeeded
47 | 06-26 15:19:01.355 1513 1590 I vold : [libfs_mgr]Running /system/bin/e2fsck on /dev/block/dm-0
48 | 06-26 15:19:01.601 1513 1590 I vold : [libfs_mgr]e2fsck returned status 0x100
49 | 06-26 15:19:01.612 1513 1590 I vold : [libfs_mgr]__mount(source=/dev/block/dm-0,target=/data,type=ext4)=0: Success
50 | 06-26 15:19:01.612 1513 1590 D Cryptfs : Just triggered post_fs_data
51 | 06-26 15:19:01.649 1513 1515 D vold : e4crypt_init_user0
52 | 06-26 15:19:01.649 1513 1515 D vold : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1
53 | 06-26 15:19:01.649 1513 1515 D vold : Preparing: /data/system/users/0
54 | 06-26 15:19:01.649 1513 1515 D vold : Preparing: /data/misc/profiles/cur/0
55 | 06-26 15:19:01.650 1513 1515 D vold : Preparing: /data/system_de/0
56 | 06-26 15:19:01.651 1513 1515 D vold : Preparing: /data/misc_de/0
57 | 06-26 15:19:01.652 1513 1515 D vold : Preparing: /data/vendor_de/0
58 | 06-26 15:19:01.652 1513 1515 D vold : Preparing: /data/user_de/0
59 | 06-26 15:19:01.653 1513 1515 V vold : /system/bin/vold_prepare_subdirs
60 | 06-26 15:19:01.653 1513 1515 V vold : prepare
61 | 06-26 15:19:01.653 1513 1515 V vold :
62 | 06-26 15:19:01.653 1513 1515 V vold : 0
63 | 06-26 15:19:01.653 1513 1515 V vold : 1
64 | 06-26 15:19:01.667 1513 1515 D vold : e4crypt_unlock_user_key 0 serial=0 token_present=0
65 | 06-26 15:19:01.691 1513 1590 D Cryptfs : post_fs_data done
66 | 06-26 15:19:01.692 1513 1590 D Cryptfs : Just triggered restart_framework
67 | 06-26 15:19:01.987 1694 1694 I wificond: wificond is starting up...
68 | 06-26 15:19:02.140 1685 1685 I installd: installd firing up
69 | 06-26 15:19:03.232 1681 1681 D Zygote32Timing: BeginIcuCachePinning took to complete: 46ms
70 | 06-26 15:19:03.721 1681 1681 D Zygote32Timing: PreloadClasses took to complete: 489ms
71 | 06-26 15:19:03.829 1681 1681 D Zygote32Timing: PreloadResources took to complete: 107ms
72 | 06-26 15:19:03.864 1681 1681 D Zygote32Timing: ZygotePreload took to complete: 679ms
73 | 06-26 15:19:03.873 1681 1681 D Zygote32Timing: PostZygoteInitGC took to complete: 9ms
74 | 06-26 15:19:03.873 1681 1681 D Zygote32Timing: ZygoteInit took to complete: 694ms
75 | 06-26 15:19:04.001 1819 1819 I SystemServer: InitBeforeStartServices
76 | 06-26 15:19:04.002 1819 1819 I SystemServer: Entered the Android system server!
77 | 06-26 15:19:04.162 1819 1819 D SystemServerTiming: InitBeforeStartServices took to complete: 161ms
78 | 06-26 15:19:04.162 1819 1819 I SystemServer: StartServices
79 | 06-26 15:19:04.162 1819 1819 I SystemServer: Reading configuration...
80 | 06-26 15:19:04.162 1819 1819 I SystemServer: ReadingSystemConfig
81 | 06-26 15:19:04.164 1819 1819 D SystemServerTiming: ReadingSystemConfig took to complete: 2ms
82 | 06-26 15:19:04.164 1819 1819 I SystemServer: StartInstaller
83 | 06-26 15:19:04.164 1819 1819 I SystemServiceManager: Starting com.android.server.pm.Installer
84 | 06-26 15:19:04.165 1819 1832 D SystemServerInitThreadPool: Started executing ReadingSystemConfig
85 | 06-26 15:19:04.169 1819 1819 D SystemServerTiming: StartInstaller took to complete: 5ms
86 | 06-26 15:19:04.169 1819 1819 I SystemServer: DeviceIdentifiersPolicyService
87 | 06-26 15:19:04.169 1819 1819 I SystemServiceManager: Starting com.android.server.os.DeviceIdentifiersPolicyService
88 | 06-26 15:19:04.171 1819 1819 D SystemServerTiming: DeviceIdentifiersPolicyService took to complete: 2ms
89 | 06-26 15:19:04.171 1819 1819 I SystemServer: StartActivityManager
90 | 06-26 15:19:04.171 1819 1819 I SystemServiceManager: Starting com.android.server.am.ActivityManagerService$Lifecycle
91 | 06-26 15:19:04.197 1819 1819 I ActivityManager: Memory class: 384
92 | 06-26 15:19:04.207 1819 1832 D SystemServerInitThreadPool: Finished executing ReadingSystemConfig
93 | 06-26 15:19:04.216 1819 1819 D BatteryStatsImpl: Reading daily items from /data/system/batterystats-daily.xml
94 | 06-26 15:19:04.230 1819 1840 E BatteryExternalStatsWorker: no controller energy info supplied for telephony
95 | 06-26 15:19:04.233 1819 1840 I KernelUidCpuFreqTimeReader: mPerClusterTimesAvailable=false
96 | 06-26 15:19:04.235 1819 1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/time_in_state
97 | 06-26 15:19:04.235 1819 1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_active_time
98 | 06-26 15:19:04.236 1819 1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_policy_time
99 | 06-26 15:19:04.236 1819 1840 E KernelCpuSpeedReader: Failed to read cpu-freq: /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state (No such file or directory)
100 | 06-26 15:19:04.236 1819 1840 W KernelMemoryBandwidthStats: No kernel memory bandwidth stats available
101 | 06-26 15:19:04.252 1819 1819 W AppOps : Unknown attribute in 'op' tag: n
102 | 06-26 15:19:04.256 1819 1819 I chatty : uid=1000 system_server identical 163 lines
103 | 06-26 15:19:04.256 1819 1819 W AppOps : Unknown attribute in 'op' tag: n
104 | 06-26 15:19:04.266 1819 1819 I IntentFirewall: Read new rules (A:0 B:0 S:0)
105 | 06-26 15:19:04.277 1819 1819 D AppOps : AppOpsService published
106 | 06-26 15:19:04.277 1819 1819 D SystemServerTiming: StartActivityManager took to complete: 106ms
107 | 06-26 15:19:04.277 1819 1819 I SystemServer: StartPowerManager
108 | 06-26 15:19:04.277 1819 1819 I SystemServiceManager: Starting com.android.server.power.PowerManagerService
109 | 06-26 15:19:04.288 1819 1819 D SystemServerTiming: StartPowerManager took to complete: 11ms
110 | 06-26 15:19:04.288 1819 1819 I SystemServer: InitPowerManagement
111 | 06-26 15:19:04.290 1819 1819 D SystemServerTiming: InitPowerManagement took to complete: 2ms
112 | 06-26 15:19:04.290 1819 1819 I SystemServer: StartRecoverySystemService
113 | 06-26 15:19:04.290 1819 1819 I SystemServiceManager: Starting com.android.server.RecoverySystemService
114 | 06-26 15:19:04.291 1819 1819 D SystemServerTiming: StartRecoverySystemService took to complete: 1ms
115 | 06-26 15:19:04.293 1819 1819 W RescueParty: Failed to determine if device was on USB
116 | 06-26 15:19:04.293 1819 1819 W RescueParty: java.io.FileNotFoundException: /sys/class/android_usb/android0/state (No such file or directory)
117 | 06-26 15:19:04.293 1819 1819 W RescueParty: at java.io.FileInputStream.open0(Native Method)
118 | 06-26 15:19:04.293 1819 1819 W RescueParty: at java.io.FileInputStream.open(FileInputStream.java:231)
119 | 06-26 15:19:04.293 1819 1819 W RescueParty: at java.io.FileInputStream.(FileInputStream.java:165)
120 | 06-26 15:19:04.293 1819 1819 W RescueParty: at android.os.FileUtils.readTextFile(FileUtils.java:514)
121 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.RescueParty.isUsbActive(RescueParty.java:348)
122 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.RescueParty.isDisabled(RescueParty.java:88)
123 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.RescueParty.noteBoot(RescueParty.java:107)
124 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.SystemServer.startBootstrapServices(SystemServer.java:587)
125 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.SystemServer.run(SystemServer.java:429)
126 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.server.SystemServer.main(SystemServer.java:294)
127 | 06-26 15:19:04.293 1819 1819 W RescueParty: at java.lang.reflect.Method.invoke(Native Method)
128 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
129 | 06-26 15:19:04.293 1819 1819 W RescueParty: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
130 | 06-26 15:19:04.296 1819 1819 W RescueParty: Noticed 1 events for UID 0 in last 7 sec
131 | 06-26 15:19:04.296 1819 1819 I SystemServer: StartLightsService
132 | 06-26 15:19:04.296 1819 1819 I SystemServiceManager: Starting com.android.server.lights.LightsService
133 | 06-26 15:19:04.297 1819 1819 D SystemServerTiming: StartLightsService took to complete: 1ms
134 | 06-26 15:19:04.297 1819 1819 I SystemServer: StartSidekickService
135 | 06-26 15:19:04.297 1819 1819 D SystemServerTiming: StartSidekickService took to complete: 0ms
136 | 06-26 15:19:04.297 1819 1819 I SystemServer: StartDisplayManager
137 | 06-26 15:19:04.297 1819 1819 I SystemServiceManager: Starting com.android.server.display.DisplayManagerService
138 | 06-26 15:19:04.301 1819 1819 D SystemServerTiming: StartDisplayManager took to complete: 4ms
139 | 06-26 15:19:04.301 1819 1819 I SystemServer: WaitForDisplay
140 | 06-26 15:19:04.301 1819 1819 I SystemServiceManager: Starting phase 100
141 |
--------------------------------------------------------------------------------
/okcat/adb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 |
3 | """
4 | Copyright (C) 2017 Jacksgong(jacksgong.com)
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | """
18 | from os.path import exists
19 |
20 | from okcat.confloader import ConfLoader
21 | from okcat.helper import LOG_LEVELS_MAP, get_conf_path, handle_home_case, print_unicode
22 | from okcat.logprocessor import LogProcessor, indent_wrap
23 | from okcat.logregex import LogRegex
24 | from okcat.terminalcolor import termcolor, RED, RESET, YELLOW, GREEN, colorize, WHITE, allocate_color
25 |
26 | __author__ = 'JacksGong'
27 |
28 | # Script to highlight adb logcat output for console
29 | # Originally written by Jeff Sharkey, http://jsharkey.org/
30 | # Piping detection and popen() added by other Android team members
31 | # Package filtering and output improvements by Jake Wharton, http://jakewharton.com
32 | # Package adapt for okcat by Jacks Gong, https://jacksgong.com
33 |
34 | import sys
35 | import re
36 | import subprocess
37 | from subprocess import PIPE, STDOUT
38 |
39 | # noinspection Annotator
40 | PID_LINE = re.compile(r'^\w+\s+(\w+)\s+\w+\s+\w+\s+\w+\s+\w+\s+\w+\s+\w\s([\w|\.|\/]+)')
41 | PID_START = re.compile(r'^.*: Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\d+) uid=(\d+) gids=(.*)$')
42 | PID_START_5_1 = re.compile(r'^.*: Start proc (\d+):([a-zA-Z0-9._:]+)/[a-z0-9]+ for (.*)$')
43 | PID_START_DALVIK = re.compile(
44 | r'^E/dalvikvm\(\s*(\d+)\): >>>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$')
45 | PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$')
46 | PID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$')
47 | PID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$')
48 |
49 | ADB_LOG_REGEX_EXP = 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'
50 |
51 | BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*')
52 | BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$')
53 | RULES = {
54 | # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
55 | re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)')
56 | : r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET),
57 | }
58 |
59 |
60 | class Adb:
61 | all = None
62 | min_level = None
63 | package_name = None
64 | tag = None
65 | header_size = None
66 | ignored_tag = None
67 |
68 | log_regex = None
69 | catchall_package = None
70 | named_processes = None
71 | pids = None
72 |
73 | adb = None
74 | processor = None
75 |
76 | def __init__(self):
77 | pass
78 |
79 | def setup(self, args):
80 | self.processor = LogProcessor(args.hide_same_tags)
81 |
82 | self.min_level = LOG_LEVELS_MAP[args.min_level.upper()]
83 | self.all = args.all
84 | self.ignored_tag = args.ignored_tag
85 | self.tag = args.tag
86 |
87 | self.package_name = args.package_or_path
88 | self.processor.setup_condition(tag_keywords=args.tag_keywords)
89 | if args.yml is not None:
90 | conf_file_path = get_conf_path(args.yml)
91 | if not exists(handle_home_case(conf_file_path)):
92 | exit('you provide conf file path: ' + conf_file_path + ' is not exist!')
93 |
94 | conf_loader = ConfLoader()
95 | conf_loader.load(conf_file_path)
96 |
97 | yml_package = conf_loader.get_package()
98 | if yml_package is not None:
99 | self.package_name.append(yml_package)
100 |
101 | yml_adb_log_regex = conf_loader.get_adb_log_line_regex()
102 | if yml_adb_log_regex is not None:
103 | self.log_regex = LogRegex(yml_adb_log_regex)
104 |
105 | self.processor.setup_condition(tag_keywords=conf_loader.get_tag_keyword_list())
106 | self.processor.setup_trans(trans_msg_map=conf_loader.get_trans_msg_map(),
107 | trans_tag_map=conf_loader.get_trans_tag_map(),
108 | hide_msg_list=conf_loader.get_hide_msg_list())
109 | self.processor.setup_ignore(ignore_msg_list=conf_loader.get_ignore_msg_list(),
110 | ignore_tag_list=conf_loader.get_ignore_tag_list())
111 | self.processor.setup_highlight(highlight_list=conf_loader.get_highlight_list())
112 | self.processor.setup_separator(separator_rex_list=conf_loader.get_separator_regex_list())
113 |
114 | if self.log_regex is None:
115 | self.log_regex = LogRegex(ADB_LOG_REGEX_EXP)
116 |
117 | base_adb_command = ['adb']
118 | if args.device_serial:
119 | base_adb_command.extend(['-s', args.device_serial])
120 | if args.use_device:
121 | base_adb_command.append('-d')
122 | if args.use_emulator:
123 | base_adb_command.append('-e')
124 |
125 | if args.current_app:
126 | system_dump_command = base_adb_command + ["shell", "dumpsys", "activity", "activities"]
127 | system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0]
128 | running_package_name = re.search(".*TaskRecord.*A[= ]([^ ^}]*)", system_dump).group(1)
129 | self.package_name.append(running_package_name)
130 |
131 | if len(self.package_name) == 0:
132 | self.all = True
133 |
134 | # Store the names of packages for which to match all processes.
135 | self.catchall_package = list(filter(lambda package: package.find(":") == -1, self.package_name))
136 | # Store the name of processes to match exactly.
137 | named_processes = filter(lambda package: package.find(":") != -1, self.package_name)
138 | # Convert default process names from : (cli notation) to (android notation) in the exact names match group.
139 | self.named_processes = map(lambda package: package if package.find(":") != len(package) - 1 else package[:-1],
140 | named_processes)
141 |
142 | self.header_size = args.tag_width + 1 + 3 + 1 # space, level, space
143 |
144 | # Only enable GC coloring if the user opted-in
145 | if args.color_gc:
146 | # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms
147 | key = re.compile(
148 | r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed \d+.)(, \d+% free \d+./\d+., )(paused \d+ms(?:\+\d+ms)?)')
149 | val = r'\1%s\2%s\3%s\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET)
150 |
151 | RULES[key] = val
152 |
153 | adb_command = base_adb_command[:]
154 | adb_command.append('logcat')
155 | adb_command.extend(['-v', 'brief'])
156 | adb_command.extend(['-v', 'threadtime'])
157 |
158 | # Clear log before starting logcat
159 | if args.clear_logcat:
160 | adb_clear_command = list(adb_command)
161 | adb_clear_command.append('-c')
162 | adb_clear = subprocess.Popen(adb_clear_command)
163 |
164 | while adb_clear.poll() is None:
165 | pass
166 |
167 | if sys.stdin.isatty():
168 | self.adb = subprocess.Popen(adb_command, stdout=PIPE, stderr=STDOUT, stdin=PIPE)
169 | else:
170 | self.adb = FakeStdinProcess()
171 |
172 | self.pids = set()
173 |
174 | ps_command = base_adb_command + ['shell', 'ps']
175 | ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
176 | while True:
177 | try:
178 | line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip()
179 | except KeyboardInterrupt:
180 | break
181 | if len(line) == 0:
182 | break
183 |
184 | pid_match = PID_LINE.match(line)
185 | if pid_match is not None:
186 | pid = pid_match.group(1)
187 | proc = pid_match.group(2)
188 | if proc in self.catchall_package:
189 | self.pids.add(pid)
190 |
191 | def loop(self):
192 | app_pid = None
193 |
194 | while self.adb.poll() is None:
195 | try:
196 | line = self.adb.stdout.readline()
197 | except KeyboardInterrupt:
198 | break
199 | if len(line) == 0:
200 | break
201 |
202 | line = line.decode('utf-8', 'replace').strip()
203 | if len(line) == 0:
204 | continue
205 |
206 | bug_line = BUG_LINE.match(line)
207 | if bug_line is not None:
208 | continue
209 |
210 | date, time, level, tag, owner, thread, message = self.log_regex.parse(line)
211 | if message is None:
212 | # print 'message is none with %s' % line
213 | continue
214 |
215 | tag = tag.strip()
216 | start = parse_start_proc(line)
217 | if start:
218 | line_package, target, line_pid, line_uid, line_gids = start
219 | if self.match_packages(line_package):
220 | self.pids.add(line_pid)
221 |
222 | app_pid = line_pid
223 |
224 | linebuf = '\n'
225 | linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)
226 | linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target))
227 | linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)
228 | linebuf += ' PID: %s UID: %s GIDs: %s' % (line_pid, line_uid, line_gids)
229 | linebuf += '\n'
230 | print(linebuf)
231 |
232 | dead_pid, dead_pname = self.parse_death(tag, message)
233 | if dead_pid:
234 | self.pids.remove(dead_pid)
235 | linebuf = '\n'
236 | linebuf += colorize(' ' * (self.header_size - 1), bg=RED)
237 | linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid)
238 | linebuf += '\n'
239 | print(linebuf)
240 |
241 | # Make sure the backtrace is printed after a native crash
242 | if tag == 'DEBUG':
243 | bt_line = BACKTRACE_LINE.match(message.lstrip())
244 | if bt_line is not None:
245 | message = message.lstrip()
246 | owner = app_pid
247 |
248 | # print '%s %s %s' % (owner, self.pids, tag)
249 | if not self.all and owner not in self.pids:
250 | continue
251 | if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < self.min_level:
252 | continue
253 | if self.ignored_tag and tag_in_tags_regex(tag, self.ignored_tag):
254 | continue
255 | if self.tag and not tag_in_tags_regex(tag, self.tag):
256 | continue
257 |
258 | msg_key, linebuf, match_precondition = self.processor.process_decode_content(line, time, level, tag, owner,
259 | thread,
260 | message)
261 | if not match_precondition or linebuf is None:
262 | continue
263 |
264 | if msg_key is not None:
265 | print('')
266 | print_unicode(u''.join(colorize(msg_key + ": ", fg=allocate_color(msg_key))).encode('utf-8').lstrip())
267 |
268 | print_unicode(u''.join(linebuf).encode('utf-8').lstrip())
269 |
270 | def match_packages(self, token):
271 | if len(self.package_name) == 0:
272 | return True
273 | if token in self.named_processes:
274 | return True
275 | index = token.find(':')
276 | return (token in self.catchall_package) if index == -1 else (token[:index] in self.catchall_package)
277 |
278 | def parse_death(self, _tag, _message):
279 | if _tag != 'ActivityManager':
280 | return None, None
281 | kill = PID_KILL.match(_message)
282 | if kill:
283 | _pid = kill.group(1)
284 | package_line = kill.group(2)
285 | if self.match_packages(package_line) and _pid in self.pids:
286 | return _pid, package_line
287 | leave = PID_LEAVE.match(_message)
288 | if leave:
289 | _pid = leave.group(2)
290 | package_line = leave.group(1)
291 | if self.match_packages(package_line) and _pid in self.pids:
292 | return _pid, package_line
293 | death = PID_DEATH.match(_message)
294 | if death:
295 | _pid = death.group(2)
296 | package_line = death.group(1)
297 | if self.match_packages(package_line) and _pid in self.pids:
298 | return _pid, package_line
299 | return None, None # This is a ducktype of the subprocess.Popen object
300 |
301 |
302 | class FakeStdinProcess:
303 | def __init__(self):
304 | self.stdout = sys.stdin
305 |
306 | @staticmethod
307 | def poll():
308 | return None
309 |
310 |
311 | def parse_start_proc(_line):
312 | _start = PID_START_5_1.match(_line)
313 | if _start is not None:
314 | _line_pid, _line_package, _target = _start.groups()
315 | return _line_package, _target, _line_pid, '', ''
316 | _start = PID_START.match(_line)
317 | if _start is not None:
318 | _line_package, _target, _line_pid, _line_uid, _line_gids = _start.groups()
319 | return _line_package, _target, _line_pid, _line_uid, _line_gids
320 | _start = PID_START_DALVIK.match(_line)
321 | if _start is not None:
322 | _line_pid, _line_package, _line_uid = _start.groups()
323 | return _line_package, '', _line_pid, _line_uid, ''
324 | return None
325 |
326 |
327 | def tag_in_tags_regex(_tag, tags):
328 | return any(re.match(r'^' + t + r'$', _tag) for t in map(str.strip, tags))
329 |
--------------------------------------------------------------------------------