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