├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_CN.md ├── bintrayUpload.gradle ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── logger ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── moe │ │ └── studio │ │ └── log │ │ ├── BLogApiTest.java │ │ ├── BLogTest.java │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── moe │ │ └── studio │ │ └── log │ │ ├── BLog.java │ │ ├── Executor.java │ │ ├── Files.java │ │ ├── InternalUtils.java │ │ ├── Log.java │ │ ├── LogCatImpl.java │ │ ├── LogEngine.java │ │ ├── LogEventImpl.java │ │ ├── LogFileImpl.java │ │ ├── LogFileSyncImpl.java │ │ ├── LogFormatter.java │ │ ├── LogFormatterImpl.java │ │ ├── LogPriority.java │ │ ├── LogSetting.java │ │ └── Logger.java │ └── test │ └── java │ └── moe │ └── kaede │ └── log │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: true 3 | jdk: oraclejdk8 4 | android: 5 | components: 6 | - tools 7 | - platform-tools 8 | - build-tools-24.0.2 9 | - android-24 10 | - extra-android-m2repository 11 | licenses: 12 | - 'android-sdk-preview-license-.+' 13 | - 'android-sdk-license-.+' 14 | - 'google-gdk-license-.+' 15 | 16 | script: 17 | - ./gradlew javadocrelease 18 | - ./gradlew jarrelease 19 | - ./gradlew bintrayUpload 20 | 21 | deploy: 22 | provider: releases 23 | api_key: 24 | secure: ${GH_TOKEN} 25 | file: 26 | - b-log/build/libs/b-log-${TRAVIS_TAG}-javadoc.jar 27 | - b-log/build/libs/b-log-${TRAVIS_TAG}.jar 28 | skip_cleanup: true 29 | on: 30 | tags: true 31 | 32 | branches: 33 | only: 34 | - release/bintrayUpload -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016-2017 Kaede 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## BLog - Android Log Extended Utility 3 | [中文](/README_CN.md) 4 | 5 | 6 | 7 | 8 | 9 | BLog is an Android LogCat extended Utility. It can simplify the way you use 10 | {@link android.util.Log}, as well as write our log message into file for after support. 11 | 12 | **BLog is not pronounced 'Blog[blɒɡ]', but '[bi:lɒɡ]'.** 13 | 14 | 15 | ### Feature 16 | 1. Simplified Api for logging message. 17 | 2. Print `Thread Info`. 18 | 3. Set `LogLevel` to control whether to print log or not. 19 | 4. Write log message to `file` in order to trace bugs from release app. 20 | 21 | **Though BLog support using LogLevel to control whether to print log message or 22 | not, it is recommended to use `if statement with a constant as condition` to 23 | control the Log Block as the following snippet.** 24 | 25 | ```java 26 | if (BuildConfig.DEBUG) { 27 | BLog.v(TAG, "Log verbose"); 28 | } 29 | ``` 30 | 31 | Please try `best performance` in any case. :) 32 | 33 | 34 | ## Getting Started 35 | ### Dependency & Initialization 36 | Add dependency. 37 | ```java 38 | compile 'moe.studio:logger:latest.release' 39 | ``` 40 | Initialization. 41 | ```java 42 | BLog.initialize(context); 43 | ``` 44 | 45 | ### Basic 46 | Print log message. 47 | ```java 48 | BLog.v(TAG, "log verbose"); 49 | BLog.v("log verbose with default tag"); 50 | 51 | BLog.d(TAG, "log debug"); 52 | BLog.d("log debug with default tag"); 53 | 54 | BLog.i(TAG, "log info"); 55 | BLog.i("log info with default tag"); 56 | 57 | BLog.w(TAG, "log warning"); 58 | BLog.w("log warning with default tag"); 59 | 60 | BLog.e(TAG, "log error"); 61 | BLog.e("log error with default tag"); 62 | 63 | BLog.wtf(TAG, "log wtf"); 64 | BLog.wtf("log wtf with default tag"); 65 | ``` 66 | 67 | Print event message. 68 | ```java 69 | BLog.event(TAG, "event A"); 70 | BLog.event("event B"); 71 | BLog.event("Excited!"); 72 | ``` 73 | 74 | Get log files. 75 | ```java 76 | // Get log files; 77 | File all = BLog.zippingLogFiles(LogSetting.LOG, null); 78 | // Get log & event files. 79 | File all = BLog.zippingLogFiles(LogSetting.LOG | LogSetting.EVENT, null); 80 | 81 | // Get logs with addiction files. 82 | List attaches = new ArrayList<>(); 83 | attaches.add(outDate1); 84 | attaches.add(outDate2); 85 | File attach = BLog.zippingLogFiles(LogSetting.LOG | LogSetting.EVENT, attaches); 86 | ``` 87 | 88 | ### Advanced 89 | Print exception. 90 | ```java 91 | Exception exception = new RuntimeException("..."); 92 | 93 | BLog.v(TAG, "runtime exception", exception); 94 | BLog.v(exception); 95 | ``` 96 | 97 | Print String with format. 98 | ```java 99 | BLog.vfmt(TAG, "log %s with format string", "verbose"); 100 | BLog.dfmt(null, "log %s with format string", "debug"); 101 | BLog.ifmt(TAG, "log %s with format string", "info"); 102 | BLog.wfmt(null, "log %s with format string", "warning"); 103 | BLog.efmt(TAG, "log %s with format string", "error"); 104 | BLog.wtffmt(null, "log %s with format string", "wtf"); 105 | ``` 106 | 107 | In general, BLog uses a worker thread to write log messages into file. If you want to log message synchronously into file, you'd better use the following api. 108 | ```java 109 | BLog.syncLog(LogPriority.VERBOSE, "TEST", "Sync Log."); 110 | BLog.syncLog(LogPriority.DEBUG, "TEST", "Sync Log."); 111 | ``` 112 | 113 | Besides, you can set a custom LogAdapter to do some addiction jobs when executing a log. 114 | ```java 115 | LogSetting setting = new LogSetting.Builder(context) 116 | .setAdapter(new Log() { 117 | @Override 118 | public void log(int priority, String tag, String msg) { 119 | // Do something. 120 | } 121 | 122 | @Override 123 | public void onShutdown() { 124 | // Do something. 125 | } 126 | }) 127 | .build(); 128 | 129 | BLog.initialize(setting); 130 | ``` 131 | 132 | ### Custom Setting 133 | Initialize BLog 134 | ```java 135 | BLog.initialize(Context); 136 | ``` 137 | 138 | Initialize BLog with custom setting 139 | ```java 140 | LogSetting setting = new LogSetting.Builder(context) 141 | .setDefaultTag("TEST") 142 | .setLogDir(logDir.getPath()) 143 | .setExpiredDay(1) 144 | .setLogcatLevel(LogLevel.DEBUG) 145 | .setLogfileLevel(LogLevel.INFO) 146 | .setEventLevel(LogLevel.VERBOSE) 147 | .setFormatter(new LogFormatterImpl()) 148 | .setAdapter(new Log()) 149 | .build(); 150 | 151 | BLog.initialize(setting); 152 | ``` 153 | 154 | In general, BLog will shutdown itself when the application is terminated, but you can use `BLog#shutdown()` to shutdown BLog. 155 | 156 | For more usage showcases, please check out the [test codes](https://github.com/kaedea/b-log/tree/release/bintray/library/src/androidTest/java/moe/studio/log). 157 | 158 | ## License 159 | Licensed under the Apache License, Version 2.0 (the "License"). -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | ## BLog - Android Log Utils 2 | [![](https://img.shields.io/hexpm/l/plug.svg)](#) [![](https://img.shields.io/badge/minSdk-9-brightgreen.svg)](#) [![Download](https://api.bintray.com/packages/kaedea/moe-studio/b-log/images/download.svg)](https://bintray.com/kaedea/moe-studio/b-log/_latestVersion) 3 | 4 | BLog 是 Android SDK 的 LOG 工具 {@link android.util.Log} 的加强版,以方便在开发时用来 5 | 操作调试日志。 6 | 7 | > LOG 是任何一种编程语言的第一个API,通常被初学者用来打印 `Hello, World!`。 有研究显示, 8 | 不使用 LOG 或者使用姿势错误的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟 9 | 自己不爱的人结婚,而其馀叁成的人最後只能把遗产留给自己的猫。毕竟爱情需要书写,不能是一整张白纸。 10 | 11 | 12 | ### 特点 13 | 1. 简单易用的API; 14 | 2. 支持输出线程信息; 15 | 3. 支持设置LogLevel,方便在生产环境关闭调试用的LOG; 16 | 4. 支持将LOG内容写入文件,以便通过文件LOG定位用户反馈的问题; 17 | 18 | 注意,尽管BLog支持关闭Log的输出,但是在你调用 `BLog.v(String)` 的时候,其实已经造成了性能 19 | 丢失,所以请尽量使用正确的姿势来使用BLog,比如 20 | ```java 21 | if (BuildConfig.DEBUG) { 22 | BLog.v(TAG, "log verbose"); 23 | } 24 | ``` 25 | 26 | 27 | ### 开始使用 28 | #### 依赖和初始化 29 | 添加依赖 30 | ```java 31 | compile 'moe.studio:logger:latest.release' 32 | ``` 33 | 初始化 34 | ```java 35 | BLog.initialize(context); 36 | ``` 37 | 38 | #### 基本用法 39 | 打印Log 40 | ```java 41 | BLog.v(TAG, "log verbose"); 42 | BLog.v("log verbose with default tag"); 43 | 44 | BLog.d(TAG, "log debug"); 45 | BLog.d("log debug with default tag"); 46 | 47 | BLog.i(TAG, "log info"); 48 | BLog.i("log info with default tag"); 49 | 50 | BLog.w(TAG, "log warning"); 51 | BLog.w("log warning with default tag"); 52 | 53 | BLog.e(TAG, "log error"); 54 | BLog.e("log error with default tag"); 55 | 56 | BLog.wtf(TAG, "log wtf"); 57 | BLog.wtf("log wtf with default tag"); 58 | ``` 59 | 60 | 打印Event 61 | 62 | ```java 63 | BLog.event(TAG, "event A"); 64 | BLog.event("event B"); 65 | BLog.event("Excited!"); 66 | ``` 67 | 68 | 69 | #### 进阶用法 70 | 打印异常信息 71 | ```java 72 | Exception exception = new RuntimeException("..."); 73 | 74 | BLog.v(TAG, exception, "runtime exception"); 75 | // or 76 | BLog.v(exception, null); 77 | ``` 78 | 79 | 打印Format格式的字符串 80 | ```java 81 | // use log format, you must offer a tag, even it's null(use default tag) 82 | BLog.vfmt(TAG, "log %s with format string", "verbose"); 83 | BLog.dfmt(null, "log %s with format string", "debug"); 84 | BLog.ifmt(TAG, "log %s with format string", "info"); 85 | BLog.wfmt(null, "log %s with format string", "warning"); 86 | BLog.efmt(TAG, "log %s with format string", "error"); 87 | BLog.wtffmt(null, "log %s with format string", "wtf"); 88 | 89 | // test error format args 90 | // 1. error format msg 91 | BLog.dfmt(null, "error format msg", "debug"); 92 | // 2. error format args 93 | BLog.dfmt(null, "%s format msg", "error", "error", "error"); 94 | BLog.dfmt(null, "%s %s %s format msg", "error"); 95 | ``` 96 | 97 | 98 | #### 自定义 99 | 使用BLog的API之前,必须进行初始化 100 | ```java 101 | BLog.initialize(Context); 102 | ``` 103 | 104 | 进行一些自定义设置 105 | ```java 106 | LogSetting setting = new LogSetting.Builder(context) 107 | .setDefaultTag("TEST") 108 | .setLogDir(logDir.getPath()) 109 | .setExpiredDay(1) 110 | .setLogcatLevel(LogLevel.DEBUG) 111 | .setLogfileLevel(LogLevel.INFO) 112 | .setEventLevel(LogLevel.VERBOSE) 113 | .setFormatter(new LogFormatterImpl()) 114 | .build(); 115 | 116 | BLog.initialize(setting); 117 | ``` 118 | -------------------------------------------------------------------------------- /bintrayUpload.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede (kidhaibara@gmail.com) All Rights Reserved. 3 | */ 4 | 5 | // Load properties. 6 | Properties pros = new Properties() 7 | 8 | // Add Project's properties. 9 | def iterator = properties.keySet().iterator() 10 | while (iterator.hasNext()) { 11 | def key = iterator.next() 12 | def value = properties.get(key) 13 | if (value != null) 14 | pros.put(key, value) 15 | } 16 | 17 | // Add properties from files. 18 | File[] files = [project.file("project.properties"), rootProject.file("local.properties")] 19 | for (File item : files) { 20 | if (item.exists()) 21 | pros.load(item.newDataInputStream()) 22 | } 23 | 24 | // Core config. 25 | if (!pros.getProperty("project.groupId")) throw new StopActionException("NO GROUPID SPECIFICED") 26 | if (!pros.getProperty("project.version")) throw new StopActionException("NO VERSION SPECIFICED") 27 | def projectGroupId = pros.getProperty("project.groupId") 28 | def projectVersion = pros.getProperty("project.version") 29 | def bintrayUser = pros.getProperty("bintray.user") ? pros.getProperty("bintray.user") : System.getenv('BT_USER') 30 | def bintrayApiKey = pros.getProperty("bintray.apikey") ? pros.getProperty("bintray.apikey") : System.getenv('BT_KEY') 31 | // if (!bintrayUser || !bintrayApiKey) throw new StopActionException("NO BINTRAY ACCOUNT SPECIFICED") 32 | 33 | // Optional config. 34 | def projectSiteUrl = pros.getProperty("project.siteUrl") ? pros.getProperty("project.siteUrl") : "NO WEBSITE" 35 | def projectGitUrl = pros.getProperty("project.gitUrl") ? pros.getProperty("project.gitUrl") : "NO GIT URL" 36 | def projectIssueUrl = pros.getProperty("project.issueUrl") ? pros.getProperty("project.issueUrl") : "NO ISSUE URL" 37 | def projectDesc = pros.getProperty("project.description") ? pros.getProperty("project.description") : "NO DESCRIPTION" 38 | // def javadocName = pros.getProperty("javadoc.name") 39 | 40 | def bintrayRepo = pros.getProperty("bintary.repo") ? pros.getProperty("bintary.repo") : "maven" 41 | def developerName = pros.getProperty("developer.name") ? pros.getProperty("developer.name") : "NO DEVELOPER" 42 | def developerSite = pros.getProperty("developer.siteUrl") ? pros.getProperty("developer.siteUrl") : "NO DEVELOPER SITE" 43 | def developerEmail = pros.getProperty("developer.email") ? pros.getProperty("developer.email") : "NO EMAIL" 44 | 45 | group = projectGroupId 46 | version = projectVersion 47 | 48 | apply plugin: 'com.github.dcendents.android-maven' 49 | apply plugin: 'com.jfrog.bintray' 50 | 51 | // BintrayUpload Task. 52 | def hasAndroidPlugin() { 53 | return getPlugins().inject(false) { a, b -> 54 | def classStr = b.getClass().name 55 | def isAndroid = ("com.android.build.gradle.LibraryPlugin" == classStr) || ("com.android.build.gradle.AppPlugin" == classStr) 56 | a || isAndroid 57 | } 58 | } 59 | 60 | task sourcesJar(type: Jar) { 61 | if (hasAndroidPlugin()) { 62 | from android.sourceSets.main.java.srcDirs 63 | classifier = 'sources' 64 | } else { 65 | from sourceSets.main.allSource 66 | classifier = 'sources' 67 | } 68 | } 69 | 70 | artifacts { 71 | archives sourcesJar 72 | // archives javadocJar 73 | } 74 | 75 | bintray { 76 | user = bintrayUser 77 | key = bintrayApiKey 78 | configurations = ['archives'] 79 | pkg { 80 | repo = bintrayRepo 81 | name = "${project.name}" 82 | desc = projectDesc 83 | licenses = ['Apache-2.0'] 84 | userOrg = user 85 | vcsUrl = projectGitUrl 86 | websiteUrl = projectSiteUrl 87 | issueTrackerUrl = projectIssueUrl 88 | publish = true 89 | publicDownloadNumbers = true 90 | // Optional package-level attributes 91 | attributes = ['Developer': developerName, 92 | 'Site' : developerSite, 93 | 'Support' : developerEmail] 94 | version { 95 | name = projectVersion 96 | desc = projectDesc 97 | vcsTag = projectVersion 98 | attributes = ['Version': "${project.version}"] 99 | } 100 | } 101 | } 102 | 103 | tasks.withType(Javadoc).all { 104 | enabled = false 105 | options.encoding = 'UTF-8' 106 | } 107 | 108 | tasks.withType(JavaCompile) { 109 | options.encoding = "UTF-8" 110 | } 111 | 112 | // JFrog SNAPSHOT. 113 | apply plugin: "com.jfrog.artifactory" 114 | 115 | artifactory { 116 | contextUrl = 'http://oss.jfrog.org/artifactory' 117 | resolve { 118 | repository { 119 | repoKey = 'libs-release' 120 | } 121 | } 122 | publish { 123 | repository { 124 | repoKey = 'oss-snapshot-local' 125 | username = bintray.user 126 | password = bintray.key 127 | maven = true 128 | } 129 | defaults { 130 | publishConfigs('archives') 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 6 | 7 | buildscript { 8 | repositories { 9 | jcenter() 10 | } 11 | 12 | ext { 13 | buildGradle = '2.2.0'; 14 | buildToolsVersion = '24.0.2' 15 | targetSdkVersion = 24 16 | minSdkVersion = 9 17 | androidSupportLibraryVersion = '23.4.0' 18 | } 19 | 20 | dependencies { 21 | classpath "com.android.tools.build:gradle:${rootProject.ext.buildGradle}" 22 | } 23 | 24 | } 25 | 26 | plugins { 27 | id "com.jfrog.bintray" version "1.7.3" 28 | id "com.jfrog.artifactory" version "4.4.15" 29 | id "com.github.dcendents.android-maven" version "1.5" 30 | } 31 | 32 | allprojects { 33 | repositories { 34 | jcenter() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017. Kaede 3 | # 4 | 5 | # Project-wide Gradle settings. 6 | 7 | # IDE (e.g. Android Studio) users: 8 | # Gradle settings configured through the IDE *will override* 9 | # any settings specified in this file. 10 | 11 | # For more details on how to configure your build environment visit 12 | # http://www.gradle.org/docs/current/userguide/build_environment.html 13 | 14 | # Specifies the JVM arguments used for the daemon process. 15 | # The setting is particularly useful for tweaking memory settings. 16 | org.gradle.jvmargs=-Xmx1536m 17 | 18 | # When configured, Gradle will run in incubating parallel mode. 19 | # This option should only be used with decoupled projects. More details, visit 20 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 21 | # org.gradle.parallel=true 22 | 23 | # Bintray 24 | bintary.repo=moe-studio 25 | 26 | # Developer 27 | developer.name=Kaede Akatsuki 28 | developer.siteUrl=www.kaedea.com 29 | developer.email=kidhaibara@gmail.com -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaedea/b-log/b2924c237d6018570cda8d164994fe97e1df127e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017. Kaede 3 | # 4 | 5 | #Mon Dec 28 10:00:20 PST 2015 6 | distributionBase=GRADLE_USER_HOME 7 | distributionPath=wrapper/dists 8 | zipStoreBase=GRADLE_USER_HOME 9 | zipStorePath=wrapper/dists 10 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 11 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /logger/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /logger/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | apply plugin: 'com.android.library' 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.targetSdkVersion 9 | buildToolsVersion rootProject.ext.buildToolsVersion 10 | resourcePrefix "blog_" 11 | 12 | defaultConfig { 13 | minSdkVersion rootProject.ext.minSdkVersion 14 | targetSdkVersion rootProject.ext.targetSdkVersion 15 | versionCode 1 16 | versionName version 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | testCompile 'junit:junit:4.12' 33 | 34 | // compile 'com.android.support:appcompat-v7:24.1.1' 35 | compile "com.android.support:support-v4:${rootProject.ext.androidSupportLibraryVersion}" 36 | compile 'commons-io:commons-io:2.5' 37 | compile 'moe.studio:dispatchers:0.2.0' 38 | 39 | } 40 | 41 | // http://stackoverflow.com/questions/16763090/how-to-export-library-to-jar-in-android-studio 42 | // makeJar 1 43 | // This is the actual solution, as in http://stackoverflow.com/a/19037807/1002054 44 | /* 45 | task clearJar(type: Delete) { 46 | delete 'build/libs/myCompiledLibrary.jar' 47 | } 48 | 49 | task makeJar(type: Copy) { 50 | from('build/bundles/release/') 51 | into('build/libs/') 52 | include('classes.jar') 53 | rename ('classes.jar', 'myCompiledLibrary.jar') 54 | } 55 | 56 | makeJar.dependsOn(clearJar, build) 57 | */ 58 | 59 | // // makeJar 2 60 | android.libraryVariants.all { variant -> 61 | task("generate${variant.name}Javadoc", type: Javadoc) { 62 | group = "publishing" 63 | description "Generates Javadoc for $variant.name." 64 | source = variant.javaCompile.source 65 | ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" 66 | classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) 67 | } 68 | 69 | task("javadoc${variant.name}", type: Jar) { 70 | group = "publishing" 71 | classifier = "javadoc" 72 | description "Bundles Javadoc into a JAR file for $variant.name." 73 | from tasks["generate${variant.name}Javadoc"] 74 | 75 | } 76 | 77 | task("jar${variant.name}", type: Jar) { 78 | group = "publishing" 79 | description "Bundles compiled .class files into a JAR file for $variant.name." 80 | dependsOn variant.javaCompile 81 | from variant.javaCompile.destinationDir 82 | exclude '**/R.class', '**/R$*.class', '**/R.html', '**/R.*.html' 83 | } 84 | } 85 | 86 | apply from: rootProject.file('bintrayUpload.gradle') 87 | -------------------------------------------------------------------------------- /logger/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017 Kaede Akatsuki (kidhaibara@gmail.com) 3 | # 4 | 5 | # Project 6 | project.version=1.1.0 7 | project.groupId=moe.studio 8 | project.name=logger 9 | project.description=Android Log Utility. 10 | project.siteUrl=https://github.com/kaedea/b-log 11 | project.gitUrl=https://github.com/kaedea/b-log.git 12 | project.issueUrl=https://github.com/kaedea/b-log/issues 13 | 14 | # Javadoc 15 | javadoc.name=logger -------------------------------------------------------------------------------- /logger/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/cosmos/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /logger/src/androidTest/java/moe/studio/log/BLogApiTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.support.test.InstrumentationRegistry; 8 | import android.support.test.runner.AndroidJUnit4; 9 | 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | /** 16 | * BLog Apis test, acting as usage demo. 17 | * 18 | * @author kaede 19 | * @version date 16/9/25 20 | */ 21 | @RunWith(AndroidJUnit4.class) 22 | public class BLogApiTest { 23 | 24 | public static final String TAG = "BLogApiTest"; 25 | 26 | @Before 27 | public void setUp() { 28 | LogSetting setting = new LogSetting.Builder(InstrumentationRegistry.getTargetContext()) 29 | .showThreadInfo(true) 30 | .debuggable(true) 31 | .build(); 32 | BLog.initialize(setting); 33 | } 34 | 35 | @After 36 | public void shutDown() { 37 | BLog.shutdown(); 38 | } 39 | 40 | @Test 41 | public void useLogBasic() { 42 | BLog.v(TAG, "log verbose"); 43 | BLog.v("log verbose with default tag"); 44 | 45 | BLog.d(TAG, "log debug"); 46 | BLog.d("log debug with default tag"); 47 | 48 | BLog.i(TAG, "log info"); 49 | BLog.i("log info with default tag"); 50 | 51 | BLog.w(TAG, "log warning"); 52 | BLog.w("log warning with default tag"); 53 | 54 | BLog.e(TAG, "log error"); 55 | BLog.e("log error with default tag"); 56 | 57 | BLog.wtf(TAG, "log wtf"); 58 | BLog.wtf("log wtf with default tag"); 59 | } 60 | 61 | @Test 62 | public void useLogFormat() { 63 | // use log format, you must offer a tag, even it's null(use default tag) 64 | BLog.vfmt(TAG, "log %s with format string", "verbose"); 65 | BLog.dfmt(null, "log %s with format string", "debug"); 66 | BLog.ifmt(TAG, "log %s with format string", "info"); 67 | BLog.wfmt(null, "log %s with format string", "warning"); 68 | BLog.efmt(TAG, "log %s with format string", "error"); 69 | BLog.wtffmt(null, "log %s with format string", "wtf"); 70 | 71 | // test error format args 72 | // 1. error format msg 73 | BLog.dfmt(null, "error format msg", "debug"); 74 | // 2. error format args 75 | BLog.dfmt(null, "%s format msg", "error", "error", "error"); 76 | BLog.dfmt(null, "%s %s %s format msg", "error"); 77 | } 78 | 79 | @Test 80 | public void useLogThrowable() { 81 | Exception exception = new RuntimeException("一种钦定的感觉"); 82 | 83 | BLog.v(TAG, "runtime exception", exception); 84 | BLog.v(null, exception); 85 | 86 | BLog.d(TAG, "runtime exception", exception); 87 | BLog.d(null, exception); 88 | 89 | BLog.i(TAG, "runtime exception", exception); 90 | BLog.i(null, exception); 91 | 92 | BLog.w(TAG, "runtime exception", exception); 93 | BLog.w(null, exception); 94 | 95 | BLog.e(TAG, "runtime exception", exception); 96 | BLog.e(null, exception); 97 | } 98 | 99 | @Test 100 | public void useEvent() { 101 | BLog.event("BLOG", "XX一律不得经商"); 102 | BLog.event("XX发大财才是坠猴的"); 103 | BLog.event("做了一点成绩"); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /logger/src/androidTest/java/moe/studio/log/BLogTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.content.Context; 8 | import android.os.Looper; 9 | import android.os.SystemClock; 10 | import android.test.InstrumentationTestCase; 11 | 12 | import java.io.File; 13 | import java.io.FileReader; 14 | import java.io.IOException; 15 | import java.io.LineNumberReader; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.Date; 19 | import java.util.Enumeration; 20 | import java.util.List; 21 | import java.util.concurrent.CountDownLatch; 22 | import java.util.zip.ZipEntry; 23 | import java.util.zip.ZipFile; 24 | 25 | public class BLogTest extends InstrumentationTestCase { 26 | 27 | public void testInitialize() { 28 | Context context = getInstrumentation().getTargetContext(); 29 | BLog.initialize(context); 30 | LogSetting setting = BLog.getSetting(); 31 | 32 | assertNotNull(setting); 33 | assertTrue(BLog.getLogDir().exists()); 34 | assertTrue(setting.getExpiredDay() == 2); 35 | 36 | BLog.shutdown(); 37 | } 38 | 39 | public void testInitializeWithConfig() throws IOException { 40 | Context context = getInstrumentation().getTargetContext(); 41 | File logDir = context.getExternalFilesDir("test_log"); 42 | if (logDir == null) 43 | logDir = context.getDir("test_log", Context.MODE_PRIVATE); 44 | 45 | logDir.createNewFile(); 46 | 47 | LogSetting setting = new LogSetting.Builder(context) 48 | .setDefaultTag("TEST") 49 | .setLogDir(logDir.getPath()) 50 | .setExpiredDay(1) 51 | .setLogcatPriority(LogPriority.DEBUG) 52 | .setLogfilePriority(LogPriority.INFO) 53 | .setEventPriority(LogPriority.VERBOSE) 54 | .setFormatter(new LogFormatterImpl()) 55 | .build(); 56 | 57 | BLog.initialize(setting); 58 | setting = BLog.getSetting(); 59 | 60 | assertNotNull(setting); 61 | assertTrue(new File(setting.getLogDir()).exists()); 62 | assertTrue(setting.getLogDir().equals(logDir.getAbsolutePath())); 63 | assertTrue(setting.getDefaultTag().equals("TEST")); 64 | assertTrue(setting.getExpiredDay() == 1); 65 | assertTrue(setting.getLogcatPriority() == LogPriority.DEBUG); 66 | assertTrue(setting.getLogfilePriority() == LogPriority.INFO); 67 | assertTrue(setting.getLogFormatter() instanceof LogFormatterImpl); 68 | 69 | BLog.shutdown(); 70 | } 71 | 72 | public void testCleanExpiredFiles() throws IOException, InterruptedException { 73 | Context context = getInstrumentation().getTargetContext(); 74 | BLog.initialize(context); 75 | LogSetting setting = BLog.getSetting(); 76 | 77 | File folder = new File(setting.getLogDir()); 78 | assertTrue(folder.exists()); 79 | BLog.deleteLogs(); 80 | assertEquals(folder.exists(), false); 81 | 82 | folder.mkdirs(); 83 | assertEquals(folder.listFiles().length, 0); 84 | 85 | File outDate1 = new File(folder.getAbsolutePath() + File.separator + "20160520-main.log"); 86 | File outDate2 = new File(folder.getAbsolutePath() + File.separator + "20140516-downlaod.log"); 87 | File outDate3 = new File(folder.getAbsolutePath() + File.separator + "20150602-web.event"); 88 | File outDate4 = new File(folder.getAbsolutePath() + File.separator + "20130322-main.event"); 89 | outDate1.createNewFile(); 90 | outDate2.createNewFile(); 91 | outDate3.createNewFile(); 92 | outDate4.createNewFile(); 93 | 94 | File newLog = new File(Files.instance(setting).getLogFile().getAbsolutePath()); 95 | newLog.createNewFile(); 96 | 97 | assertEquals(folder.listFiles().length, 5); 98 | 99 | LogEngine logEngine = BLog.getLogger(); 100 | logEngine.cleanExpiredFiles(); 101 | 102 | Thread.sleep(30); 103 | 104 | assertEquals(folder.listFiles().length, 1); 105 | 106 | BLog.shutdown(); 107 | } 108 | 109 | public void testExecutor() throws InterruptedException { 110 | final int[] i = {0}; 111 | final Looper looper = Looper.myLooper(); 112 | Executor.post(new Runnable() { 113 | @Override 114 | public void run() { 115 | assertTrue(Looper.myLooper() != looper); 116 | i[0]++; 117 | } 118 | }); 119 | Thread.sleep(100); 120 | assertEquals(i[0], 1); 121 | } 122 | 123 | 124 | public void testLogCount() throws InterruptedException, IOException { 125 | Context context = getInstrumentation().getTargetContext(); 126 | BLog.initialize(context); 127 | LogSetting setting = BLog.getSetting(); 128 | 129 | File folder = new File(setting.getLogDir()); 130 | assertTrue(folder.exists()); 131 | 132 | File log = Files.instance(setting).getLogFile(); 133 | File event = Files.instance(setting).getEventFile(); 134 | BLog.deleteLogs(); 135 | 136 | int i = 0; 137 | while (i < 120) { 138 | BLog.event("TEST", "naive! " + i); 139 | 140 | if (i < 45) { 141 | BLog.d("TEST", "too young! " + i); 142 | } 143 | 144 | i++; 145 | } 146 | 147 | LineNumberReader lineNumberReader1 = null; 148 | LineNumberReader lineNumberReader2 = null; 149 | Thread.sleep(5000); 150 | 151 | assertTrue(log.exists()); 152 | assertTrue(event.exists()); 153 | 154 | lineNumberReader1 = new LineNumberReader(new FileReader(log)); 155 | lineNumberReader1.skip(Long.MAX_VALUE); 156 | assertEquals(lineNumberReader1.getLineNumber(), 45); 157 | 158 | lineNumberReader2 = new LineNumberReader(new FileReader(event)); 159 | lineNumberReader2.skip(Long.MAX_VALUE); 160 | assertEquals(lineNumberReader2.getLineNumber(), 120); 161 | InternalUtils.closeQuietly(lineNumberReader1); 162 | InternalUtils.closeQuietly(lineNumberReader2); 163 | 164 | BLog.shutdown(); 165 | } 166 | 167 | public void testPrintStackTrace() throws InterruptedException, IOException { 168 | Context context = getInstrumentation().getTargetContext(); 169 | BLog.initialize(context); 170 | LogSetting setting = BLog.getSetting(); 171 | 172 | File folder = new File(setting.getLogDir()); 173 | assertTrue(folder.exists()); 174 | 175 | File log = new File(Files.instance(setting).getLogFile().getAbsolutePath()); 176 | BLog.deleteLogs(); 177 | 178 | try { 179 | String e = null; 180 | // throw null exception 181 | boolean b = e.length() == 3; 182 | } catch (Exception e) { 183 | //print stacktrace, probably has 15 lines 184 | BLog.w("TEST", null, e); 185 | } 186 | 187 | LineNumberReader lineNumberReader = null; 188 | Thread.sleep(3000); 189 | 190 | lineNumberReader = new LineNumberReader(new FileReader(log)); 191 | lineNumberReader.skip(Long.MAX_VALUE); 192 | assertTrue(lineNumberReader.getLineNumber() > 1); // may have 15 lines 193 | InternalUtils.closeQuietly(lineNumberReader); 194 | 195 | BLog.shutdown(); 196 | } 197 | 198 | public void testMultiThread() throws InterruptedException, IOException { 199 | Context context = getInstrumentation().getTargetContext(); 200 | BLog.initialize(context); 201 | LogSetting setting = BLog.getSetting(); 202 | 203 | File folder = new File(setting.getLogDir()); 204 | assertTrue(folder.exists()); 205 | 206 | File log = new File(Files.instance(setting).getLogFile().getAbsolutePath()); 207 | File event = new File(Files.instance(setting).getEventFile().getAbsolutePath()); 208 | BLog.deleteLogs(); 209 | 210 | LineNumberReader ll = null; 211 | LineNumberReader le = null; 212 | final CountDownLatch countDownLatch = new CountDownLatch(4); 213 | 214 | new Thread(new Runnable() { 215 | @Override 216 | public void run() { 217 | int i = 0; 218 | while (i < 1000) { 219 | BLog.d("thread 1", "做了一点小成绩"); 220 | i++; 221 | } 222 | countDownLatch.countDown(); 223 | } 224 | }).start(); 225 | 226 | new Thread(new Runnable() { 227 | @Override 228 | public void run() { 229 | int i = 0; 230 | while (i < 1000) { 231 | BLog.d("thread 2", "无可奉告"); 232 | i++; 233 | } 234 | countDownLatch.countDown(); 235 | } 236 | }).start(); 237 | 238 | new Thread(new Runnable() { 239 | @Override 240 | public void run() { 241 | int i = 0; 242 | while (i < 1000) { 243 | BLog.event("thread 3", "成天就想搞个大新闻"); 244 | i++; 245 | } 246 | countDownLatch.countDown(); 247 | } 248 | }).start(); 249 | 250 | new Thread(new Runnable() { 251 | @Override 252 | public void run() { 253 | int i = 0; 254 | while (i < 1000) { 255 | BLog.event("thread 4", "谈笑风生"); 256 | i++; 257 | } 258 | countDownLatch.countDown(); 259 | } 260 | }).start(); 261 | 262 | long current = SystemClock.elapsedRealtime(); 263 | countDownLatch.await(); 264 | 265 | // logcat task should be very fast 266 | long interval = SystemClock.elapsedRealtime() - current; 267 | 268 | assertTrue(interval < 1000); 269 | 270 | Thread.sleep(15000); 271 | 272 | ll = new LineNumberReader(new FileReader(log)); 273 | ll.skip(Long.MAX_VALUE); 274 | assertEquals(ll.getLineNumber(), 2000); 275 | 276 | le = new LineNumberReader(new FileReader(event)); 277 | le.skip(Long.MAX_VALUE); 278 | assertEquals(le.getLineNumber(), 2000); 279 | 280 | 281 | String path = Files.instance(setting).getZipFile(LogSetting.LOG | LogSetting.EVENT).getAbsolutePath(); 282 | File[] files = BLog.getLogFilesByDate(LogSetting.LOG | LogSetting.EVENT, null); 283 | File output = new File(path); 284 | 285 | assertTrue(!output.exists()); 286 | assertTrue(InternalUtils.zippingFiles(Arrays.asList(files), output)); 287 | assertTrue(output.exists()); 288 | 289 | BLog.shutdown(); 290 | } 291 | 292 | public void testGetLogFiles() throws InterruptedException, IOException { 293 | Context context = getInstrumentation().getTargetContext(); 294 | LogSetting setting = new LogSetting.Builder(context) 295 | .showThreadInfo(true) 296 | .build(); 297 | BLog.initialize(setting); 298 | 299 | File folder = new File(setting.getLogDir()); 300 | assertTrue(folder.exists()); 301 | BLog.deleteLogs(); 302 | 303 | BLog.d("TEST", "传授一点人生经验"); 304 | BLog.event("TEST", "Exciting!"); 305 | 306 | Thread.sleep(3000); 307 | 308 | File[] files = BLog.getLogFilesByDate(LogSetting.LOG | LogSetting.EVENT, null); 309 | assertNotNull(files); 310 | assertEquals(2, files.length); 311 | 312 | files = BLog.getLogFilesByDate(LogSetting.LOG, null); 313 | assertNotNull(files); 314 | assertEquals(1, files.length); 315 | assertTrue(files[0].getAbsolutePath().contains(Files.LOG_FILE_EXTENSION)); 316 | 317 | files = BLog.getLogFilesByDate(LogSetting.EVENT, null); 318 | assertNotNull(files); 319 | assertEquals(1, files.length); 320 | assertTrue(files[0].getAbsolutePath().contains(Files.EVENT_FILE_EXTENSION)); 321 | 322 | File outDate1 = new File(folder.getAbsolutePath() + File.separator + "20160520-main.log"); 323 | File outDate3 = new File(folder.getAbsolutePath() + File.separator + "20150602-web.event"); 324 | File outDate4 = new File(folder.getAbsolutePath() + File.separator + "20130322-main.event"); 325 | outDate1.createNewFile(); 326 | outDate3.createNewFile(); 327 | outDate4.createNewFile(); 328 | 329 | files = BLog.getLogFiles(LogSetting.LOG | LogSetting.EVENT); 330 | assertNotNull(files); 331 | assertEquals(5, files.length); 332 | 333 | files = BLog.getLogFiles(LogSetting.LOG); 334 | assertNotNull(files); 335 | assertEquals(2, files.length); 336 | 337 | files = BLog.getLogFiles(LogSetting.EVENT); 338 | assertNotNull(files); 339 | assertEquals(3, files.length); 340 | 341 | BLog.shutdown(); 342 | } 343 | 344 | public void testZippingLogFiles() throws InterruptedException, IOException { 345 | Context context = getInstrumentation().getTargetContext(); 346 | LogSetting setting = new LogSetting.Builder(context) 347 | .showThreadInfo(true) 348 | .build(); 349 | BLog.initialize(setting); 350 | 351 | File folder = new File(setting.getLogDir()); 352 | assertTrue(folder.exists()); 353 | BLog.deleteLogs(); 354 | 355 | BLog.d("TEST", "传授一点人生经验"); 356 | BLog.event("TEST", "Exciting!"); 357 | 358 | Thread.sleep(3000); 359 | 360 | String path; 361 | path = Files.instance(setting).getZipFile(LogSetting.EVENT).getAbsolutePath(); 362 | assertTrue(path.contains("event")); 363 | path = Files.instance(setting).getZipFile(LogSetting.EVENT | LogSetting.LOG).getAbsolutePath(); 364 | assertTrue(path.contains("all")); 365 | path = Files.instance(setting).getZipFile(LogSetting.LOG).getAbsolutePath(); 366 | assertTrue(path.contains("log")); 367 | 368 | File[] files = BLog.getLogFilesByDate(LogSetting.LOG, null); 369 | File output = new File(path); 370 | 371 | assertTrue(!output.exists()); 372 | assertTrue(InternalUtils.zippingFiles(Arrays.asList(files), output)); 373 | assertTrue(output.exists()); 374 | 375 | File all = BLog.zippingLogFiles(LogSetting.LOG | LogSetting.EVENT, null); 376 | assertTrue(all.exists()); 377 | int allCount = 0; 378 | try { 379 | ZipFile zipFile = new ZipFile(all); 380 | Enumeration entries = zipFile.entries(); 381 | while (entries.hasMoreElements()) { 382 | ZipEntry zipEntry = entries.nextElement(); 383 | if (!zipEntry.isDirectory()) { 384 | allCount ++; 385 | } 386 | } 387 | } catch (IOException e) { 388 | assertNull(e); 389 | } 390 | 391 | File event = BLog.zippingLogFilesByDate(LogSetting.EVENT, new Date(), null); 392 | assertTrue(event.exists()); 393 | assertTrue(all.length() > event.length()); 394 | int eventCount = 0; 395 | try { 396 | ZipFile zipFile = new ZipFile(event); 397 | Enumeration entries = zipFile.entries(); 398 | while (entries.hasMoreElements()) { 399 | ZipEntry zipEntry = entries.nextElement(); 400 | if (!zipEntry.isDirectory()) { 401 | eventCount ++; 402 | } 403 | } 404 | } catch (IOException e) { 405 | assertNull(e); 406 | } 407 | 408 | assertTrue(eventCount < allCount); 409 | 410 | File outDate1 = new File(folder.getAbsolutePath() + File.separator + "attatch1"); 411 | File outDate3 = new File(folder.getAbsolutePath() + File.separator + "attatch2"); 412 | File outDate4 = new File(folder.getAbsolutePath() + File.separator + "attatch3"); 413 | outDate1.createNewFile(); 414 | outDate3.createNewFile(); 415 | outDate4.createNewFile(); 416 | List attaches = new ArrayList<>(); 417 | attaches.add(outDate1); 418 | attaches.add(outDate3); 419 | attaches.add(outDate4); 420 | 421 | File attach = BLog.zippingLogFiles(LogSetting.LOG | LogSetting.EVENT, attaches); 422 | assertTrue(attach.exists()); 423 | assertTrue(all.length() > event.length()); 424 | int allAttach = 0; 425 | try { 426 | ZipFile zipFile = new ZipFile(attach); 427 | Enumeration entries = zipFile.entries(); 428 | while (entries.hasMoreElements()) { 429 | ZipEntry zipEntry = entries.nextElement(); 430 | if (!zipEntry.isDirectory()) { 431 | allAttach ++; 432 | } 433 | } 434 | } catch (IOException e) { 435 | assertNull(e); 436 | } 437 | 438 | assertEquals(allAttach, allCount + attaches.size()); 439 | } 440 | 441 | public void testSyncLogFiles() throws IOException, InterruptedException { 442 | Context context = getInstrumentation().getTargetContext(); 443 | BLog.initialize(context); 444 | LogSetting setting = BLog.getSetting(); 445 | 446 | File folder = new File(setting.getLogDir()); 447 | assertTrue(folder.exists()); 448 | 449 | File log = new File(Files.instance(setting).getLogFile().getAbsolutePath()); 450 | BLog.deleteLogs(); 451 | 452 | Runnable runnable = new Runnable() { 453 | public void run() { 454 | int i = 0; 455 | while (i < 120) { 456 | BLog.syncLog(LogPriority.INFO, "TEST", "你们这要搞事"); 457 | 458 | if (i < 20) { 459 | BLog.syncLog(LogPriority.VERBOSE, "TEST", "要是将来报道出了偏差"); 460 | } 461 | 462 | if (i < 30) { 463 | BLog.syncLog(LogPriority.DEBUG, "TEST", "可是要负责的"); 464 | } 465 | 466 | i++; 467 | } 468 | } 469 | }; 470 | 471 | Executor.post(runnable); 472 | 473 | Thread.sleep(3000); 474 | 475 | LineNumberReader lineNumberReader = null; 476 | assertTrue(log.exists()); 477 | 478 | lineNumberReader = new LineNumberReader(new FileReader(log)); 479 | lineNumberReader.skip(Long.MAX_VALUE); 480 | assertEquals(lineNumberReader.getLineNumber(), 120 + 30); 481 | 482 | InternalUtils.closeQuietly(lineNumberReader); 483 | BLog.shutdown(); 484 | } 485 | 486 | public void testLogAdapter() throws InterruptedException { 487 | Context context = getInstrumentation().getTargetContext(); 488 | final int[] logCount = new int[1]; 489 | final boolean[] isShutdown = new boolean[1]; 490 | 491 | LogSetting setting = new LogSetting.Builder(context) 492 | .setAdapter(new Log() { 493 | @Override 494 | public void log(int priority, String tag, String msg) { 495 | logCount[0]++; 496 | } 497 | 498 | @Override 499 | public void onShutdown() { 500 | isShutdown[0] = true; 501 | } 502 | }) 503 | .build(); 504 | 505 | BLog.initialize(setting); 506 | 507 | File folder = new File(setting.getLogDir()); 508 | assertTrue(folder.exists()); 509 | 510 | int i = 0; 511 | while (i < 120) { 512 | BLog.event("TEST", "naive! " + i); 513 | 514 | if (i < 45) { 515 | BLog.d("TEST", "too young! " + i); 516 | } 517 | 518 | i++; 519 | } 520 | 521 | Thread.sleep(5000); 522 | assertEquals(logCount[0], 45 + 120); 523 | assertEquals(isShutdown[0], false); 524 | 525 | BLog.shutdown(); 526 | assertEquals(isShutdown[0], true); 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /logger/src/androidTest/java/moe/studio/log/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.content.Context; 8 | import android.support.test.InstrumentationRegistry; 9 | import android.support.test.runner.AndroidJUnit4; 10 | 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | 16 | /** 17 | * Instrumentation test, which will execute on an Android device. 18 | * 19 | * @see Testing documentation 20 | */ 21 | @RunWith(AndroidJUnit4.class) 22 | public class ExampleInstrumentedTest { 23 | @Test 24 | public void useAppContext() throws Exception { 25 | // Context of the app under test. 26 | Context appContext = InstrumentationRegistry.getTargetContext(); 27 | 28 | String packageName = appContext.getPackageName(); 29 | assertEquals("moe.studio.log.test", packageName); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /logger/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/BLog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.content.Context; 8 | import android.support.annotation.WorkerThread; 9 | 10 | import java.io.File; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | /** 15 | * BLog is an Android LogCat extended Utility. It can simplify the way you use 16 | * {@link android.util.Log}, as well as write our log message into file for after support. 17 | * 18 | * @author Kaede 19 | * @version date 16/9/22 20 | */ 21 | 22 | @SuppressWarnings({"WeakerAccess", "unused"}) 23 | public class BLog { 24 | 25 | private static LogEngine sLogEngine; 26 | 27 | private BLog() { 28 | } 29 | 30 | private static boolean checkInit() { 31 | boolean init = sLogEngine != null; 32 | 33 | if (!init) { 34 | throw new RuntimeException("Pls call Blog.initialize first!"); 35 | } 36 | 37 | return true; 38 | } 39 | 40 | /** 41 | * You should call this method before using BLog. 42 | */ 43 | public static void initialize(Context context) { 44 | if (context == null) { 45 | throw new RuntimeException("Context is null."); 46 | } 47 | 48 | initialize(new LogSetting.Builder(context).build()); 49 | } 50 | 51 | /** 52 | * You should call this method before using BLog. 53 | * 54 | * @param setting Custom config 55 | */ 56 | public static void initialize(LogSetting setting) { 57 | if (setting == null) { 58 | throw new RuntimeException("Setting is null."); 59 | } 60 | 61 | if (sLogEngine == null) { 62 | synchronized (BLog.class) { 63 | if (sLogEngine == null) { 64 | sLogEngine = new LogEngine(setting); 65 | } 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * You should call this method before you call {@link BLog#initialize(Context)} again. 72 | */ 73 | public static void shutdown() { 74 | if (checkInit()) { 75 | sLogEngine.shutdown(); 76 | sLogEngine = null; 77 | } 78 | } 79 | 80 | /** 81 | * Verbose log. 82 | */ 83 | public static void v(String message) { 84 | if (checkInit()) { 85 | sLogEngine.verbose(null, message); 86 | } 87 | } 88 | 89 | public static void v(String tag, String message) { 90 | if (checkInit()) { 91 | sLogEngine.verbose(tag, message); 92 | } 93 | } 94 | 95 | public static void v(String message, Throwable throwable) { 96 | if (checkInit()) { 97 | sLogEngine.verbose(null, throwable, message); 98 | } 99 | } 100 | 101 | public static void v(String tag, String message, Throwable throwable) { 102 | if (checkInit()) { 103 | sLogEngine.verbose(tag, throwable, message); 104 | } 105 | } 106 | 107 | @SuppressWarnings("SpellCheckingInspection") 108 | public static void vfmt(String tag, String fmt, Object... args) { 109 | if (checkInit()) { 110 | sLogEngine.verbose(tag, fmt, args); 111 | } 112 | } 113 | 114 | /** 115 | * Debug log. 116 | */ 117 | public static void d(String message) { 118 | if (checkInit()) { 119 | sLogEngine.debug(null, message); 120 | } 121 | } 122 | 123 | public static void d(String tag, String message) { 124 | if (checkInit()) { 125 | sLogEngine.debug(tag, message); 126 | } 127 | } 128 | 129 | public static void d(String message, Throwable throwable) { 130 | if (checkInit()) { 131 | sLogEngine.debug(null, throwable, message); 132 | } 133 | } 134 | 135 | public static void d(String tag, String message, Throwable throwable) { 136 | if (checkInit()) { 137 | sLogEngine.debug(tag, throwable, message); 138 | } 139 | } 140 | 141 | @SuppressWarnings("SpellCheckingInspection") 142 | public static void dfmt(String tag, String fmt, Object... args) { 143 | if (checkInit()) { 144 | sLogEngine.debug(tag, fmt, args); 145 | } 146 | } 147 | 148 | /** 149 | * info 150 | **/ 151 | public static void i(String message) { 152 | if (checkInit()) { 153 | sLogEngine.info(null, message); 154 | } 155 | } 156 | 157 | public static void i(String tag, String message) { 158 | if (checkInit()) { 159 | sLogEngine.info(tag, message); 160 | } 161 | } 162 | 163 | public static void i(String message, Throwable throwable) { 164 | if (checkInit()) { 165 | sLogEngine.verbose(null, throwable, message); 166 | } 167 | } 168 | 169 | public static void i(String tag, String message, Throwable throwable) { 170 | if (checkInit()) { 171 | sLogEngine.info(tag, throwable, message); 172 | } 173 | } 174 | 175 | @SuppressWarnings("SpellCheckingInspection") 176 | public static void ifmt(String tag, String fmt, Object... args) { 177 | if (checkInit()) { 178 | sLogEngine.info(tag, fmt, args); 179 | } 180 | } 181 | 182 | /** 183 | * warning 184 | **/ 185 | public static void w(String message) { 186 | if (checkInit()) { 187 | sLogEngine.warn(null, message); 188 | } 189 | } 190 | 191 | public static void w(String tag, String message) { 192 | if (checkInit()) { 193 | sLogEngine.warn(tag, message); 194 | } 195 | } 196 | 197 | public static void w(String message, Throwable throwable) { 198 | if (checkInit()) { 199 | sLogEngine.warn(null, throwable, message); 200 | } 201 | } 202 | 203 | public static void w(String tag, String message, Throwable throwable) { 204 | if (checkInit()) { 205 | sLogEngine.warn(tag, throwable, message); 206 | } 207 | } 208 | 209 | @SuppressWarnings("SpellCheckingInspection") 210 | public static void wfmt(String tag, String fmt, Object... args) { 211 | if (checkInit()) { 212 | sLogEngine.warn(tag, fmt, args); 213 | } 214 | } 215 | 216 | /** 217 | * warning 218 | **/ 219 | public static void e(String message) { 220 | if (checkInit()) { 221 | sLogEngine.error(null, message); 222 | } 223 | } 224 | 225 | public static void e(String tag, String message) { 226 | if (checkInit()) { 227 | sLogEngine.error(tag, message); 228 | } 229 | } 230 | 231 | public static void e(String message, Throwable throwable) { 232 | if (checkInit()) { 233 | sLogEngine.error(null, throwable, message); 234 | } 235 | } 236 | 237 | public static void e(String tag, String message, Throwable throwable) { 238 | if (checkInit()) { 239 | sLogEngine.error(tag, throwable, message); 240 | } 241 | } 242 | 243 | @SuppressWarnings("SpellCheckingInspection") 244 | public static void efmt(String tag, String fmt, Object... args) { 245 | if (checkInit()) { 246 | sLogEngine.error(tag, fmt, args); 247 | } 248 | } 249 | 250 | /** 251 | * wtf 252 | **/ 253 | public static void wtf(String message) { 254 | if (checkInit()) { 255 | sLogEngine.wtf(null, message); 256 | } 257 | } 258 | 259 | public static void wtf(String tag, String message) { 260 | if (checkInit()) { 261 | sLogEngine.wtf(tag, message); 262 | } 263 | } 264 | 265 | @SuppressWarnings("SpellCheckingInspection") 266 | public static void wtffmt(String tag, String fmt, Object... args) { 267 | if (checkInit()) { 268 | sLogEngine.wtf(tag, fmt, args); 269 | } 270 | } 271 | 272 | /** 273 | * Log event, logging message in an unique file. 274 | * Note that this api will log message in logcat according to {@link LogSetting#getEventPriority()}. 275 | **/ 276 | public static void event(String message) { 277 | if (checkInit()) { 278 | sLogEngine.event(null, message); 279 | } 280 | } 281 | 282 | /** 283 | * See {@linkplain #event(String)}. 284 | **/ 285 | public static void event(String tag, String message) { 286 | if (checkInit()) { 287 | sLogEngine.event(tag, message); 288 | } 289 | } 290 | 291 | /** 292 | * Sync log message to file. 293 | */ 294 | @WorkerThread 295 | public static void syncLog(int priority, String message) { 296 | if (checkInit()) { 297 | sLogEngine.syncLog(priority, null, message); 298 | } 299 | } 300 | 301 | /** 302 | * See {@linkplain #syncLog(int, String)}. 303 | **/ 304 | @WorkerThread 305 | public static void syncLog(int priority, String tag, String message) { 306 | if (checkInit()) { 307 | sLogEngine.syncLog(priority, tag, message); 308 | } 309 | } 310 | 311 | /** 312 | * others 313 | **/ 314 | 315 | /** 316 | * get all log files 317 | * 318 | * @param mode mode for filtering log files, support '|' operation, 319 | * see {@link LogSetting#LOG}, {@link LogSetting#EVENT} 320 | */ 321 | public static File[] getLogFiles(int mode) { 322 | if (checkInit()) { 323 | return sLogEngine.queryFiles(mode); 324 | } 325 | return null; 326 | } 327 | 328 | /** 329 | * get log files by day 330 | * 331 | * @param date retain null if today 332 | */ 333 | public static File[] getLogFilesByDate(int mode, Date date) { 334 | if (checkInit()) { 335 | if (date == null) { 336 | date = new Date(); // today 337 | } 338 | return sLogEngine.queryFilesByDate(mode, date.getTime()); 339 | } 340 | return null; 341 | } 342 | 343 | @Deprecated 344 | @WorkerThread 345 | public static File zippingLogFiles(int mode) { 346 | return zippingLogFiles(mode, null); 347 | } 348 | 349 | @Deprecated 350 | @WorkerThread 351 | public static File zippingLogFilesByDate(int mode, Date date) { 352 | return zippingLogFilesByDate(mode, date, null); 353 | } 354 | 355 | /** 356 | * Zipping log files and return the zip file. 357 | */ 358 | @WorkerThread 359 | public static File zippingLogFiles(int mode, List attaches) { 360 | if (checkInit()) { 361 | return sLogEngine.zippingFiles(mode, attaches); 362 | } 363 | return null; 364 | } 365 | 366 | /** 367 | * See {@linkplain #zippingLogFiles(int)}. 368 | **/ 369 | @WorkerThread 370 | public static File zippingLogFilesByDate(int mode, Date date, List attaches) { 371 | if (checkInit()) { 372 | if (date == null) { 373 | date = new Date(); // today 374 | } 375 | return sLogEngine.zippingFiles(mode, date.getTime(), attaches); 376 | } 377 | return null; 378 | } 379 | 380 | /** 381 | * Get log file's directory. 382 | */ 383 | public static File getLogDir() { 384 | if (checkInit()) { 385 | return sLogEngine.getSetting().getLogDirectory(); 386 | } 387 | return null; 388 | } 389 | 390 | /** 391 | * Delete existing log files. 392 | */ 393 | public static void deleteLogs() { 394 | if (checkInit()) { 395 | InternalUtils.delete(sLogEngine.getSetting().getLogDirectory()); 396 | } 397 | } 398 | 399 | /** 400 | * Package accessible for testcase. 401 | */ 402 | static LogEngine getLogger() { 403 | return sLogEngine; 404 | } 405 | 406 | /** 407 | * Package accessible for testcase. 408 | */ 409 | static LogSetting getSetting() { 410 | if (checkInit()) { 411 | return sLogEngine.getSetting(); 412 | } 413 | return null; 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/Executor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import moe.studio.dispatcher.Task; 8 | 9 | /** 10 | * @author kaede 11 | * @version date 16/9/22 12 | */ 13 | 14 | @SuppressWarnings("WeakerAccess") 15 | class Executor { 16 | 17 | private static final int DELAY_MILLIS = 2000; 18 | private static Task.Dispatcher sDispatcher; 19 | 20 | private static void ensureHandler() { 21 | if (sDispatcher == null) { 22 | synchronized (Executor.class) { 23 | if (sDispatcher == null) { 24 | sDispatcher = Task.Dispatchers.newSimpleDispatcher(); 25 | sDispatcher.start(); 26 | } 27 | } 28 | } 29 | } 30 | 31 | public static void post(Runnable runnable) { 32 | if (runnable == null) { 33 | return; 34 | } 35 | ensureHandler(); 36 | sDispatcher.post(runnable); 37 | } 38 | 39 | public static void post(int what, Runnable runnable) { 40 | if (runnable == null) { 41 | return; 42 | } 43 | ensureHandler(); 44 | sDispatcher.postDelay(what, runnable, DELAY_MILLIS); 45 | 46 | } 47 | 48 | public static boolean has(int what) { 49 | ensureHandler(); 50 | return sDispatcher.has(what); 51 | } 52 | 53 | public static void setDispatcher(Task.Dispatcher dispatcher) { 54 | if (dispatcher != null) { 55 | sDispatcher = dispatcher; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/Files.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.support.annotation.Nullable; 8 | import android.support.annotation.WorkerThread; 9 | import android.support.v4.util.Pools; 10 | 11 | import java.io.File; 12 | import java.io.FileOutputStream; 13 | import java.io.FilenameFilter; 14 | import java.io.IOException; 15 | import java.io.OutputStreamWriter; 16 | import java.io.PrintWriter; 17 | import java.io.RandomAccessFile; 18 | import java.nio.channels.FileChannel; 19 | import java.nio.channels.FileLock; 20 | import java.text.ParseException; 21 | import java.text.SimpleDateFormat; 22 | import java.util.Calendar; 23 | import java.util.Date; 24 | import java.util.List; 25 | import java.util.Locale; 26 | 27 | @SuppressWarnings("WeakerAccess") 28 | class Files { 29 | 30 | private static Files sInstance; 31 | 32 | static final String LOG_FILE_EXTENSION = ".log"; 33 | static final String EVENT_FILE_EXTENSION = ".event"; 34 | static final String ZIP_FILE_EXTENSION = ".zip"; 35 | static final String FILE_HYPHEN = "-"; 36 | 37 | private final LogSetting mSetting; 38 | private final LogFormatter mFormatter; 39 | private final SimpleDateFormat mNameFormatter = new SimpleDateFormat( 40 | "yyyyMMdd", Locale.getDefault()); 41 | private final byte[] mLock = new byte[0]; 42 | 43 | private Files(LogSetting setting) { 44 | mSetting = setting; 45 | mFormatter = setting.getLogFormatter(); 46 | } 47 | 48 | public static Files instance(LogSetting setting) { 49 | if (sInstance == null) { 50 | synchronized (Files.class) { 51 | if (sInstance == null) { 52 | sInstance = new Files(setting); 53 | } 54 | } 55 | } 56 | return sInstance; 57 | } 58 | 59 | public static void release() { 60 | sInstance = null; 61 | } 62 | 63 | // ROOT_DIR/20160927-main.log 64 | @Nullable 65 | public File getLogFile() { 66 | if (mSetting.getLogDirectory() == null) { 67 | return null; 68 | } 69 | String date = mNameFormatter.format(System.currentTimeMillis()); 70 | return new File(mSetting.getLogDirectory(), date + FILE_HYPHEN + InternalUtils.getProcessName() 71 | + LOG_FILE_EXTENSION); 72 | } 73 | 74 | // ROOT_DIR/20160927-main.event 75 | @Nullable 76 | public File getEventFile() { 77 | if (mSetting.getLogDirectory() == null) { 78 | return null; 79 | } 80 | String date = mNameFormatter.format(System.currentTimeMillis()); 81 | return new File(mSetting.getLogDirectory(), date + FILE_HYPHEN + InternalUtils.getProcessName() 82 | + EVENT_FILE_EXTENSION) ; 83 | } 84 | 85 | // ROOT_DIR/20160927-all.zip 86 | @Nullable 87 | public File getZipFile(int mode) { 88 | if (mSetting.getLogDirectory() == null) { 89 | return null; 90 | } 91 | 92 | String suffix; 93 | switch (mode) { 94 | case LogSetting.EVENT: 95 | suffix = "event"; 96 | break; 97 | case LogSetting.LOG: 98 | suffix = "log"; 99 | break; 100 | case LogSetting.LOG | LogSetting.EVENT: 101 | default: 102 | suffix = "all"; 103 | break; 104 | } 105 | 106 | String date = mNameFormatter.format(System.currentTimeMillis()); 107 | return new File(mSetting.getLogDirectory(), date + FILE_HYPHEN + suffix 108 | + ZIP_FILE_EXTENSION); 109 | } 110 | 111 | public boolean canWrite(File file) { 112 | try { 113 | InternalUtils.checkCreateFile(file); 114 | } catch (IOException e) { 115 | Logger.w( "Can not create file.", e); 116 | return false; 117 | } 118 | 119 | if (!file.canWrite()) { 120 | Logger.w("Log file is not allowed to be written."); 121 | return false; 122 | } 123 | 124 | return true; 125 | } 126 | 127 | @WorkerThread 128 | public void writeToFile(List logMessages, File file) { 129 | if (!file.exists()) { 130 | Logger.w("Log file not exist, can not write!"); 131 | return; 132 | } 133 | 134 | RandomAccessFile lockRaf = null; 135 | FileChannel lockChannel = null; 136 | FileLock fileLock = null; 137 | PrintWriter printWriter = null; 138 | 139 | try { 140 | lockRaf = new RandomAccessFile(file, "rw"); 141 | lockChannel = lockRaf.getChannel(); 142 | fileLock = lockChannel.lock(); 143 | 144 | synchronized (mLock) { 145 | FileOutputStream fos = new FileOutputStream(file, true); 146 | OutputStreamWriter writer = new OutputStreamWriter(fos, "utf-8"); 147 | printWriter = new PrintWriter(writer); 148 | 149 | for (LogMessage logMessage : logMessages) { 150 | printWriter.println(logMessage.buildMessage(mFormatter)); 151 | } 152 | } 153 | } catch (IOException e) { 154 | Logger.w(e); 155 | 156 | } finally { 157 | InternalUtils.closeQuietly(printWriter); 158 | if (fileLock != null) { 159 | try { 160 | fileLock.release(); 161 | } catch (IOException e) { 162 | Logger.w(e); 163 | } 164 | } 165 | InternalUtils.closeQuietly(lockChannel); 166 | InternalUtils.closeQuietly(lockRaf); 167 | } 168 | } 169 | 170 | @WorkerThread 171 | public void writeToFile(LogMessage logMessage, File file) { 172 | if (!file.exists()) { 173 | Logger.w("Log file not exist, can not write!"); 174 | return; 175 | } 176 | RandomAccessFile lockRaf = null; 177 | FileChannel lockChannel = null; 178 | FileLock fileLock = null; 179 | PrintWriter printWriter = null; 180 | 181 | try { 182 | lockRaf = new RandomAccessFile(file, "rw"); 183 | lockChannel = lockRaf.getChannel(); 184 | fileLock = lockChannel.lock(); 185 | 186 | synchronized (mLock) { 187 | FileOutputStream fos = new FileOutputStream(file, true); 188 | OutputStreamWriter writer = new OutputStreamWriter(fos, "utf-8"); 189 | printWriter = new PrintWriter(writer); 190 | printWriter.println(logMessage.buildMessage(mFormatter)); 191 | } 192 | } catch (IOException e) { 193 | Logger.w(e); 194 | 195 | } finally { 196 | InternalUtils.closeQuietly(printWriter); 197 | if (fileLock != null) { 198 | try { 199 | fileLock.release(); 200 | } catch (IOException e) { 201 | Logger.w(e); 202 | } 203 | } 204 | InternalUtils.closeQuietly(lockChannel); 205 | InternalUtils.closeQuietly(lockRaf); 206 | } 207 | } 208 | 209 | @WorkerThread 210 | public void cleanExpiredLogs() { 211 | File folder = mSetting.getLogDirectory(); 212 | if (folder == null) { 213 | Logger.w("Log directory is null."); 214 | return; 215 | } 216 | 217 | if (folder.exists() && folder.isDirectory()) { 218 | File[] allFiles = folder.listFiles(new FilenameFilter() { 219 | @Override 220 | public boolean accept(File dir, String fileName) { 221 | return fileName.contains(FILE_HYPHEN) && 222 | (fileName.endsWith(LOG_FILE_EXTENSION) 223 | || fileName.endsWith(EVENT_FILE_EXTENSION) 224 | || fileName.endsWith(ZIP_FILE_EXTENSION)); 225 | } 226 | }); 227 | 228 | for (File file : allFiles) { 229 | String fileName = file.getName(); 230 | String fileDateInfo = getFileNameWithoutExtension(fileName); 231 | 232 | if (fileDateInfo == null) { 233 | continue; 234 | } 235 | 236 | if (isExpired(fileDateInfo)) { 237 | InternalUtils.delete(file); 238 | } 239 | } 240 | } 241 | } 242 | 243 | private String getFileNameWithoutExtension(String fileName) { 244 | int index = fileName.indexOf(FILE_HYPHEN); 245 | if (index == -1) return null; 246 | 247 | return fileName.substring(0, index); 248 | } 249 | 250 | private boolean isExpired(String dateStr) { 251 | boolean canDel; 252 | Calendar calendar = Calendar.getInstance(); 253 | calendar.add(Calendar.DAY_OF_MONTH, -1 * mSetting.getExpiredDay()); 254 | Date expiredDate = calendar.getTime(); 255 | try { 256 | Date createDate = mNameFormatter.parse(dateStr); 257 | canDel = createDate.before(expiredDate); 258 | } catch (ParseException e) { 259 | canDel = false; 260 | } 261 | return canDel; 262 | } 263 | 264 | public File[] queryFilesByDate(final int mode, long ms) { 265 | File folder = mSetting.getLogDirectory(); 266 | if (folder == null) { 267 | Logger.w("Log directory is null."); 268 | return null; 269 | } 270 | 271 | final String name = mNameFormatter.format(new Date(ms)); 272 | if (folder.exists() && folder.isDirectory()) { 273 | return folder.listFiles(new FilenameFilter() { 274 | @Override 275 | public boolean accept(File dir, String filename) { 276 | switch (mode) { 277 | case LogSetting.EVENT: 278 | return filename.startsWith(name) && filename.contains(FILE_HYPHEN) 279 | && filename.endsWith(EVENT_FILE_EXTENSION); 280 | case LogSetting.LOG: 281 | return filename.startsWith(name) && filename.contains(FILE_HYPHEN) 282 | && filename.endsWith(LOG_FILE_EXTENSION); 283 | case LogSetting.LOG | LogSetting.EVENT: 284 | default: 285 | return filename.startsWith(name) && filename.contains(FILE_HYPHEN) 286 | && (filename.endsWith(LOG_FILE_EXTENSION) 287 | || filename.endsWith(EVENT_FILE_EXTENSION)); 288 | } 289 | } 290 | }); 291 | 292 | } 293 | return null; 294 | } 295 | 296 | public File[] queryFiles(final int mode) { 297 | File folder = mSetting.getLogDirectory(); 298 | if (folder == null) { 299 | Logger.w("Log directory is null."); 300 | return null; 301 | } 302 | 303 | if (folder.exists() && folder.isDirectory()) { 304 | return folder.listFiles(new FilenameFilter() { 305 | @Override 306 | public boolean accept(File dir, String filename) { 307 | switch (mode) { 308 | case LogSetting.EVENT: 309 | return filename.contains(FILE_HYPHEN) 310 | && filename.endsWith(EVENT_FILE_EXTENSION); 311 | case LogSetting.LOG: 312 | return filename.contains(FILE_HYPHEN) 313 | && filename.endsWith(LOG_FILE_EXTENSION); 314 | case LogSetting.LOG | LogSetting.EVENT: 315 | default: 316 | return filename.contains(FILE_HYPHEN) 317 | && (filename.endsWith(LOG_FILE_EXTENSION) 318 | || filename.endsWith(EVENT_FILE_EXTENSION)); 319 | } 320 | } 321 | }); 322 | 323 | } 324 | return null; 325 | } 326 | 327 | 328 | /** 329 | * Message Entity 330 | */ 331 | @SuppressWarnings({"WeakerAccess", "unused"}) 332 | public static class LogMessage { 333 | private static final Pools.SynchronizedPool sPool = 334 | new Pools.SynchronizedPool<>(20); 335 | public int priority; 336 | public long time; 337 | public String tag; 338 | public String msg; 339 | public String thread; 340 | 341 | public LogMessage() { 342 | 343 | } 344 | 345 | public static LogMessage obtain() { 346 | LogMessage instance = sPool.acquire(); 347 | return (instance != null) ? instance : new LogMessage(); 348 | } 349 | 350 | public void setMessage(int priority, long time, String tag, String thread, String msg) { 351 | this.priority = priority; 352 | this.time = time; 353 | this.tag = tag; 354 | this.thread = thread; 355 | this.msg = msg; 356 | } 357 | 358 | public String buildMessage(LogFormatter formatter) { 359 | return formatter.buildMessage(priority, time, tag, thread, msg); 360 | } 361 | 362 | public void recycle() { 363 | sPool.release(this); 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/InternalUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.os.Process; 8 | import android.text.TextUtils; 9 | 10 | import org.apache.commons.io.FileUtils; 11 | import org.apache.commons.io.IOUtils; 12 | 13 | import java.io.BufferedInputStream; 14 | import java.io.BufferedOutputStream; 15 | import java.io.Closeable; 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.FileOutputStream; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.util.List; 22 | import java.util.zip.Deflater; 23 | import java.util.zip.ZipEntry; 24 | import java.util.zip.ZipOutputStream; 25 | 26 | /** 27 | * @author kaede 28 | * @version date 16/9/23 29 | */ 30 | @SuppressWarnings({"WeakerAccess", "unused"}) 31 | class InternalUtils { 32 | 33 | @SuppressWarnings("SpellCheckingInspection") 34 | static String getProcessName() { 35 | int pid = Process.myPid(); 36 | StringBuilder cmdline = new StringBuilder(); 37 | InputStream is = null; 38 | 39 | try { 40 | is = new FileInputStream("/proc/" + pid + "/cmdline"); 41 | for (; ; ) { 42 | int c = is.read(); 43 | if (c < 0) 44 | break; 45 | cmdline.append((char) c); 46 | } 47 | } catch (Exception ignore) { 48 | } finally { 49 | closeQuietly(is); 50 | } 51 | 52 | String pname = cmdline.toString().trim(); 53 | if (!pname.contains(":")) { 54 | return "main"; 55 | 56 | } else { 57 | return pname.substring(pname.indexOf(":") + 1); 58 | } 59 | } 60 | 61 | static boolean exist(String path) { 62 | return !TextUtils.isEmpty(path) && (new File(path).exists()); 63 | } 64 | 65 | @SuppressWarnings("ResultOfMethodCallIgnored") 66 | static void checkCreateFile(File file) throws IOException { 67 | if (file == null) { 68 | throw new IOException("File is null."); 69 | } 70 | if (file.exists()) { 71 | if (!file.isDirectory()) { 72 | return; 73 | } 74 | delete(file); 75 | } 76 | File parentFile = file.getParentFile(); 77 | if (!parentFile.exists()) { 78 | parentFile.mkdirs(); 79 | } 80 | if (!file.createNewFile()) { 81 | throw new IOException("Create file fail, file already exists."); 82 | } 83 | } 84 | 85 | @SuppressWarnings("ResultOfMethodCallIgnored") 86 | static void checkCreateDir(File file) throws IOException { 87 | if (file == null) { 88 | throw new IOException("Dir is null."); 89 | } 90 | if (file.exists()) { 91 | if (file.isDirectory()) { 92 | return; 93 | } 94 | if (!delete(file)) { 95 | throw new IOException("Fail to delete existing file, file = " 96 | + file.getAbsolutePath()); 97 | } 98 | file.mkdir(); 99 | } else { 100 | file.mkdirs(); 101 | } 102 | if (!file.exists() || !file.isDirectory()) { 103 | throw new IOException("Fail to create dir, dir = " + file.getAbsolutePath()); 104 | } 105 | } 106 | 107 | static boolean delete(File file) { 108 | return FileUtils.deleteQuietly(file); 109 | } 110 | 111 | static void closeQuietly(Closeable closeable) { 112 | IOUtils.closeQuietly(closeable); 113 | } 114 | 115 | static String getStackTraceString(Throwable tr) { 116 | return android.util.Log.getStackTraceString(tr); 117 | } 118 | 119 | static boolean zippingFiles(List files, File output) { 120 | if (files == null || files.size() == 0) return false; 121 | 122 | BufferedInputStream in = null; 123 | ZipOutputStream out = null; 124 | try { 125 | FileOutputStream fo = new FileOutputStream(output); 126 | out = new ZipOutputStream(new BufferedOutputStream(fo)); 127 | out.setLevel(Deflater.BEST_COMPRESSION); // best compress 128 | 129 | for (File file : files) { 130 | FileInputStream fi = new FileInputStream(file); 131 | in = new BufferedInputStream(fi, 2048); 132 | String entryName = file.getName(); 133 | ZipEntry entry = new ZipEntry(entryName); 134 | out.putNextEntry(entry); 135 | IOUtils.copy(in, out); 136 | in.close(); 137 | } 138 | return true; 139 | 140 | } catch (Exception e) { 141 | Logger.w(e); 142 | return false; 143 | 144 | } finally { 145 | IOUtils.closeQuietly(out); 146 | IOUtils.closeQuietly(in); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | interface Log { 8 | /** 9 | * Log a message. 10 | * 11 | * @param priority The current log level. 12 | * @param tag Log tag. 13 | * @param msg Log message content. 14 | */ 15 | void log(int priority, String tag, String msg); 16 | 17 | void onShutdown(); 18 | } 19 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogCatImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.text.TextUtils; 8 | 9 | @SuppressWarnings("WeakerAccess") 10 | class LogCatImpl implements Log { 11 | 12 | private static final int CHUNK_SIZE = 4000; 13 | 14 | private final String mEmptyMessage; 15 | private final LogSetting mSetting; 16 | private final boolean mShowThreadInfo; 17 | 18 | public LogCatImpl(LogSetting setting) { 19 | mSetting = setting; 20 | mShowThreadInfo = setting.isShowThreadInfo(); 21 | mEmptyMessage = setting.getLogFormatter().emptyMessage(); 22 | } 23 | 24 | @Override 25 | public void log(int priority, String tag, String msg) { 26 | if (mSetting.getLogcatPriority() == LogPriority.NONE || mSetting.getLogcatPriority() > priority) { 27 | return; 28 | } 29 | 30 | // AndroidLogcat may abort long message, just in case. 31 | separateMessageIfNeed(priority, tag, msg); 32 | } 33 | 34 | @Override 35 | public void onShutdown() { 36 | 37 | } 38 | 39 | private void separateMessageIfNeed(int priority, String tag, String msg) { 40 | if (TextUtils.isEmpty(msg)) { 41 | logMessage(priority, tag, mEmptyMessage); 42 | return; 43 | } 44 | 45 | byte[] bytes = msg.getBytes(); 46 | int length = bytes.length; 47 | 48 | if (length <= CHUNK_SIZE) { 49 | logMessage(priority, tag, msg); 50 | return; 51 | } 52 | 53 | for (int i = 0; i < length; i += CHUNK_SIZE) { 54 | int count = Math.min(length - i, CHUNK_SIZE); 55 | logMessage(priority, tag, new String(bytes, i, count)); 56 | } 57 | } 58 | 59 | private void logMessage(int priority, String tag, String chunk) { 60 | String[] lines = chunk.split(System.getProperty("line.separator")); 61 | for (String line : lines) { 62 | if (mShowThreadInfo) { 63 | line = "[" + Thread.currentThread().getName() + "] " + line; 64 | } 65 | logcat(priority, tag, line); 66 | } 67 | } 68 | 69 | private void logcat(int priority, String tag, String chunk) { 70 | android.util.Log.println(priority, tag, chunk); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.text.TextUtils; 8 | 9 | import java.io.File; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @SuppressWarnings("WeakerAccess") 15 | class LogEngine { 16 | 17 | private final int mEventPriority; 18 | private final String mDefaultTag; 19 | private final LogSetting mSetting; 20 | private final Log mLogCatImpl; 21 | private final Log mLogFileImpl; 22 | private final Log mLogFileSyncImpl; 23 | private final Log mLogEventImpl; 24 | private final Log mLogAdapter; 25 | private final Files mFiles; 26 | 27 | public LogEngine(LogSetting setting) { 28 | mSetting = setting; 29 | mEventPriority = setting.getEventPriority(); 30 | mDefaultTag = setting.getDefaultTag(); 31 | mLogAdapter = setting.getAdapter(); 32 | 33 | if (setting.getLogcatPriority() != LogPriority.NONE) { 34 | mLogCatImpl = new LogCatImpl(setting); 35 | } else { 36 | mLogCatImpl = null; 37 | } 38 | 39 | if (setting.getLogfilePriority() != LogPriority.NONE) { 40 | mLogFileImpl = new LogFileImpl(setting); 41 | mLogEventImpl = new LogEventImpl(setting); 42 | mLogFileSyncImpl = new LogFileSyncImpl(setting); 43 | } else { 44 | mLogFileImpl = null; 45 | mLogEventImpl = null; 46 | mLogFileSyncImpl = null; 47 | } 48 | 49 | mFiles = Files.instance(setting); 50 | cleanExpiredFiles(); 51 | } 52 | 53 | public void shutdown() { 54 | if (mLogCatImpl != null) { 55 | mLogCatImpl.onShutdown(); 56 | } 57 | if (mLogFileImpl != null) { 58 | mLogFileImpl.onShutdown(); 59 | } 60 | if (mLogEventImpl != null) { 61 | mLogEventImpl.onShutdown(); 62 | } 63 | if (mLogFileSyncImpl != null) { 64 | mLogFileSyncImpl.onShutdown(); 65 | } 66 | if (mLogAdapter != null) { 67 | mLogAdapter.onShutdown(); 68 | } 69 | 70 | Files.release(); 71 | } 72 | 73 | public void cleanExpiredFiles() { 74 | Executor.post(new Runnable() { 75 | @Override 76 | public void run() { 77 | try { 78 | mFiles.cleanExpiredLogs(); 79 | } catch (Throwable e) { 80 | Logger.w(e); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | /** 87 | * verbose 88 | **/ 89 | public void verbose(String tag, String fmt, Object... args) { 90 | log(LogPriority.VERBOSE, ensureTag(tag), formatMessage(fmt, args)); 91 | } 92 | 93 | public void verbose(String tag, Throwable throwable, String message) { 94 | log(LogPriority.VERBOSE, ensureTag(tag), formatThrowable(message, throwable)); 95 | } 96 | 97 | /** 98 | * debug 99 | **/ 100 | public void debug(String tag, String fmt, Object... args) { 101 | log(LogPriority.DEBUG, ensureTag(tag), formatMessage(fmt, args)); 102 | } 103 | 104 | public void debug(String tag, Throwable throwable, String message) { 105 | log(LogPriority.DEBUG, ensureTag(tag), formatThrowable(message, throwable)); 106 | } 107 | 108 | /** 109 | * info 110 | **/ 111 | public void info(String tag, String fmt, Object... args) { 112 | log(LogPriority.INFO, ensureTag(tag), formatMessage(fmt, args)); 113 | } 114 | 115 | public void info(String tag, Throwable throwable, String message) { 116 | log(LogPriority.INFO, ensureTag(tag), formatThrowable(message, throwable)); 117 | } 118 | 119 | /** 120 | * warning 121 | **/ 122 | public void warn(String tag, String fmt, Object... args) { 123 | log(LogPriority.WARN, ensureTag(tag), formatMessage(fmt, args)); 124 | } 125 | 126 | public void warn(String tag, Throwable throwable, String message) { 127 | log(LogPriority.WARN, ensureTag(tag), formatThrowable(message, throwable)); 128 | } 129 | 130 | /** 131 | * error 132 | **/ 133 | public void error(String tag, String fmt, Object... args) { 134 | log(LogPriority.ERROR, ensureTag(tag), formatMessage(fmt, args)); 135 | } 136 | 137 | public void error(String tag, Throwable throwable, String message) { 138 | log(LogPriority.ERROR, ensureTag(tag), formatThrowable(message, throwable)); 139 | } 140 | 141 | /** 142 | * wtf 143 | **/ 144 | public void wtf(String tag, String fmt, Object... args) { 145 | log(LogPriority.ASSERT, ensureTag(tag), formatMessage(fmt, args)); 146 | } 147 | 148 | /** 149 | * event 150 | **/ 151 | public void event(String tag, String message) { 152 | event(mEventPriority, ensureTag(tag), message); 153 | } 154 | 155 | public void syncLog(int priority, String tag, String message) { 156 | sync(priority, ensureTag(tag), message); 157 | } 158 | 159 | private String ensureTag(String tag) { 160 | return TextUtils.isEmpty(tag) ? mDefaultTag : tag; 161 | } 162 | 163 | private String formatMessage(String fmt, Object... args) { 164 | if (args == null) return fmt; 165 | 166 | String message; 167 | try { 168 | message = String.format(fmt, args); 169 | return message; 170 | 171 | } catch (Throwable e) { 172 | Logger.w(e); 173 | 174 | StringBuilder sb = new StringBuilder("format error, fmt = " + String.valueOf(fmt) 175 | + ", args = "); 176 | for (int i = 0; i < args.length; i++) { 177 | Object item = args[i]; 178 | sb.append(String.valueOf(item)); 179 | if (i != (args.length - 1)) sb.append(", "); 180 | } 181 | 182 | return sb.toString(); 183 | } 184 | } 185 | 186 | private String formatThrowable(String message, Throwable throwable) { 187 | return String.valueOf(message) + " : " 188 | + (throwable == null ? "null" : InternalUtils.getStackTraceString(throwable)); 189 | } 190 | 191 | private void log(int priority, String tag, String message) { 192 | if (mLogCatImpl != null) { 193 | mLogCatImpl.log(priority, tag, message); 194 | } 195 | 196 | if (mLogAdapter != null) { 197 | mLogAdapter.log(priority, tag, message); 198 | } 199 | 200 | if (mLogFileImpl != null) { 201 | mLogFileImpl.log(priority, tag, message); 202 | } 203 | } 204 | 205 | private void event(int priority, String tag, String message) { 206 | if (mLogCatImpl != null) { 207 | mLogCatImpl.log(priority, tag, message); 208 | } 209 | 210 | if (mLogAdapter != null) { 211 | mLogAdapter.log(priority, tag, message); 212 | } 213 | 214 | if (mLogEventImpl != null) { 215 | mLogEventImpl.log(priority, tag, message); 216 | } 217 | } 218 | 219 | private void sync(int priority, String tag, String message) { 220 | if (mLogCatImpl != null) { 221 | mLogCatImpl.log(priority, tag, message); 222 | } 223 | 224 | if (mLogAdapter != null) { 225 | mLogAdapter.log(priority, tag, message); 226 | } 227 | 228 | if (mLogFileSyncImpl != null) { 229 | mLogFileSyncImpl.log(priority, tag, message); 230 | } 231 | } 232 | 233 | public LogSetting getSetting() { 234 | return mSetting; 235 | } 236 | 237 | public File[] queryFilesByDate(int mode, long ms) { 238 | if (mFiles != null) { 239 | return mFiles.queryFilesByDate(mode, ms); 240 | } 241 | return null; 242 | } 243 | 244 | public File[] queryFiles(int mode) { 245 | if (mFiles != null) { 246 | return mFiles.queryFiles(mode); 247 | } 248 | return null; 249 | } 250 | 251 | public File zippingFiles(int mode, List attaches) { 252 | File zipFile = mFiles.getZipFile(mode); 253 | if (zipFile == null) { 254 | Logger.w("Log directory is null."); 255 | return null; 256 | } 257 | 258 | InternalUtils.delete(zipFile); 259 | List files = Arrays.asList(queryFiles(mode)); 260 | 261 | if (attaches != null) { 262 | List newList = new ArrayList<>(files); 263 | newList.addAll(attaches); 264 | files = newList; 265 | } 266 | 267 | if (InternalUtils.zippingFiles(files, zipFile)) { 268 | return zipFile; 269 | } 270 | 271 | return null; 272 | } 273 | 274 | public File zippingFiles(int mode, long ms, List attaches) { 275 | File zipFile = mFiles.getZipFile(mode); 276 | if (zipFile == null) { 277 | Logger.w("Log directory is null."); 278 | return null; 279 | } 280 | 281 | InternalUtils.delete(zipFile); 282 | List files = Arrays.asList(queryFilesByDate(mode, ms)); 283 | 284 | if (attaches != null) { 285 | List newList = new ArrayList<>(files); 286 | newList.addAll(attaches); 287 | files = newList; 288 | } 289 | 290 | if (InternalUtils.zippingFiles(files, zipFile)) { 291 | return zipFile; 292 | } 293 | 294 | return null; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogEventImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.support.annotation.WorkerThread; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | @SuppressWarnings("WeakerAccess") 15 | class LogEventImpl implements Log { 16 | 17 | private static final int EVENT_TASK_ID = 0x333; 18 | 19 | private int mWriteCount; 20 | private final byte[] mLock = new byte[0]; 21 | private final LogSetting mSetting; 22 | private final Files mFiles; 23 | private final File mEventFile; 24 | private final List mCacheQueue; 25 | 26 | private final Runnable mWriteTask = new Runnable() { 27 | @Override 28 | public void run() { 29 | writeToFile(); 30 | } 31 | }; 32 | 33 | public LogEventImpl(LogSetting setting) { 34 | mSetting = setting; 35 | mFiles = Files.instance(setting); 36 | mCacheQueue = new LinkedList<>(); 37 | mEventFile = mFiles.getEventFile(); 38 | 39 | try { 40 | InternalUtils.checkCreateFile(mEventFile); 41 | } catch (IOException e) { 42 | Logger.w("Can not create file.", e); 43 | } 44 | } 45 | 46 | @Override 47 | public void log(int priority, String tag, String msg) { 48 | if (mSetting.getLogfilePriority() == LogPriority.NONE || mSetting.getLogfilePriority() > priority) 49 | return; 50 | 51 | // get logMessage from Object Pools 52 | Files.LogMessage logMessage = Files.LogMessage.obtain(); 53 | logMessage.setMessage(priority, System.currentTimeMillis(), tag, Thread.currentThread().getName(), msg); 54 | 55 | // add to list 56 | synchronized (mLock) { 57 | mCacheQueue.add(logMessage); 58 | } 59 | 60 | // write to file 61 | if (!Executor.has(EVENT_TASK_ID)) { 62 | Executor.post(EVENT_TASK_ID, mWriteTask); 63 | } 64 | } 65 | 66 | @Override 67 | public void onShutdown() { 68 | if (mSetting.debuggable()) { 69 | Logger.w("LogEvent is shutdown, file written count = " + mWriteCount); 70 | } 71 | } 72 | 73 | 74 | @WorkerThread 75 | private void writeToFile() { 76 | if (mFiles.canWrite(mEventFile)) { 77 | List list; 78 | 79 | synchronized (mLock) { 80 | list = new LinkedList<>(mCacheQueue); 81 | mCacheQueue.clear(); 82 | } 83 | 84 | mFiles.writeToFile(list, mEventFile); 85 | if (mSetting.debuggable()) { 86 | mWriteCount ++; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogFileImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.support.annotation.WorkerThread; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | import moe.studio.log.Files.LogMessage; 15 | 16 | @SuppressWarnings("WeakerAccess") 17 | class LogFileImpl implements Log { 18 | 19 | private static final int LOG_TASK_ID = 0x222; 20 | 21 | private int mWriteCount; 22 | private final byte[] mLock = new byte[0]; 23 | private final LogSetting mSetting; 24 | private final Files mFiles; 25 | private final File mLogFile; 26 | private final List mCacheQueue; 27 | 28 | private final Runnable mWriteTask = new Runnable() { 29 | @Override 30 | public void run() { 31 | writeToFile(); 32 | } 33 | }; 34 | 35 | public LogFileImpl(LogSetting setting) { 36 | mSetting = setting; 37 | mFiles = Files.instance(setting); 38 | mCacheQueue = new LinkedList<>(); 39 | mLogFile = mFiles.getLogFile(); 40 | 41 | try { 42 | InternalUtils.checkCreateFile(mLogFile); 43 | } catch (IOException e) { 44 | Logger.w("Can not create file.", e); 45 | } 46 | } 47 | 48 | @Override 49 | public void log(int priority, String tag, String msg) { 50 | if (mSetting.getLogfilePriority() == LogPriority.NONE 51 | || mSetting.getLogfilePriority() > priority) { 52 | return; 53 | } 54 | 55 | // get logMessage from Object Pools 56 | LogMessage logMessage = LogMessage.obtain(); 57 | logMessage.setMessage(priority, System.currentTimeMillis(), tag, Thread.currentThread().getName(), msg); 58 | 59 | // add to list 60 | synchronized (mLock) { 61 | mCacheQueue.add(logMessage); 62 | } 63 | 64 | // write to file 65 | if (!Executor.has(LOG_TASK_ID)) { 66 | Executor.post(LOG_TASK_ID, mWriteTask); 67 | } 68 | } 69 | 70 | @Override 71 | public void onShutdown() { 72 | if (mSetting.debuggable()) { 73 | Logger.w("LogFile is shutdown, file written count = " + mWriteCount); 74 | } 75 | } 76 | 77 | @WorkerThread 78 | private void writeToFile() { 79 | if (mFiles.canWrite(mLogFile)) { 80 | List list; 81 | 82 | synchronized (mLock) { 83 | list = new LinkedList<>(mCacheQueue); 84 | mCacheQueue.clear(); 85 | } 86 | 87 | mFiles.writeToFile(list, mLogFile); 88 | if (mSetting.debuggable()) { 89 | mWriteCount ++; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogFileSyncImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.support.annotation.WorkerThread; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | @SuppressWarnings("WeakerAccess") 13 | class LogFileSyncImpl implements Log { 14 | 15 | private final LogSetting mSetting; 16 | private final File mLogFile; 17 | private final Files mFiles; 18 | 19 | public LogFileSyncImpl(LogSetting setting) { 20 | mSetting = setting; 21 | mFiles = Files.instance(setting); 22 | mLogFile = mFiles.getLogFile(); 23 | 24 | try { 25 | InternalUtils.checkCreateFile(mLogFile); 26 | } catch (IOException e) { 27 | Logger.w("Can not create file.", e); 28 | } 29 | } 30 | 31 | @WorkerThread 32 | public void log(int priority, String tag, String msg) { 33 | if (mSetting.getLogfilePriority() == LogPriority.NONE 34 | || mSetting.getLogfilePriority() > priority) { 35 | return; 36 | } 37 | 38 | // get logMessage from Object Pools 39 | Files.LogMessage logMessage = Files.LogMessage.obtain(); 40 | logMessage.setMessage(priority, System.currentTimeMillis(), tag, 41 | Thread.currentThread().getName(), msg); 42 | 43 | if (mFiles.canWrite(mLogFile)) { 44 | mFiles.writeToFile(logMessage, mLogFile); 45 | } 46 | } 47 | 48 | @Override 49 | public void onShutdown() { 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | /** 8 | * Log Message formatter 9 | * 10 | * @author kaede 11 | * @version date 16/9/25 12 | */ 13 | 14 | public interface LogFormatter { 15 | 16 | /** 17 | * Get empty log message style. 18 | */ 19 | String emptyMessage(); 20 | 21 | /** 22 | * Build a log message to log in file. 23 | */ 24 | String buildMessage(int priority, long time, String tag, String thread, String msg); 25 | } 26 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogFormatterImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import java.text.SimpleDateFormat; 8 | import java.util.Locale; 9 | 10 | /** 11 | * @author kaede 12 | * @version date 16/9/25 13 | */ 14 | 15 | @SuppressWarnings("WeakerAccess") 16 | class LogFormatterImpl implements LogFormatter { 17 | 18 | private final boolean mShowThreadInfo; 19 | private final SimpleDateFormat mDateFormatter; 20 | 21 | public LogFormatterImpl() { 22 | mShowThreadInfo = false; 23 | mDateFormatter = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.getDefault()); 24 | } 25 | 26 | public LogFormatterImpl(LogSetting setting) { 27 | mShowThreadInfo = setting.isShowThreadInfo(); 28 | mDateFormatter = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.getDefault()); 29 | } 30 | 31 | @Override 32 | public String emptyMessage() { 33 | return "Empty/NULL"; 34 | } 35 | 36 | @Override 37 | public String buildMessage(int priority, long time, String tag, String thread, String msg) { 38 | StringBuilder sb = new StringBuilder(); 39 | sb.append(mDateFormatter.format(time)) 40 | .append(" ") 41 | .append(LogPriority.getName(priority)) 42 | .append("/") 43 | .append(tag) 44 | .append(" "); 45 | 46 | if (mShowThreadInfo) { 47 | sb.append("[") 48 | .append(thread) 49 | .append("] ") 50 | .append(msg); 51 | } else { 52 | sb.append(" ") 53 | .append(msg); 54 | } 55 | 56 | return sb.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogPriority.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.util.Log; 8 | 9 | /** 10 | * Log Priority, see {@link LogSetting} 11 | * 12 | * @author kaede 13 | * @version date 16/9/22 14 | */ 15 | 16 | @SuppressWarnings("WeakerAccess") 17 | public class LogPriority { 18 | 19 | public static final int VERBOSE = Log.VERBOSE; 20 | public static final int DEBUG = Log.DEBUG; 21 | public static final int INFO = Log.INFO; 22 | public static final int WARN = Log.WARN; 23 | public static final int ERROR = Log.ERROR; 24 | public static final int ASSERT = Log.ASSERT; 25 | public static final int NONE = ASSERT + 1; // Do not log. 26 | 27 | /** 28 | * Get name for the current log level. 29 | */ 30 | public static String getName(int priority) { 31 | switch (priority) { 32 | case VERBOSE: 33 | return "VERBOSE"; 34 | case DEBUG: 35 | return "DEBUG"; 36 | case INFO: 37 | return "INFO"; 38 | case WARN: 39 | return "WARN"; 40 | case ERROR: 41 | return "ERROR"; 42 | case ASSERT: 43 | return "ASSERT"; 44 | case NONE: 45 | return "NONE"; 46 | default: 47 | return "UNKNOWN"; 48 | } 49 | } 50 | 51 | /** 52 | * Whether the given log level in valid. 53 | */ 54 | public static boolean isValid(int priority) { 55 | return (priority >= VERBOSE && priority <= NONE); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/LogSetting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import android.content.Context; 8 | import android.support.annotation.IntRange; 9 | import android.text.TextUtils; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | 14 | /** 15 | * BLog config class, use {@link LogSetting.Builder} to custom your config. 16 | * 17 | * @author kaede 18 | * @version date 16/9/22 19 | */ 20 | 21 | @SuppressWarnings({"WeakerAccess", "unused"}) 22 | public class LogSetting { 23 | 24 | static final String DEFAULT_DIR = "blog"; 25 | 26 | // QUERY MODE 27 | public static final int LOG = 0x0001; 28 | public static final int EVENT = 0x0010; 29 | 30 | private int mExpiredDay; 31 | private int mLogcatPriority; 32 | private int mLogfilePriority; 33 | private int mEventPriority; 34 | private boolean mShowThreadInfo; 35 | private boolean mDebuggable; 36 | private String mDefaultTag; 37 | private File mLogDir; 38 | private LogFormatter mFormatter; 39 | private Log mAdapter; 40 | 41 | private LogSetting() { 42 | } 43 | 44 | /** 45 | * Get level to check whether to logcat or not. 46 | */ 47 | public int getLogcatPriority() { 48 | return mLogcatPriority; 49 | } 50 | 51 | /** 52 | * Get level to check whether to log file or not. 53 | */ 54 | public int getLogfilePriority() { 55 | return mLogfilePriority; 56 | } 57 | 58 | /** 59 | * Get level to check whether to log event or not. 60 | */ 61 | public int getEventPriority() { 62 | return mEventPriority; 63 | } 64 | 65 | /** 66 | * Get log files' base dir, using {@link #getLogcatPriority()} instead. 67 | */ 68 | @Deprecated 69 | public String getLogDir() { 70 | return mLogDir.getAbsolutePath(); 71 | } 72 | 73 | /** 74 | * Get log files' base dir. 75 | */ 76 | public File getLogDirectory() { 77 | return mLogDir; 78 | } 79 | 80 | /** 81 | * Get log files' expired day. 82 | */ 83 | public int getExpiredDay() { 84 | return mExpiredDay; 85 | } 86 | 87 | /** 88 | * Get log format. 89 | */ 90 | public LogFormatter getLogFormatter() { 91 | return mFormatter; 92 | } 93 | 94 | /** 95 | * Get default tag. 96 | */ 97 | public String getDefaultTag() { 98 | return mDefaultTag; 99 | } 100 | 101 | /** 102 | * Whether or not to show thread info. 103 | */ 104 | public boolean isShowThreadInfo() { 105 | return mShowThreadInfo; 106 | } 107 | 108 | /** 109 | * Whether it is debug mode. 110 | */ 111 | public boolean debuggable() { 112 | return mDebuggable; 113 | } 114 | 115 | /** 116 | * Get user {@link Log} implement. 117 | */ 118 | public Log getAdapter() { 119 | return mAdapter; 120 | } 121 | 122 | public static class Builder { 123 | 124 | private Context mContext; 125 | private int mExpiredDay; 126 | private int mLogcatPriority = -1; 127 | private int mLogfilePriority = -1; 128 | private int mEventPriority; 129 | private boolean mShowThreadInfo; 130 | private String mDefaultTag; 131 | private File mLogDir; 132 | private LogFormatter mFormatter; 133 | private Log mAdapter; 134 | private boolean mDebuggable; 135 | 136 | public Builder(Context context) { 137 | mContext = context; 138 | mExpiredDay = 2; 139 | mEventPriority = LogPriority.INFO; 140 | mDefaultTag = "BLOG"; 141 | mDebuggable = BuildConfig.DEBUG; 142 | } 143 | 144 | /** 145 | * Set log files' base dir, using {@link #setLogDirectory(File)} instead. 146 | */ 147 | @Deprecated 148 | public Builder setLogDir(String path) { 149 | if (!TextUtils.isEmpty(path)) { 150 | mLogDir = new File(path); 151 | } 152 | return this; 153 | } 154 | /** 155 | * Set log files' base dir. 156 | */ 157 | public Builder setLogDirectory(File dir) { 158 | mLogDir = dir; 159 | return this; 160 | } 161 | 162 | /** 163 | * Set default tag. 164 | */ 165 | public Builder setDefaultTag(String defaultTag) { 166 | this.mDefaultTag = defaultTag; 167 | return this; 168 | } 169 | 170 | /** 171 | * Set level to check whether to logcat or not. 172 | */ 173 | public Builder setLogcatPriority(@IntRange(from = LogPriority.VERBOSE, to = LogPriority.NONE) 174 | int priority) { 175 | if (LogPriority.isValid(priority)) { 176 | mLogcatPriority = priority; 177 | } else { 178 | throw new RuntimeException("Priority is invalid."); 179 | } 180 | return this; 181 | } 182 | 183 | /** 184 | * Set level to check whether to log file or not. 185 | */ 186 | public Builder setLogfilePriority(@IntRange(from = LogPriority.VERBOSE, to = LogPriority.NONE) 187 | int priority) { 188 | if (LogPriority.isValid(priority)) { 189 | mLogfilePriority = priority; 190 | } else { 191 | throw new RuntimeException("Priority is invalid."); 192 | } 193 | return this; 194 | } 195 | 196 | /** 197 | * Set level to check whether to log file or not. 198 | */ 199 | public Builder setEventPriority(int priority) { 200 | if (LogPriority.isValid(priority)) { 201 | mEventPriority = priority; 202 | } else { 203 | throw new RuntimeException("Priority is invalid."); 204 | } 205 | return this; 206 | } 207 | 208 | /** 209 | * Set days to keep the current log file. 210 | */ 211 | public Builder setExpiredDay(int expiredDay) { 212 | if (mExpiredDay > 0) { 213 | mExpiredDay = expiredDay; 214 | } else { 215 | throw new RuntimeException("Expired day is invalid."); 216 | } 217 | return this; 218 | } 219 | 220 | /** 221 | * Set log format. 222 | */ 223 | public Builder setFormatter(LogFormatter formatter) { 224 | mFormatter = formatter; 225 | return this; 226 | } 227 | 228 | /** 229 | * Set whether to show thread info in log or not. 230 | */ 231 | public Builder showThreadInfo(boolean showThreadInfo) { 232 | mShowThreadInfo = showThreadInfo; 233 | return this; 234 | } 235 | 236 | /** 237 | * Set whether it is debug mode. 238 | */ 239 | public Builder debuggable(boolean debuggable) { 240 | mDebuggable = debuggable; 241 | return this; 242 | } 243 | 244 | /** 245 | * Set user {@link Log} implement. 246 | */ 247 | public Builder setAdapter(Log adapter) { 248 | mAdapter = adapter; 249 | return this; 250 | } 251 | 252 | public LogSetting build() { 253 | 254 | LogSetting setting = new LogSetting(); 255 | setting.mLogDir = mLogDir; 256 | setting.mDebuggable = mDebuggable; 257 | setting.mExpiredDay = mExpiredDay; 258 | setting.mLogcatPriority = mLogcatPriority; 259 | setting.mLogfilePriority = mLogfilePriority; 260 | setting.mEventPriority = mEventPriority; 261 | setting.mDefaultTag = mDefaultTag; 262 | setting.mFormatter = mFormatter; 263 | setting.mShowThreadInfo = mShowThreadInfo; 264 | setting.mAdapter = mAdapter; 265 | 266 | if (setting.mLogcatPriority == -1) { 267 | setting.mLogcatPriority = mDebuggable ? LogPriority.VERBOSE : LogPriority.ERROR; 268 | } 269 | if (setting.mLogfilePriority == -1) { 270 | setting.mLogfilePriority = mDebuggable ? LogPriority.DEBUG : LogPriority.INFO; 271 | } 272 | if (setting.mFormatter == null) { 273 | setting.mFormatter = new LogFormatterImpl(setting); 274 | } 275 | 276 | if (mLogDir == null) { 277 | File logDir = null; 278 | 279 | try { 280 | logDir = mContext.getExternalFilesDir(DEFAULT_DIR); 281 | } catch (Throwable e) { 282 | if (mDebuggable) { 283 | Logger.w(e); 284 | } 285 | } 286 | 287 | if (logDir == null) { 288 | if (mDebuggable) { 289 | Logger.w("Create external log dir fail, do you miss the permission?"); 290 | } 291 | logDir = mContext.getDir(DEFAULT_DIR, Context.MODE_PRIVATE); 292 | } 293 | 294 | setting.mLogDir = logDir; 295 | } 296 | 297 | try { 298 | InternalUtils.checkCreateDir(setting.mLogDir); 299 | } catch (IOException e) { 300 | setting.mLogDir = null; 301 | Logger.w(e); 302 | } 303 | 304 | return setting; 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /logger/src/main/java/moe/studio/log/Logger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | /** 8 | * @author Kaede 9 | * @since 2017/1/20 10 | */ 11 | @SuppressWarnings("WeakerAccess") 12 | class Logger { 13 | 14 | static final String TAG = "BLog"; 15 | 16 | public static void w(String message) { 17 | android.util.Log.w(TAG, message); 18 | } 19 | 20 | public static void w(Throwable throwable) { 21 | android.util.Log.w(TAG, throwable); 22 | } 23 | 24 | public static void w(String message, Throwable throwable) { 25 | android.util.Log.w(TAG, message, throwable); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /logger/src/test/java/moe/kaede/log/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | package moe.studio.log; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Example local unit test, which will execute on the development machine (host). 13 | * 14 | * @see Testing documentation 15 | */ 16 | public class ExampleUnitTest { 17 | @Test 18 | public void addition_isCorrect() throws Exception { 19 | assertEquals(4, 2 + 2); 20 | } 21 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. Kaede 3 | */ 4 | 5 | include ':logger' --------------------------------------------------------------------------------