├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.md ├── Readme.md ├── android-app-log ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── me │ │ └── allenz │ │ └── androidapplog │ │ ├── AbstractAppender.java │ │ ├── AbstractLogger.java │ │ ├── Appender.java │ │ ├── AppenderSupportLogger.java │ │ ├── AsyncAppender.java │ │ ├── Configure.java │ │ ├── InternalLogger.java │ │ ├── LogCatAppender.java │ │ ├── LogEvent.java │ │ ├── LogLevel.java │ │ ├── LogTextView.java │ │ ├── Logger.java │ │ ├── LoggerConfig.java │ │ ├── LoggerFactory.java │ │ ├── LoggerFactoryConfig.java │ │ ├── PropertiesParser.java │ │ ├── ReflectUtils.java │ │ ├── Repository.java │ │ ├── RollingFileAppender.java │ │ ├── TextViewAppender.java │ │ └── UncaughtExceptionLogger.java │ └── test │ └── java │ └── me │ └── allenz │ └── androidapplog │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── screenshot1.png ├── screenshot2.png └── screenshot3.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/gradle.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | /.idea/misc.xml 11 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | androidapplog1 -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Android 46 | 47 | 48 | Android Lint 49 | 50 | 51 | General 52 | 53 | 54 | Gradle 55 | 56 | 57 | Java 58 | 59 | 60 | Probable bugsGradle 61 | 62 | 63 | Probable bugsJava 64 | 65 | 66 | 67 | 68 | Android 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | C:\Users\Allenz\AppData\Roaming\Subversion 92 | 93 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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 {yyyy} {name of copyright owner} 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. 202 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Android App Log 2 | ====================================== 3 | A lightweight android logger support auto tag, file logging and show logs on screen 4 | ![image](https://github.com/allenz8512/androidapplog/blob/master/screenshots/screenshot2.png) 5 | ![image](https://github.com/allenz8512/androidapplog/blob/master/screenshots/screenshot1.png) 6 | ![image](https://github.com/allenz8512/androidapplog/blob/master/screenshots/screenshot3.png) 7 | 8 | ####New in 1.4.0: 9 | Use LoggerFactoryConfig.setPropertiesEncoding(encoding) to set the encoding for your aal.properties, 'ISO-8859-1' by default. 10 | ####Build: 11 | Binary download: 12 | [![Download](https://api.bintray.com/packages/allenz8512/maven/android-app-log/images/download.svg) ](https://github.com/allenz8512/AndroidAppLog/releases/download/1.4.0/android-app-log-1.4.0.aar) 13 | Gradle build (jcenter): 14 | 15 | dependencies{ 16 | compile 'me.allenz:android-app-log:1.4.0' 17 | } 18 | 19 | Will be synchronize to maven central soon! 20 | ####How to use: 21 | 22 | Put aal.properties into 'assets' or 'res/raw' under your app's root directory, format: 23 | 24 | debug=[Show debug log:True|False] 25 | root=[Log level],[Log tag],[Show thread name in tag:True|False] 26 | logcat=[Output to logcat:True|False] 27 | file=[Output to file:True|False],[parent folder of log files],[rolling file size],[enable gzip compress:true|false] 28 | textview=[Output to textview:True|False] 29 | handleex=[Log uncaught exception message:True|False] 30 | logger.[Package or class fullname]=[Log level],[Log tag],[Show thread name in tag:True|False] 31 | 32 | Value of 'Log level' can be one of following: 33 | 34 | VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, OFF 35 | 36 | Now use loggers just like log4j! 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(); 39 | 40 | LOGGER.debug("Message"); 41 | LOGGER.info("Message with arguments: %s %d", "str", 123); 42 | LOGGER.warn(new Throwable()); 43 | LOGGER.error(new Throwable(), "Throwable with a message"); 44 | 45 | ####Example: 46 | 47 | 48 | aal.properties 49 | 50 | debug=false 51 | root=debug 52 | #use ${internal} or ${external} to be prefix of folder path, means using internal or external storage of app 53 | file=true,${internal}/logs 54 | textview=true 55 | logger.com.example.app.MainActivity=info,Activity,true 56 | logger.com.example.app=warn 57 | 58 | A.java 59 | 60 | package com.example.app; 61 | 62 | import me.allenz.androidapplog.Logger; 63 | import me.allenz.androidapplog.LoggerFactory; 64 | 65 | public class A { 66 | 67 | private static final Logger logger = LoggerFactory.getLogger(); 68 | 69 | public A() { 70 | logger.verbose("verbose"); 71 | logger.debug("debug"); 72 | logger.info("info"); 73 | logger.warn("warn"); 74 | } 75 | } 76 | 77 | B.java 78 | 79 | package com.example; 80 | 81 | import me.allenz.androidapplog.Logger; 82 | import me.allenz.androidapplog.LoggerFactory; 83 | 84 | public class B { 85 | 86 | private static final Logger logger = LoggerFactory.getLogger(); 87 | 88 | public B() { 89 | logger.verbose("verbose"); 90 | logger.debug("debug"); 91 | logger.info("info"); 92 | logger.warn("warn"); 93 | } 94 | } 95 | 96 | MainActivity.java 97 | 98 | package com.example.app; 99 | 100 | import me.allenz.androidapplog.Logger; 101 | import me.allenz.androidapplog.LoggerFactory; 102 | import android.app.Activity; 103 | import android.os.Bundle; 104 | 105 | import com.example.B; 106 | 107 | public class MainActivity extends Activity { 108 | 109 | private static final Logger logger = LoggerFactory.getLogger(); 110 | 111 | private TextView logView; 112 | 113 | @Override 114 | protected void onCreate(final Bundle savedInstanceState) { 115 | super.onCreate(savedInstanceState); 116 | setContentView(R.layout.activity_main); 117 | //create an intouchable and transparent textview to show logs on screen 118 | logView = LoggerFactory.createLogTextView(this); 119 | logger.verbose("verbose"); 120 | logger.debug("debug"); 121 | logger.info("info"); 122 | logger.warn("warn"); 123 | new A(); 124 | new B(); 125 | } 126 | 127 | @Override 128 | protected void onResume() { 129 | super.onResume(); 130 | LoggerFactory.bindTextView(logView); 131 | } 132 | 133 | @Override 134 | protected void onPause() { 135 | super.onPause(); 136 | LoggerFactory.unbindTextView(); 137 | } 138 | 139 | } 140 | 141 | Log output in '/data/data/com.example.app/files/logs/com.example.app.log': 142 | 143 | >2014-08-26 12:05:50.269 INFO [main]Activity info 144 | >2014-08-26 12:05:50.269 WARN [main]Activity warn 145 | >2014-08-26 12:05:50.269 WARN A warn 146 | >2014-08-26 12:05:50.269 DEBUG B debug 147 | >2014-08-26 12:05:50.269 INFO B info 148 | >2014-08-26 12:05:50.269 WARN B warn 149 | -------------------------------------------------------------------------------- /android-app-log/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android-app-log/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | android { 5 | compileSdkVersion 23 6 | buildToolsVersion "23.0.1" 7 | 8 | defaultConfig { 9 | minSdkVersion 4 10 | targetSdkVersion 23 11 | versionCode 8 12 | versionName "1.4.1" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | group = 'me.allenz' 23 | version = android.defaultConfig.versionName 24 | 25 | task sourcesJar(type: Jar) { 26 | from android.sourceSets.main.java.srcDirs 27 | classifier = 'sources' 28 | } 29 | 30 | artifacts { 31 | archives sourcesJar 32 | } -------------------------------------------------------------------------------- /android-app-log/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 C:\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 | -------------------------------------------------------------------------------- /android-app-log/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/AbstractAppender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | public abstract class AbstractAppender implements Appender { 4 | 5 | private static final Logger internalLogger = LoggerFactory.getInternalLogger(); 6 | 7 | protected boolean started; 8 | 9 | @Override 10 | public void start() { 11 | if (!started) { 12 | started = doStart(); 13 | if (started) { 14 | internalLogger.verbose("%s is started", this.getClass().getSimpleName()); 15 | } else { 16 | internalLogger.verbose("can not start %s", this.getClass().getSimpleName()); 17 | } 18 | } 19 | } 20 | 21 | @Override 22 | public boolean isStarted() { 23 | return started; 24 | } 25 | 26 | @Override 27 | public void stop() { 28 | if (started) { 29 | started = !doStop(); 30 | if (!started) { 31 | internalLogger.verbose("%s is stop", this.getClass().getSimpleName()); 32 | } else { 33 | internalLogger.verbose("can not stop %s", this.getClass().getSimpleName()); 34 | } 35 | } 36 | } 37 | 38 | @Override 39 | public void append(final LogEvent event) { 40 | if (isStarted()) { 41 | doAppend(event); 42 | } 43 | } 44 | 45 | protected abstract boolean doStart(); 46 | 47 | protected abstract boolean doStop(); 48 | 49 | protected abstract void doAppend(LogEvent event); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/AbstractLogger.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.util.Log; 4 | 5 | public abstract class AbstractLogger implements Logger { 6 | 7 | protected static final String STRING_NULL = "null"; 8 | 9 | protected String name; 10 | 11 | protected LogLevel level; 12 | 13 | protected String tag; 14 | 15 | protected boolean showThreadName; 16 | 17 | public AbstractLogger(final String name) { 18 | this(name, Configure.DEFAULT_ROOT_LOG_LEVEL, null, false); 19 | } 20 | 21 | public AbstractLogger(final String name, final LogLevel level, 22 | final String tag, final boolean showThreadName) { 23 | this.name = name; 24 | this.level = level; 25 | this.tag = tag; 26 | this.showThreadName = showThreadName; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | @Override 35 | public LogLevel getLogLevel() { 36 | return level; 37 | } 38 | 39 | @Override 40 | public void setLogLevel(final LogLevel level) { 41 | this.level = level; 42 | } 43 | 44 | @Override 45 | public String getTag() { 46 | return tag; 47 | } 48 | 49 | @Override 50 | public void setTag(final String tag) { 51 | this.tag = tag; 52 | } 53 | 54 | @Override 55 | public boolean isShowThreadName() { 56 | return showThreadName; 57 | } 58 | 59 | @Override 60 | public void setShowThreadName(final boolean show) { 61 | showThreadName = show; 62 | } 63 | 64 | @Override 65 | public void verbose(final String format, final Object... args) { 66 | println(LogLevel.VERBOSE, null, format, args); 67 | } 68 | 69 | @Override 70 | public void verbose(final Throwable t) { 71 | println(LogLevel.VERBOSE, t, null); 72 | } 73 | 74 | @Override 75 | public void verbose(final Throwable t, final String format, 76 | final Object... args) { 77 | println(LogLevel.VERBOSE, t, null); 78 | } 79 | 80 | @Override 81 | public void verbose(final String message) { 82 | println(LogLevel.VERBOSE, null, message); 83 | } 84 | 85 | @Override 86 | public void verbose(final Throwable t, final String message) { 87 | println(LogLevel.VERBOSE, t, message); 88 | } 89 | 90 | @Override 91 | public void verbose(final Object obj) { 92 | final String message = obj == null ? STRING_NULL : obj.toString(); 93 | println(LogLevel.VERBOSE, null, message); 94 | } 95 | 96 | @Override 97 | public void debug(final String format, final Object... args) { 98 | println(LogLevel.DEBUG, null, format, args); 99 | } 100 | 101 | @Override 102 | public void debug(final Throwable t) { 103 | println(LogLevel.DEBUG, t, null); 104 | } 105 | 106 | @Override 107 | public void debug(final Throwable t, final String format, 108 | final Object... args) { 109 | println(LogLevel.DEBUG, t, format, args); 110 | } 111 | 112 | @Override 113 | public void debug(final String message) { 114 | println(LogLevel.DEBUG, null, message); 115 | } 116 | 117 | @Override 118 | public void debug(final Throwable t, final String message) { 119 | println(LogLevel.DEBUG, t, message); 120 | } 121 | 122 | @Override 123 | public void debug(final Object obj) { 124 | final String message = obj == null ? STRING_NULL : obj.toString(); 125 | println(LogLevel.DEBUG, null, message); 126 | } 127 | 128 | @Override 129 | public void info(final String format, final Object... args) { 130 | println(LogLevel.INFO, null, format, args); 131 | } 132 | 133 | @Override 134 | public void info(final Throwable t) { 135 | println(LogLevel.INFO, t, null); 136 | } 137 | 138 | @Override 139 | public void info(final Throwable t, final String format, 140 | final Object... args) { 141 | println(LogLevel.INFO, t, format, args); 142 | } 143 | 144 | @Override 145 | public void info(final String message) { 146 | println(LogLevel.INFO, null, message); 147 | } 148 | 149 | @Override 150 | public void info(final Throwable t, final String message) { 151 | println(LogLevel.INFO, t, message); 152 | } 153 | 154 | @Override 155 | public void info(final Object obj) { 156 | final String message = obj == null ? STRING_NULL : obj.toString(); 157 | println(LogLevel.INFO, null, message); 158 | } 159 | 160 | @Override 161 | public void warn(final String format, final Object... args) { 162 | println(LogLevel.WARN, null, format, args); 163 | } 164 | 165 | @Override 166 | public void warn(final Throwable t) { 167 | println(LogLevel.WARN, t, null); 168 | } 169 | 170 | @Override 171 | public void warn(final Throwable t, final String format, 172 | final Object... args) { 173 | println(LogLevel.WARN, t, format, args); 174 | } 175 | 176 | @Override 177 | public void warn(final String message) { 178 | println(LogLevel.WARN, null, message); 179 | } 180 | 181 | @Override 182 | public void warn(final Throwable t, final String message) { 183 | println(LogLevel.WARN, t, message); 184 | } 185 | 186 | @Override 187 | public void warn(final Object obj) { 188 | final String message = obj == null ? STRING_NULL : obj.toString(); 189 | println(LogLevel.WARN, null, message); 190 | } 191 | 192 | @Override 193 | public void error(final String format, final Object... args) { 194 | println(LogLevel.ERROR, null, format, args); 195 | } 196 | 197 | @Override 198 | public void error(final Throwable t) { 199 | println(LogLevel.ERROR, t, null); 200 | } 201 | 202 | @Override 203 | public void error(final Throwable t, final String format, 204 | final Object... args) { 205 | println(LogLevel.ERROR, t, format, args); 206 | } 207 | 208 | @Override 209 | public void error(final String message) { 210 | println(LogLevel.ERROR, null, message); 211 | } 212 | 213 | @Override 214 | public void error(final Throwable t, final String message) { 215 | println(LogLevel.ERROR, t, message); 216 | } 217 | 218 | @Override 219 | public void error(final Object obj) { 220 | final String message = obj == null ? STRING_NULL : obj.toString(); 221 | println(LogLevel.ERROR, null, message); 222 | } 223 | 224 | @Override 225 | public void wtf(final String format, final Object... args) { 226 | println(LogLevel.ASSERT, null, format, args); 227 | } 228 | 229 | @Override 230 | public void wtf(final Throwable t) { 231 | println(LogLevel.ASSERT, t, null); 232 | } 233 | 234 | @Override 235 | public void wtf(final Throwable t, final String format, 236 | final Object... args) { 237 | println(LogLevel.ASSERT, t, format, args); 238 | } 239 | 240 | @Override 241 | public void wtf(final String message) { 242 | println(LogLevel.ASSERT, null, message); 243 | } 244 | 245 | @Override 246 | public void wtf(final Throwable t, final String message) { 247 | println(LogLevel.ASSERT, t, message); 248 | } 249 | 250 | @Override 251 | public void wtf(final Object obj) { 252 | final String message = obj == null ? STRING_NULL : obj.toString(); 253 | println(LogLevel.ASSERT, null, message); 254 | } 255 | 256 | protected LogEvent buildLogEvent(final LogLevel level, final Throwable t, 257 | final String format, final Object... args) { 258 | String message = null; 259 | if (format != null && format.length() > 0) { 260 | message = (args != null && args.length > 0) ? String.format(format, 261 | args) : format; 262 | } 263 | if (t != null) { 264 | message = message != null ? message + "\n" 265 | + Log.getStackTraceString(t) : Log.getStackTraceString(t); 266 | } 267 | String tag; 268 | if (showThreadName) { 269 | final StringBuilder sb = new StringBuilder(); 270 | sb.append("[").append(Thread.currentThread().getName()).append("]") 271 | .append(this.tag); 272 | tag = sb.toString(); 273 | } else { 274 | tag = this.tag; 275 | } 276 | return new LogEvent(level, tag, message); 277 | } 278 | 279 | protected abstract void println(final LogLevel level, final Throwable t, 280 | final String format, final Object... args); 281 | 282 | @Override 283 | public String toString() { 284 | return "Logger [name=" + name + ", level=" + level + ", tag=" + tag 285 | + ", showThreadName=" + showThreadName + "]"; 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/Appender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | public interface Appender { 4 | 5 | void start(); 6 | 7 | boolean isStarted(); 8 | 9 | void stop(); 10 | 11 | void append(LogEvent event); 12 | } 13 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/AppenderSupportLogger.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.util.List; 4 | 5 | public class AppenderSupportLogger extends AbstractLogger { 6 | 7 | public AppenderSupportLogger(final String name, final LogLevel level, final String tag, final boolean showThreadName){ 8 | super(name, level, tag, showThreadName); 9 | } 10 | 11 | @Override 12 | protected void println(final LogLevel level, final Throwable t, final String format, final Object... args) { 13 | if (this.level.includes(level) && 14 | (t != null || format != null)) { 15 | final LogEvent event = buildLogEvent(level, t, format, args); 16 | final List appenders = LoggerFactory.getRepository().getAppenders(); 17 | for (final Appender appender: appenders) { 18 | appender.append(event); 19 | } 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/AsyncAppender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | 6 | import android.os.Process; 7 | 8 | public abstract class AsyncAppender extends AbstractAppender { 9 | 10 | protected static final int DEFAULT_LOG_EVENT_QUEUE_SIZE = 100; 11 | 12 | protected Thread workThread; 13 | 14 | protected BlockingQueue logEventQueue; 15 | 16 | public AsyncAppender(){ 17 | workThread = new WorkThread(); 18 | logEventQueue = new ArrayBlockingQueue( 19 | DEFAULT_LOG_EVENT_QUEUE_SIZE); 20 | } 21 | 22 | @Override 23 | protected boolean doStart() { 24 | workThread.start(); 25 | return true; 26 | } 27 | 28 | @Override 29 | protected boolean doStop() { 30 | workThread.interrupt(); 31 | return true; 32 | } 33 | 34 | @Override 35 | protected void doAppend(final LogEvent event) { 36 | try { 37 | logEventQueue.put(event); 38 | } catch (final InterruptedException e) { 39 | } 40 | } 41 | 42 | protected abstract void handleEventQueue() throws InterruptedException; 43 | 44 | protected class WorkThread extends Thread { 45 | 46 | @Override 47 | public void run() { 48 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 49 | while (!isInterrupted()) { 50 | try { 51 | handleEventQueue(); 52 | } catch (final InterruptedException e) { 53 | if (!isStarted()) { 54 | return; 55 | } 56 | } 57 | } 58 | } 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/Configure.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class Configure { 8 | 9 | public static final LogLevel DEFAULT_ROOT_LOG_LEVEL = LogLevel.VERBOSE; 10 | 11 | private static final long DEFAULT_LOG_FILE_ROLLING_SIZE = 1024 * 1024; 12 | 13 | private static final String ROOT_LOGGER_NAME = "root"; 14 | 15 | private boolean debug = true; 16 | 17 | private LogLevel rootLogLevel = DEFAULT_ROOT_LOG_LEVEL; 18 | 19 | private String rootTag = null; 20 | 21 | private boolean rootShowThread = false; 22 | 23 | private List loggerConfigs; 24 | 25 | private boolean handleException = true; 26 | 27 | private boolean useLogCatAppender = true; 28 | 29 | private boolean useFileAppender = false; 30 | 31 | private File logFileDir; 32 | 33 | private long logFileRollingSize = DEFAULT_LOG_FILE_ROLLING_SIZE; 34 | 35 | private boolean compressLogFiles = true; 36 | 37 | private boolean useTextViewAppender = false; 38 | 39 | public Configure() { 40 | loggerConfigs = new ArrayList(); 41 | } 42 | 43 | public boolean isDebug() { 44 | return debug; 45 | } 46 | 47 | public void setDebug(final boolean debug) { 48 | this.debug = debug; 49 | } 50 | 51 | public LogLevel getRootLogLevel() { 52 | return rootLogLevel; 53 | } 54 | 55 | public void setRootLogLevel(final LogLevel rootLogLevel) { 56 | this.rootLogLevel = rootLogLevel; 57 | } 58 | 59 | public String getRootTag() { 60 | return rootTag; 61 | } 62 | 63 | public void setRootTag(final String rootTag) { 64 | this.rootTag = rootTag; 65 | } 66 | 67 | public boolean isRootShowThread() { 68 | return rootShowThread; 69 | } 70 | 71 | public void setRootShowThread(final boolean rootShowThread) { 72 | this.rootShowThread = rootShowThread; 73 | } 74 | 75 | public boolean isHandleException() { 76 | return handleException; 77 | } 78 | 79 | public void setHandleException(final boolean handleException) { 80 | this.handleException = handleException; 81 | } 82 | 83 | public boolean isUseLogCatAppender() { 84 | return useLogCatAppender; 85 | } 86 | 87 | public void setUseLogCatAppender(final boolean useLogCatAppender) { 88 | this.useLogCatAppender = useLogCatAppender; 89 | } 90 | 91 | public boolean isUseFileAppender() { 92 | return useFileAppender; 93 | } 94 | 95 | public void setUseFileAppender(final boolean useFileAppender) { 96 | this.useFileAppender = useFileAppender; 97 | } 98 | 99 | public File getLogFileDir() { 100 | return logFileDir; 101 | } 102 | 103 | public void setLogFileDir(final File logFileDir) { 104 | this.logFileDir = logFileDir; 105 | } 106 | 107 | public long getLogFileRollingSize() { 108 | return logFileRollingSize; 109 | } 110 | 111 | public void setLogFileRollingSize(final long logFileRollingSize) { 112 | this.logFileRollingSize = logFileRollingSize; 113 | } 114 | 115 | public boolean isCompressLogFiles() { 116 | return compressLogFiles; 117 | } 118 | 119 | public void setCompressLogFiles(final boolean compressLogFiles) { 120 | this.compressLogFiles = compressLogFiles; 121 | } 122 | 123 | public boolean isUseTextViewAppender() { 124 | return useTextViewAppender; 125 | } 126 | 127 | public void setUseTextViewAppender(final boolean useTextViewAppender) { 128 | this.useTextViewAppender = useTextViewAppender; 129 | } 130 | 131 | public List getLoggerConfigs() { 132 | return loggerConfigs; 133 | } 134 | 135 | public void addLoggerConfig(final String name, final LogLevel level, 136 | final String tag, final boolean showThreadName) { 137 | loggerConfigs.add(new LoggerConfig(name, tag, level, showThreadName)); 138 | } 139 | 140 | public static Configure defaultConfigure() { 141 | final Configure configure = new Configure(); 142 | return configure; 143 | } 144 | 145 | public static Configure releaseConfigure() { 146 | final Configure configure = new Configure(); 147 | configure.setDebug(false); 148 | configure.setRootLogLevel(LogLevel.OFF); 149 | configure.setUseLogCatAppender(false); 150 | return configure; 151 | } 152 | 153 | public void applyConfigure(final Repository repository) { 154 | if (!debug) { 155 | LoggerFactory.getInternalLogger().setLogLevel(LogLevel.OFF); 156 | } else { 157 | LoggerFactory.getInternalLogger().setLogLevel( 158 | DEFAULT_ROOT_LOG_LEVEL); 159 | } 160 | repository.setRootLoggerConfig(new LoggerConfig(ROOT_LOGGER_NAME, 161 | rootTag, rootLogLevel, rootShowThread)); 162 | if (handleException) { 163 | LoggerFactory.enableLoggingUncaughtException(null); 164 | } 165 | if (useLogCatAppender) { 166 | repository.addAppender(new LogCatAppender()); 167 | } 168 | if (useFileAppender) { 169 | repository.addAppender(new RollingFileAppender(logFileDir, 170 | logFileRollingSize, compressLogFiles)); 171 | } 172 | if (useTextViewAppender) { 173 | repository.addAppender(new TextViewAppender()); 174 | } 175 | for (final LoggerConfig loggerConfig : loggerConfigs) { 176 | repository.addLoggerConfig(loggerConfig); 177 | } 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/InternalLogger.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.util.Log; 4 | 5 | public class InternalLogger extends AbstractLogger { 6 | 7 | private static final LogLevel DEFAULT_INTERNAL_LOG_LEVEL = LogLevel.VERBOSE; 8 | 9 | private static final String TAG = "aal"; 10 | 11 | public InternalLogger(){ 12 | super(TAG, DEFAULT_INTERNAL_LOG_LEVEL, TAG, false); 13 | } 14 | 15 | @Override 16 | protected void println(final LogLevel level, final Throwable t, final String format, final Object... args) { 17 | if (this.level.includes(level) && 18 | (t != null || format != null)) { 19 | final LogEvent event = buildLogEvent(level, t, format, args); 20 | Log.println(event.getLevel().intValue(), event.getTag(), event.getMessage()); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LogCatAppender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.util.Log; 4 | 5 | public class LogCatAppender extends AbstractAppender { 6 | 7 | @Override 8 | public void doAppend(final LogEvent event) { 9 | Log.println(event.getLevel().intValue(), event.getTag(), event.getMessage()); 10 | } 11 | 12 | @Override 13 | protected boolean doStart() { 14 | return true; 15 | } 16 | 17 | @Override 18 | protected boolean doStop() { 19 | return true; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LogEvent.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | public class LogEvent { 7 | 8 | private static final String DELIMITER = " "; 9 | 10 | private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; 11 | 12 | private long time; 13 | 14 | private LogLevel level; 15 | 16 | private String tag; 17 | 18 | private String message; 19 | 20 | public LogEvent(final LogLevel level, final String tag, final String message) { 21 | this(System.currentTimeMillis(), level, tag, message); 22 | } 23 | 24 | public LogEvent(final long millis, final LogLevel level, final String tag, 25 | final String message) { 26 | this.time = millis; 27 | this.level = level; 28 | this.tag = tag; 29 | this.message = message; 30 | } 31 | 32 | public long getTime() { 33 | return time; 34 | } 35 | 36 | public LogLevel getLevel() { 37 | return level; 38 | } 39 | 40 | public String getTag() { 41 | return tag; 42 | } 43 | 44 | public String getMessage() { 45 | return message; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | final SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT); 51 | final StringBuilder sb = new StringBuilder(); 52 | sb.append(formatter.format(new Date(time))).append(DELIMITER); 53 | sb.append(level.toString()).append(DELIMITER); 54 | sb.append(tag).append(DELIMITER); 55 | sb.append(message); 56 | return sb.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LogLevel.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.graphics.Color; 4 | import android.util.Log; 5 | 6 | public enum LogLevel { 7 | 8 | VERBOSE(Log.VERBOSE, "#a0000000"), 9 | DEBUG(Log.DEBUG, "#a000007f"), 10 | INFO(Log.INFO, "#a0007f00"), 11 | WARN(Log.WARN, "#a0ff7f00"), 12 | ERROR(Log.ERROR, "#a0ff0000"), 13 | ASSERT(Log.ASSERT, "#a0ff0000"), 14 | OFF(Integer.MAX_VALUE, "#a0000000"); 15 | 16 | private final int intVal; 17 | private final int color; 18 | 19 | private LogLevel(final int intVal, final String color) { 20 | this.intVal = intVal; 21 | this.color = Color.parseColor(color); 22 | } 23 | 24 | public int intValue() { 25 | return intVal; 26 | } 27 | 28 | public int getColor() { 29 | return color; 30 | } 31 | 32 | public boolean includes(final LogLevel level) { 33 | return level != null && this.intValue() <= level.intValue(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LogTextView.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.text.Editable; 6 | import android.text.TextWatcher; 7 | import android.text.method.ScrollingMovementMethod; 8 | import android.util.AttributeSet; 9 | import android.util.TypedValue; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.ViewGroup.LayoutParams; 13 | import android.widget.TextView; 14 | 15 | public class LogTextView extends TextView { 16 | 17 | public LogTextView(final Context context) { 18 | super(context); 19 | setDefaultAttributes(); 20 | setAutoScrolling(); 21 | } 22 | 23 | public LogTextView(final Context context, final AttributeSet attrs) { 24 | super(context, attrs); 25 | setAutoScrolling(); 26 | } 27 | 28 | public LogTextView(final Context context, final AttributeSet attrs, 29 | final int defStyle) { 30 | super(context, attrs, defStyle); 31 | setAutoScrolling(); 32 | } 33 | 34 | private void setDefaultAttributes() { 35 | setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 36 | LayoutParams.MATCH_PARENT)); 37 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f); 38 | setBackgroundColor(Color.parseColor("#00ffffff")); 39 | } 40 | 41 | private void setAutoScrolling() { 42 | setGravity(Gravity.BOTTOM); 43 | setSingleLine(false); 44 | setKeyListener(null); 45 | setMovementMethod(ScrollingMovementMethod.getInstance()); 46 | addTextChangedListener(new TextWatcher() { 47 | 48 | @Override 49 | public void onTextChanged(final CharSequence s, final int start, 50 | final int before, final int count) { 51 | } 52 | 53 | @Override 54 | public void beforeTextChanged(final CharSequence s, 55 | final int start, final int count, final int after) { 56 | } 57 | 58 | @Override 59 | public void afterTextChanged(final Editable s) { 60 | final int scrollAmount = getLayout().getLineTop(getLineCount()) 61 | - getHeight(); 62 | if (scrollAmount > 0) { 63 | scrollTo(0, scrollAmount); 64 | } else { 65 | scrollTo(0, 0); 66 | } 67 | } 68 | }); 69 | } 70 | 71 | @Override 72 | public boolean dispatchTouchEvent(final MotionEvent event) { 73 | return false; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/Logger.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | public interface Logger { 4 | 5 | String getName(); 6 | 7 | LogLevel getLogLevel(); 8 | 9 | void setLogLevel(LogLevel level); 10 | 11 | String getTag(); 12 | 13 | void setTag(String tag); 14 | 15 | boolean isShowThreadName(); 16 | 17 | void setShowThreadName(boolean show); 18 | 19 | void verbose(String format, Object... args); 20 | 21 | void verbose(Throwable t); 22 | 23 | void verbose(Throwable t, String format, Object... args); 24 | 25 | void verbose(String message); 26 | 27 | void verbose(Throwable t, String message); 28 | 29 | void verbose(Object obj); 30 | 31 | void debug(String format, Object... args); 32 | 33 | void debug(Throwable t); 34 | 35 | void debug(Throwable t, String format, Object... args); 36 | 37 | void debug(String message); 38 | 39 | void debug(Throwable t, String message); 40 | 41 | void debug(Object obj); 42 | 43 | void info(String format, Object... args); 44 | 45 | void info(Throwable t); 46 | 47 | void info(Throwable t, String format, Object... args); 48 | 49 | void info(String message); 50 | 51 | void info(Throwable t, String message); 52 | 53 | void info(Object obj); 54 | 55 | void warn(String format, Object... args); 56 | 57 | void warn(Throwable t); 58 | 59 | void warn(Throwable t, String format, Object... args); 60 | 61 | void warn(String message); 62 | 63 | void warn(Throwable t, String message); 64 | 65 | void warn(Object obj); 66 | 67 | void error(String format, Object... args); 68 | 69 | void error(Throwable t); 70 | 71 | void error(Throwable t, String format, Object... args); 72 | 73 | void error(String message); 74 | 75 | void error(Throwable t, String message); 76 | 77 | void error(Object obj); 78 | 79 | void wtf(String format, Object... args); 80 | 81 | void wtf(Throwable t); 82 | 83 | void wtf(Throwable t, String format, Object... args); 84 | 85 | void wtf(String message); 86 | 87 | void wtf(Throwable t, String message); 88 | 89 | void wtf(Object obj); 90 | } 91 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LoggerConfig.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | public class LoggerConfig { 4 | 5 | private String name; 6 | 7 | private String tag; 8 | 9 | private LogLevel level; 10 | 11 | private boolean showThreadName; 12 | 13 | public LoggerConfig(final String name, final String tag, 14 | final LogLevel level, final boolean showThreadName){ 15 | this.name = name; 16 | this.tag = tag; 17 | this.level = level; 18 | this.showThreadName = showThreadName; 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public void setName(final String name) { 26 | this.name = name; 27 | } 28 | 29 | public String getTag() { 30 | return tag; 31 | } 32 | 33 | public void setTag(final String tag) { 34 | this.tag = tag; 35 | } 36 | 37 | public LogLevel getLevel() { 38 | return level; 39 | } 40 | 41 | public void setLevel(final LogLevel level) { 42 | this.level = level; 43 | } 44 | 45 | public boolean isShowThreadName() { 46 | return showThreadName; 47 | } 48 | 49 | public void setShowThreadName(final boolean showThreadName) { 50 | this.showThreadName = showThreadName; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "LoggerConfig [name=" + 56 | name + ", tag=" + tag + ", level=" + level + ", showThreadName=" + showThreadName + "]"; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LoggerFactory.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.widget.FrameLayout; 8 | import android.widget.TextView; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.lang.Thread.UncaughtExceptionHandler; 14 | import java.lang.reflect.Method; 15 | import java.util.List; 16 | import java.util.Properties; 17 | 18 | public class LoggerFactory { 19 | 20 | private static final String CONFIG_FILE_NAME = "aal"; 21 | 22 | private static Logger internalLogger = new InternalLogger(); 23 | 24 | private static Repository repository = new Repository(); 25 | 26 | private static Context appContext; 27 | 28 | private static String packageName; 29 | 30 | private static UncaughtExceptionLogger mUncaughtExceptionLogger; 31 | 32 | static { 33 | getContext(); 34 | getPackageName(); 35 | checkBuildConfigAndApplyConfigure(); 36 | loadConfigure(); 37 | } 38 | 39 | private LoggerFactory() { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | static Logger getInternalLogger() { 44 | return internalLogger; 45 | } 46 | 47 | static Repository getRepository() { 48 | return repository; 49 | } 50 | 51 | static Context getContext() { 52 | if (appContext == null) { 53 | try { 54 | final Class activityThreadClass = LoggerFactory.class 55 | .getClassLoader().loadClass( 56 | "android.app.ActivityThread"); 57 | final Method currentActivityThread = activityThreadClass 58 | .getDeclaredMethod("currentActivityThread"); 59 | final Object activityThread = currentActivityThread 60 | .invoke(null); 61 | final Method getApplication = activityThreadClass 62 | .getDeclaredMethod("getApplication"); 63 | final Application application = (Application) getApplication 64 | .invoke(activityThread); 65 | appContext = application.getApplicationContext(); 66 | } catch (final Exception e) { 67 | } 68 | } 69 | return appContext; 70 | } 71 | 72 | static String getPackageName() { 73 | if (packageName == null) { 74 | if (appContext != null) { 75 | packageName = appContext.getPackageName(); 76 | } else { 77 | try { 78 | final Class activityThreadClass = LoggerFactory.class.getClassLoader().loadClass("android.app.ActivityThread"); 79 | final Method currentPackageName = activityThreadClass.getDeclaredMethod("currentPackageName"); 80 | packageName = (String) currentPackageName.invoke(null); 81 | } catch (final Exception e) { 82 | } 83 | } 84 | } 85 | return packageName; 86 | } 87 | 88 | static void loadConfigure() { 89 | final Properties properties = readProperties(readPropertiesFileFromClasspath()); 90 | if (properties != null) { 91 | applyProperties(properties); 92 | } 93 | } 94 | 95 | private static void applyProperties(final Properties properties) { 96 | final Configure configure = (new PropertiesParser(properties)).parse(); 97 | repository.setConfigure(configure); 98 | } 99 | 100 | private static boolean isUnderJavaEnvironment(){ 101 | try { 102 | Class.forName("java.applet.Applet"); 103 | } catch (ClassNotFoundException e) { 104 | return false; 105 | } 106 | return true; 107 | } 108 | 109 | private static void checkBuildConfigAndApplyConfigure() { 110 | final boolean underDevelopment = ReflectUtils 111 | .booleanReflectStaticFieldValue(packageName + ".BuildConfig", "DEBUG", false); 112 | if (isUnderJavaEnvironment() || underDevelopment || LoggerFactoryConfig.forceDebug) { 113 | repository.setConfigure(Configure.defaultConfigure()); 114 | } else { 115 | repository.setConfigure(Configure.releaseConfigure()); 116 | } 117 | 118 | } 119 | 120 | private static InputStream readPropertiesFileFromClasspath() { 121 | final String filename = CONFIG_FILE_NAME + ".properties"; 122 | LoggerFactory.class.getClassLoader(); 123 | InputStream in = LoggerFactory.class.getClassLoader() 124 | .getResourceAsStream("assets/" + filename); 125 | if (in != null) { 126 | internalLogger.verbose("found %s.properties in assets", 127 | CONFIG_FILE_NAME); 128 | } else { 129 | in = LoggerFactory.class.getClassLoader().getResourceAsStream( 130 | "res/raw/" + filename); 131 | if (in != null) { 132 | internalLogger.verbose("found %s.properties in res/raw", 133 | CONFIG_FILE_NAME); 134 | } 135 | } 136 | return in; 137 | } 138 | 139 | private static Properties readProperties(final InputStream in) { 140 | if (in == null) { 141 | internalLogger.verbose("%s.properties not found", CONFIG_FILE_NAME); 142 | return null; 143 | } 144 | final Properties properties = new Properties(); 145 | try { 146 | if (Build.VERSION.SDK_INT < 11) { 147 | properties.load(in); 148 | } else { 149 | properties.load(new InputStreamReader(in, LoggerFactoryConfig.propertiesEncoding)); 150 | } 151 | } catch (final IOException e) { 152 | return null; 153 | } finally { 154 | try { 155 | in.close(); 156 | } catch (final IOException e) { 157 | } 158 | } 159 | return properties; 160 | } 161 | 162 | public static Logger getLogger() { 163 | return getLogger(ReflectUtils.getCallerClassName(LoggerFactory.class 164 | .getName())); 165 | } 166 | 167 | public static Logger getLogger(final String className) { 168 | synchronized (LoggerFactory.class) { 169 | internalLogger.verbose("Caller: %s", className); 170 | final Logger logger = getDeclaredLogger(className); 171 | return logger != null ? logger : createNewLogger(className); 172 | } 173 | } 174 | 175 | private static Logger getDeclaredLogger(final String caller) { 176 | return repository.getLogger(caller); 177 | } 178 | 179 | private static Logger createNewLogger(final String caller) { 180 | Logger logger = null; 181 | final LoggerConfig loggerConfig = repository.getLoggerConfig(caller); 182 | if (loggerConfig != null) { 183 | logger = createAppenderSupportLogger(caller, loggerConfig); 184 | } else { 185 | logger = createInheritParentConfigLogger(caller); 186 | } 187 | repository.addLogger(caller, logger); 188 | return logger; 189 | } 190 | 191 | private static Logger createInheritParentConfigLogger(final String caller) { 192 | boolean parentFound = false; 193 | Logger logger = null; 194 | for (int i = caller.lastIndexOf('.'); i >= 0; i = caller.lastIndexOf( 195 | '.', i - 1)) { 196 | final String parentPackage = caller.substring(0, i); 197 | final LoggerConfig loggerConfig = repository 198 | .getLoggerConfig(parentPackage); 199 | if (loggerConfig != null) { 200 | logger = createAppenderSupportLogger(caller, loggerConfig); 201 | parentFound = true; 202 | break; 203 | } 204 | } 205 | if (!parentFound) { 206 | logger = createAppenderSupportLogger(caller, 207 | repository.getRootLoggerConfig()); 208 | } 209 | return logger; 210 | } 211 | 212 | private static Logger createAppenderSupportLogger(final String caller, 213 | final LoggerConfig loggerConfig) { 214 | final int dot = caller.lastIndexOf("."); 215 | final String className = dot == -1 ? caller : caller.substring(dot + 1); 216 | final String tag = loggerConfig.getTag() == null ? className 217 | : loggerConfig.getTag(); 218 | final Logger logger = new AppenderSupportLogger(className, 219 | loggerConfig.getLevel(), tag, loggerConfig.isShowThreadName()); 220 | internalLogger.verbose("logger created: %s", logger); 221 | return logger; 222 | } 223 | 224 | public static void bindTextView(final TextView textView) { 225 | final List appenders = repository.getAppenders(); 226 | for (final Appender appender : appenders) { 227 | if (appender instanceof TextViewAppender) { 228 | ((TextViewAppender) appender).bind(textView); 229 | return; 230 | } 231 | } 232 | } 233 | 234 | public static void unbindTextView() { 235 | final List appenders = repository.getAppenders(); 236 | for (final Appender appender : appenders) { 237 | if (appender instanceof TextViewAppender) { 238 | ((TextViewAppender) appender).unbind(); 239 | return; 240 | } 241 | } 242 | } 243 | 244 | public static TextView createLogTextView(final Activity activity) { 245 | final FrameLayout root = (FrameLayout) activity.getWindow() 246 | .getDecorView().findViewById(android.R.id.content); 247 | final TextView textView = new LogTextView(activity); 248 | root.addView(textView); 249 | root.bringChildToFront(textView); 250 | return textView; 251 | } 252 | 253 | public static void enableLoggingUncaughtException( 254 | final UncaughtExceptionHandler customHandler) { 255 | if (customHandler != null) { 256 | mUncaughtExceptionLogger = new UncaughtExceptionLogger( 257 | customHandler); 258 | } else { 259 | mUncaughtExceptionLogger = new UncaughtExceptionLogger(); 260 | } 261 | Thread.setDefaultUncaughtExceptionHandler(mUncaughtExceptionLogger); 262 | } 263 | 264 | public static void disableLoggingUncaughtException() { 265 | if (mUncaughtExceptionLogger != null) { 266 | final UncaughtExceptionHandler customHandler = mUncaughtExceptionLogger 267 | .getDefaultExceptionHandler(); 268 | Thread.setDefaultUncaughtExceptionHandler(customHandler); 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/LoggerFactoryConfig.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import android.text.TextUtils; 4 | 5 | public class LoggerFactoryConfig { 6 | 7 | private static final String DEFAULT_PROPERTIES_ENCODING = "ISO-8859-1"; 8 | 9 | static String propertiesEncoding = DEFAULT_PROPERTIES_ENCODING; 10 | 11 | static boolean forceDebug = false; 12 | 13 | public static void setPropertiesEncoding(String propertiesEncoding) { 14 | if (!TextUtils.isEmpty(propertiesEncoding)) { 15 | LoggerFactoryConfig.propertiesEncoding = propertiesEncoding; 16 | } 17 | } 18 | 19 | public static void setForceDebug(boolean forceDebug) { 20 | LoggerFactoryConfig.forceDebug = forceDebug; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/PropertiesParser.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.io.File; 4 | import java.util.Enumeration; 5 | import java.util.Locale; 6 | import java.util.Properties; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import android.os.Environment; 11 | import android.text.TextUtils; 12 | 13 | public class PropertiesParser { 14 | 15 | private static final Logger internalLogger = LoggerFactory 16 | .getInternalLogger(); 17 | 18 | private static final String DEBUG_KEY = "debug"; 19 | 20 | private static final String ROOT_KEY = "root"; 21 | 22 | private static final String HANDLE_EXCEPTION_KEY = "handleex"; 23 | 24 | private static final String LOGCAT_KEY = "logcat"; 25 | 26 | private static final String FILE_KEY = "file"; 27 | 28 | private static final String FILE_INTERNAL = "${internal}"; 29 | 30 | private static final String FILE_EXTERNAL = "${external}"; 31 | 32 | private static final Pattern FILE_VALUE_PATTERN = Pattern 33 | .compile("^(.+?)(,(.+?))?(,(.+?))?(,(.*))*$"); 34 | 35 | private static final String TEXTVIEW_KEY = "textview"; 36 | 37 | private static final String LOGGER_PREFIX = "logger."; 38 | 39 | private static final Pattern LOGGER_VALUE_PATTERN = Pattern 40 | .compile("^(.+?)(,(.+?))?(,(.+?))?(,(.*))*$"); 41 | 42 | private static final Pattern PACKAGE_NAME_PATTERN = Pattern 43 | .compile("([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*"); 44 | 45 | private Properties properties; 46 | 47 | public PropertiesParser(final Properties properties) { 48 | this.properties = properties; 49 | } 50 | 51 | public Configure parse() { 52 | final Configure configure = new Configure(); 53 | parseDebug(configure); 54 | parseRoot(configure); 55 | parseLoggers(configure); 56 | parseHandleEx(configure); 57 | parseLogCat(configure); 58 | parseFile(configure); 59 | parseTextView(configure); 60 | return configure; 61 | } 62 | 63 | private void parseDebug(final Configure configure) { 64 | final String value = (String) properties.get(DEBUG_KEY); 65 | if (TextUtils.isEmpty(value)) { 66 | return; 67 | } 68 | final Boolean debug = booleanValueOf(value.trim()); 69 | if (debug == true) { 70 | configure.setDebug(true); 71 | internalLogger.verbose("properties: enable aal debug log"); 72 | } else if (debug == false) { 73 | configure.setDebug(false); 74 | internalLogger.verbose("properties: disable aal debug log"); 75 | } 76 | } 77 | 78 | private void parseRoot(final Configure configure) { 79 | final String value = (String) properties.get(ROOT_KEY); 80 | if (TextUtils.isEmpty(value)) { 81 | return; 82 | } 83 | final LoggerConfig loggerConfig = parseLogger(ROOT_KEY, value.trim()); 84 | if (loggerConfig != null) { 85 | configure.setRootLogLevel(loggerConfig.getLevel()); 86 | configure.setRootTag(loggerConfig.getTag()); 87 | configure.setRootShowThread(loggerConfig.isShowThreadName()); 88 | internalLogger 89 | .verbose("properties: logger root : %s", loggerConfig); 90 | } else { 91 | internalLogger.verbose("properties: parse logger root failed : %s", 92 | value); 93 | } 94 | } 95 | 96 | private void parseLoggers(final Configure configure) { 97 | final int loggerPrefixLength = LOGGER_PREFIX.length(); 98 | for (final Enumeration names = properties.propertyNames(); names 99 | .hasMoreElements();) { 100 | final String propertyName = (String) names.nextElement(); 101 | if (TextUtils.isEmpty(propertyName) 102 | || propertyName.length() <= loggerPrefixLength) { 103 | continue; 104 | } 105 | if (propertyName.startsWith(LOGGER_PREFIX)) { 106 | final String name = propertyName.substring( 107 | LOGGER_PREFIX.length(), propertyName.length()); 108 | if (PACKAGE_NAME_PATTERN.matcher(name).matches()) { 109 | final String propertyValue = properties 110 | .getProperty(propertyName); 111 | final LoggerConfig loggerConfig = parseLogger(name, 112 | propertyValue.trim()); 113 | if (loggerConfig != null) { 114 | configure.addLoggerConfig(name, 115 | loggerConfig.getLevel(), loggerConfig.getTag(), 116 | loggerConfig.isShowThreadName()); 117 | internalLogger.verbose("properties: logger '%s': %s", 118 | name, loggerConfig); 119 | } else { 120 | internalLogger.verbose( 121 | "properties: parse logger %s failed : %s", 122 | name, propertyValue); 123 | } 124 | } else { 125 | internalLogger 126 | .verbose( 127 | "properties: name '%s' is illegal, it should be package or class fullname", 128 | name); 129 | } 130 | } 131 | } 132 | } 133 | 134 | private LoggerConfig parseLogger(final String name, 135 | final String propertyValue) { 136 | if (TextUtils.isEmpty(propertyValue)) { 137 | internalLogger.verbose( 138 | "properties: property value of logger '%s' is empty, skip", 139 | name); 140 | return null; 141 | } 142 | LogLevel level = null; 143 | String tag = null; 144 | boolean thread = false; 145 | final Matcher matcher = LOGGER_VALUE_PATTERN.matcher(propertyValue); 146 | if (matcher.matches()) { 147 | level = logLevelValueOf(matcher.group(1)); 148 | tag = tagValueOf(name, matcher.group(3)); 149 | thread = Boolean.valueOf(matcher.group(5)); 150 | } 151 | return level == null ? null 152 | : new LoggerConfig(name, tag, level, thread); 153 | } 154 | 155 | private LogLevel logLevelValueOf(final String str) { 156 | try { 157 | return LogLevel.valueOf(str.toUpperCase(Locale.ENGLISH)); 158 | } catch (final IllegalArgumentException e) { 159 | return null; 160 | } 161 | } 162 | 163 | private String tagValueOf(final String name, final String str) { 164 | return TextUtils.isEmpty(str) ? null : str; 165 | } 166 | 167 | private void parseHandleEx(final Configure configure) { 168 | final String value = (String) properties.get(HANDLE_EXCEPTION_KEY); 169 | if (TextUtils.isEmpty(value)) { 170 | return; 171 | } 172 | final Boolean handleException = booleanValueOf(value.trim()); 173 | if (handleException == true) { 174 | configure.setHandleException(true); 175 | internalLogger 176 | .verbose("properties: enable logging uncaught exception"); 177 | } else if (handleException == false) { 178 | configure.setHandleException(false); 179 | internalLogger 180 | .verbose("properties: disable logging uncaught exception"); 181 | } 182 | } 183 | 184 | private void parseLogCat(final Configure configure) { 185 | final String value = (String) properties.get(LOGCAT_KEY); 186 | if (TextUtils.isEmpty(value)) { 187 | return; 188 | } 189 | final Boolean useLogCatAppender = booleanValueOf(value.trim()); 190 | if (useLogCatAppender == true) { 191 | configure.setUseLogCatAppender(true); 192 | internalLogger.verbose("properties: enable logcat appender"); 193 | } else if (useLogCatAppender == false) { 194 | configure.setUseLogCatAppender(false); 195 | internalLogger.verbose("properties: disable logcat appender"); 196 | } 197 | } 198 | 199 | private void parseTextView(final Configure configure) { 200 | final String value = (String) properties.get(TEXTVIEW_KEY); 201 | if (TextUtils.isEmpty(value)) { 202 | return; 203 | } 204 | final Boolean useTextViewAppender = booleanValueOf(value.trim()); 205 | if (useTextViewAppender == true) { 206 | configure.setUseTextViewAppender(true); 207 | internalLogger.verbose("properties: enable textview appender"); 208 | } else if (useTextViewAppender == false) { 209 | configure.setUseTextViewAppender(false); 210 | internalLogger.verbose("properties: disable textview appender"); 211 | } 212 | } 213 | 214 | private void parseFile(final Configure configure) { 215 | final String value = (String) properties.get(FILE_KEY); 216 | if (TextUtils.isEmpty(value)) { 217 | return; 218 | } 219 | final Matcher matcher = FILE_VALUE_PATTERN.matcher(value.trim()); 220 | Boolean use = null; 221 | File dir = null; 222 | Long size = null; 223 | Boolean useGZip = null; 224 | if (matcher.matches()) { 225 | use = booleanValueOf(matcher.group(1)); 226 | dir = logDirValueOf(matcher.group(3)); 227 | try { 228 | size = Long.valueOf(matcher.group(5)); 229 | } catch (final NumberFormatException e) { 230 | } 231 | useGZip = booleanValueOf(matcher.group(7)); 232 | } 233 | if (use == true) { 234 | configure.setUseFileAppender(true); 235 | if (dir != null) { 236 | configure.setLogFileDir(dir); 237 | } 238 | if (size != null) { 239 | configure.setLogFileRollingSize(size); 240 | } 241 | if (useGZip != null) { 242 | configure.setCompressLogFiles(useGZip); 243 | } 244 | internalLogger.verbose("properties: enable rolling file appender"); 245 | } else { 246 | configure.setUseFileAppender(false); 247 | internalLogger.verbose("properties: disable rolling file appender"); 248 | } 249 | 250 | } 251 | 252 | private File logDirValueOf(final String str) { 253 | File dir; 254 | if (TextUtils.isEmpty(str)) { 255 | return null; 256 | } else if (str.startsWith(FILE_INTERNAL)) { 257 | dir = new File("/data/data/" + LoggerFactory.getPackageName() 258 | + "/files", str.substring(FILE_INTERNAL.length(), 259 | str.length())); 260 | } else if (str.startsWith(FILE_EXTERNAL)) { 261 | if (!Environment.getExternalStorageState().equals( 262 | Environment.MEDIA_MOUNTED)) { 263 | internalLogger 264 | .verbose("properties: external sdcard not mounted, use default log file path"); 265 | dir = new File("/data/data/" + LoggerFactory.getPackageName() 266 | + "/files"); 267 | } else { 268 | final String external = Environment 269 | .getExternalStorageDirectory().getPath() 270 | + "/Android/data/" 271 | + LoggerFactory.getPackageName() 272 | + "/files"; 273 | dir = new File(external, str.substring(FILE_EXTERNAL.length(), 274 | str.length())); 275 | } 276 | } else { 277 | dir = new File(str); 278 | } 279 | internalLogger.verbose("properties: log file path: %s", dir.getPath()); 280 | return dir; 281 | } 282 | 283 | private Boolean booleanValueOf(final String str) { 284 | if (TextUtils.isEmpty(str)) { 285 | return null; 286 | } 287 | if ("true".equalsIgnoreCase(str)) { 288 | return Boolean.TRUE; 289 | } else if ("false".equalsIgnoreCase(str)) { 290 | return Boolean.FALSE; 291 | } else { 292 | return null; 293 | } 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | 6 | public class ReflectUtils { 7 | 8 | private ReflectUtils(){ 9 | throw new UnsupportedOperationException(); 10 | } 11 | 12 | static CallerResolver callerResolver = new CallerResolver(); 13 | 14 | public static int intReflectStaticFieldValue(final String className, 15 | final String fieldName, final int defaultValue) { 16 | try { 17 | final Class clazz = Class.forName(className); 18 | final Field field = clazz.getDeclaredField(fieldName); 19 | if (Modifier.isStatic(field.getModifiers()) 20 | && 21 | field.getType().getName().equals("int")) { 22 | field.setAccessible(true); 23 | return field.getInt(null); 24 | } 25 | } catch (final Exception e) { 26 | } 27 | return defaultValue; 28 | } 29 | 30 | public static boolean booleanReflectStaticFieldValue( 31 | final String className, final String fieldName, 32 | final boolean defaultValue) { 33 | try { 34 | final Class clazz = Class.forName(className); 35 | final Field field = clazz.getDeclaredField(fieldName); 36 | if (Modifier.isStatic(field.getModifiers()) 37 | && 38 | field.getType().getName().equals("boolean")) { 39 | field.setAccessible(true); 40 | return field.getBoolean(null); 41 | } 42 | } catch (final Exception e) { 43 | } 44 | return defaultValue; 45 | } 46 | 47 | static String getCallerClassName(final String classBeingCalled) { 48 | final Class caller = callerResolver.getCaller(classBeingCalled); 49 | if (caller == null) { 50 | final StackTraceElement callerStackTrace = getCallerStackTrace( 51 | classBeingCalled); 52 | return callerStackTrace == null? null: callerStackTrace 53 | .getClassName(); 54 | } else { 55 | return caller.getName(); 56 | } 57 | } 58 | 59 | static StackTraceElement getCallerStackTrace( 60 | final String classBeingCalled) { 61 | final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 62 | if (stackTrace == null || 63 | stackTrace.length <= 0) { 64 | return null; 65 | } 66 | 67 | for (int i = 1; i < stackTrace.length; i++) { 68 | final StackTraceElement stackTraceElement = stackTrace[i]; 69 | if (stackTraceElement.getClassName().equals(classBeingCalled)) { 70 | return stackTrace[i + 1]; 71 | } 72 | } 73 | return null; 74 | } 75 | 76 | static final class CallerResolver extends SecurityManager { 77 | 78 | @SuppressWarnings("rawtypes") 79 | public Class getCaller(final String classBeingCalled) { 80 | final Class[] classContext = getClassContext(); 81 | if (classContext == null || 82 | classContext.length <= 0) { 83 | return null; 84 | } 85 | for (int i = 1; i < classContext.length; i++) { 86 | final Class clazz = classContext[i]; 87 | if (clazz.getName().equals(classBeingCalled)) { 88 | return classContext[i + 1]; 89 | } 90 | } 91 | return null; 92 | } 93 | 94 | @SuppressWarnings("rawtypes") 95 | public Class[] getClassStacks() { 96 | return getClassContext(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/Repository.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Repository { 9 | 10 | private LoggerConfig rootLoggerConfig; 11 | 12 | private Map loggerConfigs; 13 | 14 | private Map loggers; 15 | 16 | private List appenders; 17 | 18 | public Repository() { 19 | loggerConfigs = new HashMap(); 20 | loggers = new HashMap(); 21 | appenders = new ArrayList(); 22 | } 23 | 24 | public Repository(final Configure configure) { 25 | this(); 26 | setConfigure(configure); 27 | } 28 | 29 | public void setConfigure(final Configure configure) { 30 | clear(); 31 | configure.applyConfigure(this); 32 | startAppenders(); 33 | } 34 | 35 | private void clear() { 36 | LoggerFactory.disableLoggingUncaughtException(); 37 | rootLoggerConfig = null; 38 | loggerConfigs.clear(); 39 | loggers.clear(); 40 | for (final Appender appender : appenders) { 41 | appender.stop(); 42 | } 43 | appenders.clear(); 44 | } 45 | 46 | private void startAppenders() { 47 | for (final Appender appender : appenders) { 48 | appender.start(); 49 | } 50 | } 51 | 52 | public LoggerConfig getRootLoggerConfig() { 53 | return rootLoggerConfig; 54 | } 55 | 56 | public void setRootLoggerConfig(final LoggerConfig loggerConfig) { 57 | this.rootLoggerConfig = loggerConfig; 58 | } 59 | 60 | public LoggerConfig getLoggerConfig(final String name) { 61 | return loggerConfigs.get(name); 62 | } 63 | 64 | public void addLoggerConfig(final LoggerConfig loggerConfig) { 65 | loggerConfigs.put(loggerConfig.getName(), loggerConfig); 66 | } 67 | 68 | public Logger getLogger(final String name) { 69 | return loggers.get(name); 70 | } 71 | 72 | public void addLogger(final String name, final Logger logger) { 73 | loggers.put(name, logger); 74 | } 75 | 76 | public List getAppenders() { 77 | return appenders; 78 | } 79 | 80 | public void addAppender(final Appender appender) { 81 | appenders.add(appender); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/RollingFileAppender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class RollingFileAppender extends AsyncAppender { 10 | 11 | private static final byte[] LINE_BREAK_BYTES = "\n".getBytes(); 12 | 13 | private File logDir; 14 | 15 | private String packageName; 16 | 17 | private int rolling; 18 | 19 | private long rollSize; 20 | 21 | private boolean useGZip; 22 | 23 | private File logFile; 24 | 25 | private FileOutputStream out; 26 | 27 | public RollingFileAppender(final File logDir, final long rollSize, 28 | final boolean useGZip) { 29 | this.logDir = logDir; 30 | this.rollSize = rollSize; 31 | this.useGZip = useGZip; 32 | packageName = LoggerFactory.getPackageName(); 33 | } 34 | 35 | @Override 36 | protected boolean doStart() { 37 | final boolean created = createLogDir(); 38 | if (!created) { 39 | LoggerFactory.getInternalLogger().verbose( 40 | "can not create folder %s", logDir.getPath()); 41 | return false; 42 | } 43 | rolling = getRollingNumber(); 44 | logFile = getLogFile(rolling); 45 | try { 46 | out = createOrOpenLogFile(logFile); 47 | } catch (final IOException e) { 48 | e.printStackTrace(); 49 | } 50 | return super.doStart(); 51 | } 52 | 53 | @Override 54 | protected boolean doStop() { 55 | try { 56 | out.close(); 57 | } catch (final IOException e) { 58 | } 59 | return super.doStop(); 60 | } 61 | 62 | private boolean createLogDir() { 63 | if (!logDir.exists()) { 64 | return logDir.mkdirs(); 65 | } 66 | return true; 67 | } 68 | 69 | private int getRollingNumber() { 70 | final Pattern fileNamePattern = Pattern.compile("^" 71 | + packageName.replace(".", "\\.").replace("$", "\\$") 72 | + "(\\.(\\d+))?\\.log(\\.gz)?$"); 73 | int number = 0; 74 | final File[] logFiles = logDir.listFiles(); 75 | for (int i = 0; i < logFiles.length; i++) { 76 | final String logFileName = logFiles[i].getName(); 77 | final Matcher matcher = fileNamePattern.matcher(logFileName); 78 | if (matcher.find()) { 79 | try { 80 | final Integer currentNumber = Integer.valueOf(matcher 81 | .group(2)); 82 | if (number < currentNumber) { 83 | number = currentNumber; 84 | } 85 | } catch (final NumberFormatException e) { 86 | } 87 | } 88 | } 89 | return number; 90 | } 91 | 92 | private File getLogFile(final int rolling) { 93 | String logFilename = packageName; 94 | if (rolling > 0) { 95 | logFilename += "." + rolling; 96 | } 97 | logFilename += ".log"; 98 | return new File(logDir, logFilename); 99 | } 100 | 101 | private FileOutputStream createOrOpenLogFile(final File logFile) 102 | throws IOException { 103 | if (!logFile.exists()) { 104 | logFile.createNewFile(); 105 | return new FileOutputStream(logFile); 106 | } else { 107 | return new FileOutputStream(logFile, true); 108 | } 109 | } 110 | 111 | @Override 112 | protected void handleEventQueue() throws InterruptedException { 113 | try { 114 | final LogEvent event = logEventQueue.take(); 115 | final byte[] logBytes = event.toString().getBytes(); 116 | if (!logFile.exists()) { 117 | out.close(); 118 | out = createOrOpenLogFile(logFile); 119 | } 120 | final long logFileSize = out.getChannel().size(); 121 | if (logFileSize + logBytes.length > rollSize) { 122 | out.close(); 123 | if (useGZip) { 124 | Runtime.getRuntime().exec("gzip " + logFile.getPath()); 125 | } 126 | rolling++; 127 | logFile = getLogFile(rolling); 128 | out = createOrOpenLogFile(logFile); 129 | } 130 | out.write(logBytes); 131 | out.write(LINE_BREAK_BYTES); 132 | out.flush(); 133 | out.getFD().sync(); 134 | } catch (final IOException e) { 135 | e.printStackTrace(); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/TextViewAppender.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.lang.ref.WeakReference; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import android.content.Context; 7 | import android.os.Handler; 8 | import android.text.Spannable; 9 | import android.text.SpannableStringBuilder; 10 | import android.text.SpannedString; 11 | import android.text.style.ForegroundColorSpan; 12 | import android.widget.TextView; 13 | 14 | public class TextViewAppender extends AsyncAppender { 15 | 16 | private static final int MAX_LOG_TEXT_LENGHT_IN_VIEW = 5000; 17 | 18 | private static final long MIN_PRINT_PERIOD = 200; 19 | 20 | private WeakReference textViewRef; 21 | 22 | private Handler mainHandler; 23 | 24 | private SpannableStringBuilder logBuilder; 25 | 26 | private long lastUpdateMillis; 27 | 28 | public TextViewAppender() { 29 | super(); 30 | logBuilder = new SpannableStringBuilder(); 31 | } 32 | 33 | public void bind(final TextView textView) { 34 | if (textView == null) { 35 | return; 36 | } 37 | textViewRef = new WeakReference(textView); 38 | } 39 | 40 | public void unbind() { 41 | textViewRef = null; 42 | lastUpdateMillis = 0; 43 | logBuilder.clearSpans(); 44 | logBuilder.clear(); 45 | logEventQueue.clear(); 46 | } 47 | 48 | private void postLogToUIThread(final CharSequence log) { 49 | if (mainHandler == null) { 50 | final Context context = LoggerFactory.getContext(); 51 | if (context == null) { 52 | return; 53 | } else { 54 | mainHandler = new Handler(context.getMainLooper()); 55 | } 56 | } 57 | mainHandler.post(new Runnable() { 58 | 59 | @Override 60 | public void run() { 61 | if (textViewRef != null) { 62 | final TextView textView = textViewRef.get(); 63 | if (textView != null) { 64 | textView.append(log); 65 | } 66 | } 67 | } 68 | 69 | }); 70 | } 71 | 72 | @Override 73 | protected void handleEventQueue() throws InterruptedException { 74 | final LogEvent event = logEventQueue.poll(MIN_PRINT_PERIOD, 75 | TimeUnit.MILLISECONDS); 76 | if (textViewRef != null) { 77 | final TextView textView = textViewRef.get(); 78 | if (textView != null) { 79 | final long currentMillis = System.currentTimeMillis(); 80 | if (event != null) { 81 | final int start = logBuilder.length(); 82 | logBuilder.append(event.toString()); 83 | logBuilder.setSpan(new ForegroundColorSpan(event.getLevel() 84 | .getColor()), start, logBuilder.length(), 85 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 86 | logBuilder.append("\n"); 87 | } 88 | if ((currentMillis - lastUpdateMillis > MIN_PRINT_PERIOD || lastUpdateMillis == 0) 89 | && logBuilder.length() > 0) { 90 | postLogToUIThread(new SpannedString(logBuilder)); 91 | lastUpdateMillis = currentMillis; 92 | logBuilder.clearSpans(); 93 | logBuilder.clear(); 94 | } 95 | } 96 | } 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /android-app-log/src/main/java/me/allenz/androidapplog/UncaughtExceptionLogger.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import java.lang.Thread.UncaughtExceptionHandler; 4 | 5 | public class UncaughtExceptionLogger implements UncaughtExceptionHandler { 6 | 7 | private UncaughtExceptionHandler mDefaultExceptionHandler; 8 | 9 | public UncaughtExceptionLogger() { 10 | this(Thread.getDefaultUncaughtExceptionHandler()); 11 | 12 | } 13 | 14 | public UncaughtExceptionLogger( 15 | final UncaughtExceptionHandler uncaughtExceptionHandler) { 16 | this.mDefaultExceptionHandler = uncaughtExceptionHandler; 17 | } 18 | 19 | @Override 20 | public void uncaughtException(final Thread thread, final Throwable ex) { 21 | final Logger logger = LoggerFactory.getLogger(ex.getStackTrace()[0] 22 | .getClassName()); 23 | logger.error(ex, "Uncaught exception in thread [%s] :", 24 | thread.getName()); 25 | if (mDefaultExceptionHandler != null) { 26 | mDefaultExceptionHandler.uncaughtException(thread, ex); 27 | } 28 | } 29 | 30 | public UncaughtExceptionHandler getDefaultExceptionHandler() { 31 | return mDefaultExceptionHandler; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /android-app-log/src/test/java/me/allenz/androidapplog/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package me.allenz.androidapplog; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:1.3.0' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenz8512/AndroidAppLog/78fbd03fd26336b1dd0ba5e155c21ab0b6e1890f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenz8512/AndroidAppLog/78fbd03fd26336b1dd0ba5e155c21ab0b6e1890f/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenz8512/AndroidAppLog/78fbd03fd26336b1dd0ba5e155c21ab0b6e1890f/screenshots/screenshot2.png -------------------------------------------------------------------------------- /screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/allenz8512/AndroidAppLog/78fbd03fd26336b1dd0ba5e155c21ab0b6e1890f/screenshots/screenshot3.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'android-app-log' 2 | --------------------------------------------------------------------------------