├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── allens │ │ └── xloghelper │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── allens │ │ │ └── xloghelper │ │ │ ├── MainActivity.kt │ │ │ └── MyApp.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── allens │ └── xloghelper │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── xlog ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── libs └── armeabi-v7a │ ├── libc++_shared.so │ └── libmarsxlog.so ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── allens │ └── xlog │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml └── java │ └── com │ ├── allens │ └── xlog │ │ ├── Builder.kt │ │ ├── CustomXLog.kt │ │ ├── LogLevel.kt │ │ ├── LogModel.kt │ │ └── XLogHelper.kt │ └── tencent │ └── mars │ └── xlog │ ├── Log.java │ └── Xlog.java └── test └── java └── com └── allens └── xlog └── ExampleUnitTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 22 | 23 | 24 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | xmlns:android 33 | 34 | ^$ 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | xmlns:.* 44 | 45 | ^$ 46 | 47 | 48 | BY_NAME 49 | 50 |
51 |
52 | 53 | 54 | 55 | .*:id 56 | 57 | http://schemas.android.com/apk/res/android 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | .*:name 67 | 68 | http://schemas.android.com/apk/res/android 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | name 78 | 79 | ^$ 80 | 81 | 82 | 83 |
84 |
85 | 86 | 87 | 88 | style 89 | 90 | ^$ 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 99 | .* 100 | 101 | ^$ 102 | 103 | 104 | BY_NAME 105 | 106 |
107 |
108 | 109 | 110 | 111 | .* 112 | 113 | http://schemas.android.com/apk/res/android 114 | 115 | 116 | ANDROID_ATTRIBUTE_ORDER 117 | 118 |
119 |
120 | 121 | 122 | 123 | .* 124 | 125 | .* 126 | 127 | 128 | BY_NAME 129 | 130 |
131 |
132 |
133 |
134 | 135 | 137 |
138 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 基于微信XLog的日志框架&&对于XLog的分析 3 | 4 | 5 | 6 | > 建议在 [个人博客](https://allens.icu/posts/60625924/#more) 中查看,阅读体验更佳 7 | 8 | # 前言 9 | 10 | 之前写过一个 日志框架[LogHelper](https://github.com/JiangHaiYang01/LogHelper) ,是基于 Logger 开源库封装的,当时的因为项目本身的日志不是很多,完全可以使用,最近和其他公司合作,在一个新的项目上反馈,说在 大量log 的情况下会影响到手机主体功能的使用。从而让我对之前的日志行为做了一个深刻的反省 11 | 12 | 13 | 随后在开发群中咨询了其他开发的小伙伴,如果追求性能,可以研究一下 微信的 xlog ,也是本篇博客的重点 14 | 15 | 16 | # xlog 是什么 17 | 18 | xlog 是什么 这个问题 我这也是在[【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog](https://zhuanlan.zhihu.com/p/23879436)得到了答案 19 | 20 | 简单来说 ,就是腾讯团队分享的基于 c/c++ 高可靠性高性能的运行期日志组件 21 | 22 | 23 | 24 | # 官网的 sample 25 | 26 | 知道了他是什么,就要只要他是怎么用的,打开github 找到官网[Tencent/mars](https://github.com/Tencent/mars) 27 | 28 | 使用非常简单 29 | 30 | ## 下载库 31 | 32 | ``` 33 | dependencies { 34 | compile 'com.tencent.mars:mars-xlog:1.2.3' 35 | } 36 | ``` 37 | 38 | ## 使用 39 | 40 | ``` 41 | System.loadLibrary("c++_shared"); 42 | System.loadLibrary("marsxlog"); 43 | 44 | final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath(); 45 | final String logPath = SDCARD + "/marssample/log"; 46 | 47 | // this is necessary, or may crash for SIGBUS 48 | final String cachePath = this.getFilesDir() + "/xlog" 49 | 50 | //init xlog 51 | if (BuildConfig.DEBUG) { 52 | Xlog.appenderOpen(Xlog.LEVEL_DEBUG, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, ""); 53 | Xlog.setConsoleLogOpen(true); 54 | 55 | } else { 56 | Xlog.appenderOpen(Xlog.LEVEL_INFO, Xlog.AppenderModeAsync, cachePath, logPath, "MarsSample", 0, ""); 57 | Xlog.setConsoleLogOpen(false); 58 | } 59 | 60 | Log.setLogImp(new Xlog()); 61 | ``` 62 | 63 | OK 实现了他的功能 64 | 65 | > 不要高兴的太早,后续的问题都头大 66 | 67 | 68 | # 分析各个方法的作用 69 | 70 | 71 | 知道了最简单的用法,就想看看他支持哪些功能 72 | 73 | 按照官网的demo 首先分析一下``appenderOpen`` 74 | 75 | ## appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays, String pubkey) 76 | 77 | ### level 78 | 79 | 日志级别 没啥好说的 XLog 中已经写得很清楚了 80 | 81 | 82 | ``` 83 | public static final int LEVEL_ALL = 0; 84 | public static final int LEVEL_VERBOSE = 0; 85 | public static final int LEVEL_DEBUG = 1; 86 | public static final int LEVEL_INFO = 2; 87 | public static final int LEVEL_WARNING = 3; 88 | public static final int LEVEL_ERROR = 4; 89 | public static final int LEVEL_FATAL = 5; 90 | public static final int LEVEL_NONE = 6; 91 | ``` 92 | 93 | 94 | > 值得注意的地方 debug 版本下建议把控制台日志打开,日志级别设为 Verbose 或者 Debug, release 版本建议把控制台日志关闭,日志级别使用 Info. 95 | 96 | 这个在官网的 [接入指南](https://github.com/Tencent/mars/wiki/Mars-Android-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97) 97 | 98 | 99 | 这里也可以使用 100 | 101 | ```java 102 | public static native void setLogLevel(int logLevel); 103 | ``` 104 | 方法设置 105 | 106 | ### mode 107 | 108 | 写入的模式 109 | 110 | - public static final int AppednerModeAsync = 0; 111 | 112 | 异步写入 113 | 114 | - public static final int AppednerModeSync = 1; 115 | 116 | 同步写入 117 | 118 | 119 | 同步写入,可以理解为实时的日志,异步则不是 120 | 121 | > Release版本一定要用 AppednerModeAsync, Debug 版本两个都可以,但是使用 AppednerModeSync 可能会有卡顿 122 | 123 | 这里也可以使用 124 | 125 | ```java 126 | public static native void setAppenderMode(int mode); 127 | ``` 128 | 方法设置 129 | 130 | ### cacheDir 设置缓存目录 131 | 132 | 133 | 134 | 缓存目录,当 logDir 不可写时候会写进这个目录,可选项,不选用请给 "", 如若要给,建议给应用的 /data/data/packname/files/log 目录。 135 | 136 | 会在目录下生成后缀为 .mmap3 的缓存文件, 137 | 138 | 139 | ### logDir 设置写入的文件目录 140 | 141 | 真正的日志,后缀为 .xlog 142 | 143 | 144 | > 日志写入目录,请给单独的目录,除了日志文件不要把其他文件放入该目录,不然可能会被日志的自动清理功能清理掉。 145 | 146 | ### nameprefix 设置日志文件名的前缀 147 | 148 | 日志文件名的前缀,例如该值为TEST,生成的文件名为:TEST_20170102.xlog。 149 | 150 | ### cacheDays 151 | 152 | 一般情况下填0即可。非0表示会在 _cachedir 目录下存放几天的日志。 153 | 154 | 这里的描述比较晦涩难懂,当我设置这个参数非0 的时候 会发现 原本设置在 logDir 目录下的文件 出现在了 cacheDir 155 | 156 | 例如 正常应该是 157 | 158 | 159 | 文件结构 160 | 161 | ```java 162 | - cacheDir 163 | - log.mmap3 164 | - logDir 165 | - log_20200710.xlog 166 | - log_20200711.xlog 167 | ``` 168 | 169 | 变成这样 170 | 171 | ```java 172 | - cacheDir 173 | - log.mmap3 174 | - log_20200710.xlog 175 | - log_20200711.xlog 176 | - logDir 177 | 178 | ``` 179 | 180 | 全部到了 cacheDir 下面 181 | 182 | > cacheDays 的意思是 在多少天以后 从缓存目录移到日志目录 183 | 184 | 185 | ### pubkey 设置加密的 pubkey 186 | 187 | 这里涉及到了日志的加密与解密,下面会专门介绍 188 | 189 | 190 | ## setMaxFileSize 设置文件大小 191 | 192 | 在 Xlog 下有一个 native 方法 193 | 194 | ``` 195 | public static native void setMaxFileSize(long size); 196 | ``` 197 | 198 | 他表示 最大文件大小,这里需要说一下,原本的默认设置 是一天一个日志文件在 [appender.h](https://github.com/Tencent/mars/blob/master/mars/log/appender.h) 描述的很清楚 199 | 200 | ```java 201 | /* 202 | * By default, all logs will write to one file everyday. You can split logs to multi-file by changing max_file_size. 203 | * 204 | * @param _max_byte_size Max byte size of single log file, default is 0, meaning do not split. 205 | */ 206 | void appender_set_max_file_size(uint64_t _max_byte_size); 207 | ``` 208 | 209 | 默认情况下,所有日志每天都写入一个文件。可以通过更改max_file_size将日志分割为多个文件。单个日志文件的最大字节大小,默认为0,表示不分割 210 | 211 | 212 | 213 | 当超过设置的文件大小以后。文件会变成如下目录结构 214 | 215 | 216 | ```java 217 | - cacheDir 218 | - log.mmap3 219 | - logDir 220 | - log_20200710.xlog 221 | - log_20200710_1.xlog 222 | - log_20200710_2.xlog 223 | ``` 224 | 225 | 226 | 在 [appender.cc](https://github.com/Tencent/mars/blob/master/mars/log/src/appender.cc) 对应的有如下逻辑, 227 | 228 | ``` 229 | static long __get_next_fileindex(const std::string& _fileprefix, const std::string& _fileext) { 230 | ... 231 | return (filesize > sg_max_file_size) ? index + 1 : index; 232 | 233 | ``` 234 | 235 | 236 | ## setConsoleLogOpen 设置是否在控制台答应日志 237 | 238 | ···java 239 | public static native void setConsoleLogOpen(boolean isOpen); 240 | ··· 241 | 242 | 设置是否在控制台答应日志 243 | 244 | ## setErrLogOpen 245 | 246 | 这个方法是没用的,一开始以为哪里继承的有问题,在查看源码的时候发现 他是一个空方法,没有应用 247 | 248 | ![](http://allens-blog.oss-cn-beijing.aliyuncs.com/allens-blog/y9lop.png) 249 | 250 | 使用的话会导致程序异常,在自己编译的so 中我就把它给去掉了 251 | 252 | ## setMaxAliveTime 设置单个文件最大保留时间 253 | 254 | ```java 255 | public static native void setMaxAliveTime(long duration); 256 | ``` 257 | 258 | 259 | 置单个文件最大保留时间 单位是秒,这个方法有3个需要注意的地方, 260 | 261 | - 必须在 appenderOpen 方法之前才有效 262 | - 最小的时间是 一天 263 | - 默认的时间是10天 264 | 265 | 在 [appender.cc](https://github.com/Tencent/mars/blob/master/mars/log/src/appender.cc) 中可以看到 266 | 267 | ``` 268 | static const long kMaxLogAliveTime = 10 * 24 * 60 * 60; // 10 days in second 269 | static const long kMinLogAliveTime = 24 * 60 * 60; // 1 days in second 270 | static long sg_max_alive_time = kMaxLogAliveTime; 271 | 272 | .... 273 | 274 | 275 | void appender_set_max_alive_duration(long _max_time) { 276 | if (_max_time >= kMinLogAliveTime) { 277 | sg_max_alive_time = _max_time; 278 | } 279 | } 280 | ``` 281 | 282 | 默认的时间是10天 283 | 284 | ## appenderClose 285 | 286 | 在 [文档](https://github.com/Tencent/mars#mars_cn)中介绍说是在 程序退出时关闭日志 调用appenderClose的方法 287 | 288 | 然而在实际情况中 Application 类的 onTerminate() 只有在模拟器中才会生效,在真机中无效的, 289 | 290 | 如果在程序退出的时候没有触发 appenderClose 那么在下一次启动的时候,xlog 也会把日志写入到文件中 291 | 292 | 293 | 所以如何触发呢? 294 | 295 | 建议尽可能的去触发他 例如用户双击back 退出的情况下 你肯定是知道的 296 | 如果放在后台被杀死了,这个时候也真的没办法刷新,也没关系,上面也说了,再次启动的时候会刷新到日志中, 297 | 298 | ## appenderFlush 299 | 300 | 301 | 当日志写入模式为异步时,调用该接口会把内存中的日志写入到文件。 302 | 303 | isSync : true 为同步 flush,flush 结束后才会返回。 304 | isSync : false 为异步 flush,不等待 flush 结束就返回。 305 | 306 | 307 | # 日志文件的加密 308 | 309 | 这一块单独拿出来说明,是因为之前使用上遇到了坑 310 | 311 | 首先是这个 入参 PUB_KEY,一脸懵,是个啥, 312 | 313 | 在 [mars/blob/master/mars/log/crypt/gen_key.py](https://github.com/Tencent/mars/blob/master/mars/log/crypt/gen_key.py) 这个就是能够获取到 PUB_KEY 的方法 314 | 315 | 运行如下 316 | 317 | ```python 318 | $ python gen_key.py 319 | WARNING: Executing a script that is loading libcrypto in an unsafe way. This will fail in a future version of macOS. Set the LIBRESSL_REDIRECT_STUB_ABORT=1 in the environment to force this into an error. 320 | save private key 321 | 471e607b1bb3760205f74a5e53d2764f795601e241ebc780c849e7fde1b4ce40 322 | 323 | appender_open's parameter: 324 | 300330b09d9e771d6163bc53a4e23b188ac9b2f5c7150366835bce3a12b0c8d9c5ecb0b15274f12b2dffae7f4b11c3b3d340e0521e8690578f51813c93190e1e 325 | ``` 326 | 327 | 328 | 上面的 private key 自己保存好 329 | 330 | appender_open's parameter: 就是需要的 PUB_KEY 331 | 332 | 333 | # 日志文件的解密 334 | 335 | 336 | 上面已经知道如何加密了,现在了解一下如何解密 337 | 338 | ## 下载pyelliptic1 339 | 340 | 在[Xlog 加密使用指引](https://github.com/Tencent/mars/wiki/Xlog-%E5%8A%A0%E5%AF%86%E4%BD%BF%E7%94%A8%E6%8C%87%E5%BC%95)中能够看到 341 | 342 | 需要下载 [pyelliptic1.5.7](https://github.com/yann2192/pyelliptic/releases/tag/1.5.7) 343 | 然后编译 否则下面的命令会失败 344 | 345 | 346 | 347 | ## 直接解密脚本 348 | 349 | 350 | 351 | xlog 很贴心的给我们提供了两个脚本 352 | 353 | 使用 [decode_mars_nocrypt_log_file.py](https://github.com/Tencent/mars/blob/master/mars/log/crypt/decode_nomars_crypt_log_file.py) 解压没有加密的 354 | 355 | ```python 356 | python decode_mars_nocrypt_log_file [path] 357 | ``` 358 | 359 | 使用 [decode_mars_crypt_log_file.py](https://github.com/Tencent/mars/blob/master/mars/log/crypt/decode_mars_crypt_log_file.py) 加密的文件 360 | 361 | 在使用之前需要将 脚本中的 362 | 363 | ``` 364 | PRIV_KEY = "145aa7717bf9745b91e9569b80bbf1eedaa6cc6cd0e26317d810e35710f44cf8" 365 | PUB_KEY = "572d1e2710ae5fbca54c76a382fdd44050b3a675cb2bf39feebe85ef63d947aff0fa4943f1112e8b6af34bebebbaefa1a0aae055d9259b89a1858f7cc9af9df1" 366 | ``` 367 | 368 | 改成上面自己获取到的 key 否则是解压不出来的 369 | 370 | ```python 371 | python decode_mars_crypt_log_file.py ~/Desktop/log/log_20200710.xlog 372 | ``` 373 | 374 | 直接生成一个 375 | 376 | ```java 377 | - cacheDir 378 | - log.mmap3 379 | - logDir 380 | - log_20200710.xlog 381 | - log_20200710.xlog.log 382 | ``` 383 | 384 | 也可以自定义名字 385 | 386 | ```python 387 | python decode_mars_crypt_log_file.py ~/Desktop/log/log_20200710.xlog ~/Desktop/log/1.log 388 | ``` 389 | 390 | ```java 391 | - cacheDir 392 | - log.mmap3 393 | - logDir 394 | - log_20200710.xlog 395 | - 1.log 396 | ``` 397 | 398 | # 修改日志的格式 399 | 400 | 打开我们解压好的日志查看 401 | 402 | ``` 403 | ^^^^^^^^^^Oct 14 2019^^^20:27:59^^^^^^^^^^[17223,17223][2020-07-24 +0800 09:49:19] 404 | get mmap time: 3 405 | MARS_URL: 406 | MARS_PATH: master 407 | MARS_REVISION: 85b19f92 408 | MARS_BUILD_TIME: 2019-10-14 20:27:57 409 | MARS_BUILD_JOB: 410 | log appender mode:0, use mmap:1 411 | cache dir space info, capacity:57926635520 free:52452691968 available:52452691968 412 | log dir space info, capacity:57926635520 free:52452691968 available:52452691968 413 | [I][2020-07-24 +8.0 09:49:21.179][17223, 17223][TAG][, , 0][======================> 1 414 | [I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 2 415 | [I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 3 416 | [I][2020-07-24 +8.0 09:49:21.180][17223, 17223][TAG][, , 0][======================> 4 417 | [I][2020-07-24 +8.0 09:49:21.181][17223, 17223][TAG][, , 0][======================> 5 418 | [I][2020-07-24 +8.0 09:49:21.181][17223, 17223][TAG][, , 0][======================> 6 419 | [I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 7 420 | [I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 8 421 | [I][2020-07-24 +8.0 09:49:21.182][17223, 17223][TAG][, , 0][======================> 9 422 | [I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 10 423 | [I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 11 424 | [I][2020-07-24 +8.0 09:49:21.183][17223, 17223][TAG][, , 0][======================> 12 425 | [I][2020-07-24 +8.0 09:49:21.184][17223, 17223][TAG][, , 0][======================> 13 426 | [I][2020-07-24 +8.0 09:49:21.184][17223, 17223][TAG][, , 0][======================> 14 427 | [I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 15 428 | [I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 16 429 | [I][2020-07-24 +8.0 09:49:21.185][17223, 17223][TAG][, , 0][======================> 17 430 | ``` 431 | 432 | 我擦泪 除了我们需要的信息以外,还有这么多杂七杂八的信息,如何去掉,并且自己定义一下格式 433 | 434 | 435 | 这里就需要自己去编译 so 了,好在 xlog 已经给我们提供了很好的编译代码 436 | 437 | 对应的文档 [本地编译](https://github.com/Tencent/mars/wiki/Mars-Android-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97#local_compile) 438 | 439 | 440 | > 对于编译这块按照文档来就好了 需要注意的是 441 | 442 | - 一定要用 ndk-r20 不要用最新版本的 21 443 | - 一定用 Python2.7 mac 自带 不用要 Python3 444 | 445 | 446 | ## 去掉头文件 447 | 448 | 首先我们去到这个头文件,对于一个日志框架来着,这个没啥用 449 | 450 | ``` 451 | ^^^^^^^^^^Oct 14 2019^^^20:27:59^^^^^^^^^^[17223,17223][2020-07-24 +0800 09:49:19] 452 | get mmap time: 3 453 | MARS_URL: 454 | MARS_PATH: master 455 | MARS_REVISION: 85b19f92 456 | MARS_BUILD_TIME: 2019-10-14 20:27:57 457 | MARS_BUILD_JOB: 458 | log appender mode:0, use mmap:1 459 | cache dir space info, capacity:57926635520 free:52452691968 available:52452691968 460 | log dir space info, capacity:57926635520 free:52452691968 available:52452691968 461 | ``` 462 | 463 | 在本机下载好的 mars 下,找到 [appender.cc](https://github.com/Tencent/mars/blob/master/mars/log/src/appender.cc) 将头文件去掉 464 | 465 | ![](http://allens-blog.oss-cn-beijing.aliyuncs.com/allens-blog/x381l.png) 466 | 467 | 468 | ## 修改日志格式 469 | 470 | 默认的格式很长 471 | 472 | ``` 473 | [I][2020-07-24 +8.0 09:49:21.179][17223, 17223][TAG][, , 0][======================> 1 474 | ``` 475 | [日志级别][时间][pid,tid][tag][filename,strFuncName,line][日志内容 476 | 477 | 是一个这样结构 478 | 479 | 比较乱,我们想要的日志 就时间,级别,日志内容 就行了 480 | 481 | 找到 [formater.cc](https://github.com/Tencent/mars/blob/master/mars/log/src/formater.cc) 482 | 483 | 将原本的 484 | 485 | ```java 486 | int ret = snprintf((char*)_log.PosPtr(), 1024, "[%s][%s][%" PRIdMAX ", %" PRIdMAX "%s][%s][%s, %s, %d][", // **CPPLINT SKIP** 487 | _logbody ? levelStrings[_info->level] : levelStrings[kLevelFatal], temp_time, 488 | _info->pid, _info->tid, _info->tid == _info->maintid ? "*" : "", _info->tag ? _info->tag : "", 489 | filename, strFuncName, _info->line); 490 | ``` 491 | 492 | 改成 493 | 494 | ```java 495 | int ret = snprintf((char*)_log.PosPtr(), 1024, "[%s][%s]", // **CPPLINT SKIP** 496 | temp_time, _logbody ? levelStrings[_info->level] : levelStrings[kLevelFatal] ); 497 | ``` 498 | 499 | 就行了 500 | 501 | 然后从新编译,将so 翻入项目 在看一下现在的效果 502 | 503 | ```java 504 | [2020-07-24 +8.0 11:47:42.597][I]======================>9 505 | ``` 506 | 507 | ok 打完收工 508 | 509 | 510 | # 简单的封装一下 511 | 512 | 基本上分析和实现了我们需要的功能,那么把这部分简单的封装一下 513 | 514 | 放上核心的 Builder 源码可在下面自行查看 515 | 516 | ```java 517 | package com.allens.xlog 518 | 519 | import android.content.Context 520 | import com.tencent.mars.xlog.Log 521 | import com.tencent.mars.xlog.Xlog 522 | 523 | class Builder(context: Context) { 524 | 525 | companion object { 526 | //日志的tag 527 | var tag = "log_tag" 528 | } 529 | 530 | //是否是debug 模式 531 | private var debug = true 532 | 533 | 534 | //是否打印控制台日志 535 | private var consoleLogOpen = true 536 | 537 | 538 | //是否每天一个日志文件 539 | private var oneFileEveryday = true 540 | 541 | //默认的位置 542 | private val defCachePath = context.getExternalFilesDir(null)?.path + "/mmap" 543 | 544 | // mmap 位置 默认缓存的位置 545 | private var cachePath = defCachePath 546 | 547 | //实际保存的log 位置 548 | private var logPath = context.getExternalFilesDir(null)?.path + "/logDir" 549 | 550 | //文件名称前缀 例如该值为TEST,生成的文件名为:TEST_20170102.xlog 551 | private var namePreFix = "log" 552 | 553 | //写入文件的模式 554 | private var model = LogModel.Async 555 | 556 | //最大文件大小 557 | //默认情况下,所有日志每天都写入一个文件。可以通过更改max_file_size将日志分割为多个文件。 558 | //单个日志文件的最大字节大小,默认为0,表示不分割 559 | // 最大 当文件不能超过 10M 560 | private var maxFileSize = 0L 561 | 562 | //日志级别 563 | //debug 版本下建议把控制台日志打开,日志级别设为 Verbose 或者 Debug, release 版本建议把控制台日志关闭,日志级别使用 Info. 564 | private var logLevel = LogLevel.LEVEL_INFO 565 | 566 | //通过 python gen_key.py 获取到的公钥 567 | private var pubKey = "" 568 | 569 | //单个文件最大保留时间 最小 1天 默认时间 10天 570 | private var maxAliveTime = 10 571 | 572 | //缓存的天数 一般情况下填0即可。非0表示会在 _cachedir 目录下存放几天的日志。 573 | //原来缓存日期的意思是几天后从缓存目录移到日志目录 574 | private var cacheDays = 0 575 | 576 | fun setCachePath(cachePath: String): Builder { 577 | this.cachePath = cachePath 578 | return this 579 | } 580 | 581 | fun setLogPath(logPath: String): Builder { 582 | this.logPath = logPath 583 | return this 584 | } 585 | 586 | 587 | fun setNamePreFix(namePreFix: String): Builder { 588 | this.namePreFix = namePreFix 589 | return this 590 | } 591 | 592 | fun setModel(model: LogModel): Builder { 593 | this.model = model 594 | return this 595 | } 596 | 597 | fun setPubKey(key: String): Builder { 598 | this.pubKey = key 599 | return this 600 | } 601 | 602 | //原来缓存日期的意思是几天后从缓存目录移到日志目录 默认 0 即可 603 | //如果想让文件保留多少天 用 [setMaxAliveTime] 方法即可 604 | //大于 0 的时候 默认会放在缓存的位置上 [cachePath] 605 | fun setCacheDays(days: Int): Builder { 606 | if (days < 0) { 607 | this.cacheDays = 0 608 | } else { 609 | this.cacheDays = days 610 | } 611 | return this 612 | } 613 | 614 | fun setDebug(debug: Boolean): Builder { 615 | this.debug = debug 616 | return this 617 | } 618 | 619 | fun setLogLevel(level: LogLevel): Builder { 620 | this.logLevel = level 621 | return this 622 | } 623 | 624 | fun setConsoleLogOpen(consoleLogOpen: Boolean): Builder { 625 | this.consoleLogOpen = consoleLogOpen 626 | return this 627 | } 628 | 629 | 630 | fun setTag(logTag: String): Builder { 631 | tag = logTag 632 | return this 633 | } 634 | 635 | 636 | /** 637 | * [isOpen] true 设置每天一个日志文件 638 | * false 那么 [setMaxFileSize] 生效 639 | */ 640 | fun setOneFileEveryday(isOpen: Boolean): Builder { 641 | this.oneFileEveryday = isOpen 642 | return this 643 | } 644 | 645 | fun setMaxFileSize(maxFileSize: Float): Builder { 646 | when { 647 | maxFileSize < 0 -> { 648 | this.maxFileSize = 0L 649 | } 650 | maxFileSize > 10 -> { 651 | this.maxFileSize = (10 * 1024 * 1024).toLong() 652 | } 653 | else -> { 654 | this.maxFileSize = (maxFileSize * 1024 * 1024).toLong() 655 | } 656 | } 657 | return this 658 | } 659 | 660 | /** 661 | * [day] 设置单个文件的过期时间 默认10天 在程序启动30S 以后会检查过期文件 662 | * 过期时间依据 当前系统时间 - 文件最后修改时间计算 663 | * 默认 单个文件保存 10天 664 | */ 665 | fun setMaxAliveTime(day: Int): Builder { 666 | when { 667 | day < 0 -> { 668 | this.maxAliveTime = 0 669 | } 670 | day > 10 -> { 671 | this.maxAliveTime = 10 672 | } 673 | else -> { 674 | this.maxAliveTime = day 675 | } 676 | } 677 | return this 678 | } 679 | 680 | fun init() { 681 | 682 | if (!debug) { 683 | //判断如果是release 就强制使用 异步 684 | model = LogModel.Async 685 | //日志级别使用 Info 686 | logLevel = LogLevel.LEVEL_INFO 687 | } 688 | 689 | if (cachePath.isEmpty()) { 690 | //cachePath这个参数必传,而且要data下的私有文件目录,例如 /data/data/packagename/files/xlog, mmap文件会放在这个目录,如果传空串,可能会发生 SIGBUS 的crash。 691 | cachePath = defCachePath 692 | } 693 | 694 | 695 | android.util.Log.i(tag, "Xlog=========================================>") 696 | android.util.Log.i( 697 | tag, 698 | "info" + "\n" 699 | + "level:" + logLevel.level + "\n" 700 | + "model:" + model.model + "\n" 701 | + "cachePath:" + cachePath + "\n" 702 | + "logPath:" + logPath + "\n" 703 | + "namePreFix:" + namePreFix + "\n" 704 | + "cacheDays:" + cacheDays + "\n" 705 | + "pubKey:" + pubKey + "\n" 706 | + "consoleLogOpen:" + consoleLogOpen + "\n" 707 | + "maxFileSize:" + maxFileSize + "\n" 708 | ) 709 | 710 | android.util.Log.i(tag, "Xlog=========================================<") 711 | Xlog.setConsoleLogOpen(consoleLogOpen) 712 | //每天一个日志文件 713 | if (oneFileEveryday) { 714 | Xlog.setMaxFileSize(0) 715 | } else { 716 | Xlog.setMaxFileSize(maxFileSize) 717 | } 718 | 719 | Xlog.setMaxAliveTime((maxAliveTime * 24 * 60 * 60).toLong()) 720 | 721 | Xlog.appenderOpen( 722 | logLevel.level, 723 | model.model, 724 | cachePath, 725 | logPath, 726 | namePreFix, 727 | cacheDays, 728 | pubKey 729 | ) 730 | Log.setLogImp(Xlog()) 731 | } 732 | 733 | 734 | } 735 | ``` 736 | 737 | # 下载 738 | Step 1. Add the JitPack repository to your build file 739 | Add it in your root build.gradle at the end of repositories: 740 | ``` 741 | allprojects { 742 | repositories { 743 | ... 744 | maven { url 'https://www.jitpack.io' } 745 | } 746 | } 747 | 748 | ``` 749 | Step 2. Add the dependency 750 | ``` 751 | dependencies { 752 | implementation 'com.github.JiangHaiYang01:XLogHelper:Tag' 753 | } 754 | ``` 755 | 756 | 添加 abiFilter 757 | 758 | ``` 759 | android { 760 | compileSdkVersion 30 761 | buildToolsVersion "30.0.1" 762 | 763 | defaultConfig { 764 | ... 765 | ndk { 766 | abiFilter "armeabi-v7a" 767 | } 768 | } 769 | 770 | ... 771 | } 772 | ``` 773 | 774 | 775 | 776 | > 当前最新版本 777 | 778 | [![](https://www.jitpack.io/v/JiangHaiYang01/XLogHelper.svg)](https://www.jitpack.io/#JiangHaiYang01/XLogHelper) 779 | 780 | 781 | # 使用 782 | 783 | ## 初始化,建议放在 Application 中 784 | 785 | ``` 786 | XLogHelper.create(this) 787 | .setModel(LogModel.Async) 788 | .setTag("TAG") 789 | .setConsoleLogOpen(true) 790 | .setLogLevel(LogLevel.LEVEL_INFO) 791 | .setNamePreFix("log") 792 | .setPubKey("572d1e2710ae5fbca54c76a382fdd44050b3a675cb2bf39feebe85ef63d947aff0fa4943f1112e8b6af34bebebbaefa1a0aae055d9259b89a1858f7cc9af9df1") 793 | .setMaxFileSize(1f) 794 | .setOneFileEveryday(true) 795 | .setCacheDays(0) 796 | .setMaxAliveTime(2) 797 | .init() 798 | ``` 799 | 800 | 使用 801 | ``` 802 | XLogHelper.i("======================> %s", i) 803 | XLogHelper.e("======================> %s", i) 804 | ``` 805 | 806 | # 源码 807 | 808 | [GitHub](https://github.com/JiangHaiYang01/XLogHelper) 809 | [博客](https://allens.icu/posts/60625924/#more) 810 | [掘金](https://juejin.im/post/5f110c786fb9a07e802052cd) 811 | 812 | 813 | 814 | 815 | # 参考 816 | 817 | 818 | [Tencent/mars](https://github.com/Tencent/mars) 819 | [Mars Android 接口详细说明](https://github.com/Tencent/mars/wiki/Mars-Android-%E6%8E%A5%E5%8F%A3%E8%AF%A6%E7%BB%86%E8%AF%B4%E6%98%8E) 820 | [【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog](https://zhuanlan.zhihu.com/p/23879436) 821 | [本地编译](https://github.com/Tencent/mars/wiki/Mars-Android-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97#local_compile) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.1" 8 | 9 | defaultConfig { 10 | applicationId "com.allens.xloghelper" 11 | minSdkVersion 19 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | 18 | ndk { 19 | abiFilter "armeabi-v7a" 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: "libs", include: ["*.jar"]) 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 35 | implementation 'androidx.core:core-ktx:1.3.0' 36 | implementation 'androidx.appcompat:appcompat:1.1.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 41 | 42 | 43 | implementation project(':xlog') 44 | // implementation 'com.github.JiangHaiYang01:XLogHelper:0.0.1' 45 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/allens/xloghelper/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.allens.xloghelper 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.allens.xloghelper", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/allens/xloghelper/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.allens.xloghelper 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.allens.xlog.XLogHelper 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | 13 | 14 | //添加日志 15 | add.setOnClickListener { 16 | 17 | for (i in 1..10) { 18 | XLogHelper.i("======================> %s", "阿斯蒂芬") 19 | } 20 | for (i in 1..10) { 21 | XLogHelper.e("======================> %s index %s", "fuck ", i) 22 | } 23 | for (i in 1..10) { 24 | XLogHelper.v("======================> %s", i) 25 | } 26 | } 27 | 28 | 29 | //close 之后不能写入 30 | close.setOnClickListener { 31 | XLogHelper.close() 32 | } 33 | 34 | 35 | //flush 之后 还能够再次写入 36 | flush.setOnClickListener { 37 | XLogHelper.flush(true) 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/allens/xloghelper/MyApp.kt: -------------------------------------------------------------------------------- 1 | package com.allens.xloghelper 2 | 3 | import android.app.Application 4 | import com.allens.xlog.LogLevel 5 | import com.allens.xlog.LogModel 6 | import com.allens.xlog.XLogHelper 7 | 8 | class MyApp :Application(){ 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | XLogHelper.create(this) 13 | .setModel(LogModel.Async) 14 | .setTag("TAG") 15 | .setDebug(true) 16 | .setConsoleLogOpen(true) 17 | .setLogLevel(LogLevel.LEVEL_INFO) 18 | .setNamePreFix("log") 19 | .setPubKey("572d1e2710ae5fbca54c76a382fdd44050b3a675cb2bf39feebe85ef63d947aff0fa4943f1112e8b6af34bebebbaefa1a0aae055d9259b89a1858f7cc9af9df1") 20 | .setMaxFileSize(1f) 21 | .setOneFileEveryday(true) 22 | .setCacheDays(0) 23 | .setMaxAliveTime(2) 24 | .init() 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 |