Android 应用开发必备日志库 xLog

Github: https://github.com/elvishew/xLog

轻量、美观强大、可扩展的 Android 和 Java 日志库,可同时将日志打印在如 Logcat、Console 和文件中。如果你愿意,你可以将日志打印到任何地方。

Logcat 输出

快速开始

依赖

implementation 'com.elvishew:xlog:1.10.0'

初始化

XLog.init(LogLevel.ALL);

打印日志

XLog.d("你好 xlog");

打印日志

打印简单消息。

XLog.d(message);

打印带 throwable 的消息,通常用于有异常被抛出时。

XLog.e(message, throwable);

支持格式化字符串,这样你就不需要去使用 + 拼接一大串的字符串和变量。

XLog.d("你好%s,我今年 %d 岁", "Elvis", 20);

未格式化的 JSON 和 XML 字符串会被自动格式化。

XLog.json(JSON_CONTENT);
XLog.xml(XML_CONTENT);

支持所有的 CollectionMap 类型的数据。

XLog.d(array);
XLog.d(list);
XLog.d(map);

如需要,你也可以直接打印 IntentBundle 对象。

XLog.d(intent);
XLog.d(bundle);

事实上,你可以打印任何类型的对象。你甚至可以为不同类型指定不同的 ObjectFormatter,如不指定,在对象转换为字符串时,会直接调用对象类型的 toString()

XLog.d(object);

注意:以上内容中的 v/d/i/w/e 是可以相互替换的,v 表示 VERBOSEd 表示 DEBUGi for INFOw 表示 WARNINGe 表示 ERROR

配置

xLog 具有高度可扩展性,几乎任何一个组件都是可配置的。

当初始化时,可以用最简单的方式,

XLog.init(LogLevel.ALL);

也可以用高级的方式。

LogConfiguration config = new LogConfiguration.Builder()
    .logLevel(BuildConfig.DEBUG ? LogLevel.ALL             // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
        : LogLevel.NONE)
    .tag("MY_TAG")                                         // 指定 TAG,默认为 "X-LOG"
    .enableThreadInfo()                                    // 允许打印线程信息,默认禁止
    .enableStackTrace(2)                                   // 允许打印深度为 2 的调用栈信息,默认禁止
    .enableBorder()                                        // 允许打印日志边框,默认禁止
    .jsonFormatter(new MyJsonFormatter())                  // 指定 JSON 格式化器,默认为 DefaultJsonFormatter
    .xmlFormatter(new MyXmlFormatter())                    // 指定 XML 格式化器,默认为 DefaultXmlFormatter
    .throwableFormatter(new MyThrowableFormatter())        // 指定可抛出异常格式化器,默认为 DefaultThrowableFormatter
    .threadFormatter(new MyThreadFormatter())              // 指定线程信息格式化器,默认为 DefaultThreadFormatter
    .stackTraceFormatter(new MyStackTraceFormatter())      // 指定调用栈信息格式化器,默认为 DefaultStackTraceFormatter
    .borderFormatter(new MyBoardFormatter())               // 指定边框格式化器,默认为 DefaultBorderFormatter
    .addObjectFormatter(AnyClass.class,                    // 为指定类型添加对象格式化器
        new AnyClassObjectFormatter())                     // 默认使用 Object.toString()
    .addInterceptor(new BlacklistTagsFilterInterceptor(    // 添加黑名单 TAG 过滤器
        "blacklist1", "blacklist2", "blacklist3"))
    .addInterceptor(new MyInterceptor())                   // 添加一个日志拦截器
    .build();

Printer androidPrinter = new AndroidPrinter(true);         // 通过 android.util.Log 打印日志的打印器
Printer consolePrinter = new ConsolePrinter();             // 通过 System.out 打印日志到控制台的打印器
Printer filePrinter = new FilePrinter                      // 打印日志到文件的打印器
    .Builder("<日志目录全路径>")                             // 指定保存日志文件的路径
    .fileNameGenerator(new DateFileNameGenerator())        // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
    .backupStrategy(new NeverBackupStrategy())             // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
    .cleanStrategy(new FileLastModifiedCleanStrategy(MAX_TIME))     // 指定日志文件清除策略,默认为 NeverCleanStrategy()
    .flattener(new MyFlattener())                          // 指定日志平铺器,默认为 DefaultFlattener
    .build();

XLog.init(                                                 // 初始化 XLog
    config,                                                // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()
    androidPrinter,                                        // 添加任意多的打印器。如果没有添加任何打印器,会默认使用 AndroidPrinter(Android)/ConsolePrinter(java)
    consolePrinter,
    filePrinter);

初始化后,一个拥有全局配置的全局 Logger 将被创建,所有对 XLog 的打印函数的调用都会被传递到这个全局 Logger 来进行打印。

另外,你可以创建不限个数的、不同配置的 Logger

  • 基于全局 Logger将 TAG 改为 "TAG-A"
Logger logger = XLog.tag("TAG-A")
                    ... // 其他配置的覆盖
                    .build();
logger.d("定制了 TAG 的消息");
  • 基于全局 Logger,允许打印日志边框和线程信息。
Logger logger = XLog.enableBorder()
                    .enableThread()
                    ... // 其他配置的覆盖
                    .build();
logger.d("带有线程信息和日志边框的消息");

你还可以使用一次性配置来打印日志。

XLog.tag("TAG-A").d("定制了 TAG 的消息");
XLog.enableBorder().enableThread().d("带有线程信息和日志边框的消息");

打印到任何地方

只需一句调用

XLog.d("你好 xlog");

你就可以将 "你好 xlog" 打印到

  • Logcat(使用 AndroidPrinter

  • 文件(使用 FilePrinter

以及任何你想打印到的其他地方。

打印到其他地方,你只需自己实现个 Printer 接口,并在初始化过程中指定它

XLog.init(config, printer1, printer2...printerN);

或者在创建非全局 Logger 时指定它

Logger logger = XLog.printer(printer1, printer2...printerN)
                    .build();

或者在一次性打印时指定它

XLog.printer(printer1, printer2...printerN).d("用一次性配置打印的消息");

保存日志到文件

要保存日志到文件,你需要创建一个 FilePrinter

Printer filePrinter = new FilePrinter                      // 打印日志到文件的打印器
    .Builder("<日志目录全路径>")                             // 指定保存日志文件的路径
    .fileNameGenerator(new DateFileNameGenerator())        // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
    .backupStrategy(new NeverBackupStrategy())             // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
    .cleanStrategy(new FileLastModifiedCleanStrategy(MAX_TIME))     // 指定日志文件清除策略,默认为 NeverCleanStrategy()
    .flattener(new MyFlattener())                          // 指定日志平铺器,默认为 DefaultFlattener
    .build();

并在初始化时添加它

XLog.init(config, filePrinter);

或者在创建非全局 Logger 时添加它

Logger logger = XLog.printer(filePrinter)
                    ... // other overrides
                    .build();

或者在一次性打印时添加它

XLog.printer(filePrinter).d("用一次性配置打印的消息");

保存第三方库打印的日志到文件

你可以在初始化 XLog 后配置 LibCat

LibCat.config(true, filePrinter);

然后,由第三方库/模块(在同一个 app 里)打印的日志也将会被保存到文件中。

点击 LibCat 了解更多细节。

自定义日志文件名

你可以直接指定一个文件名,也可以根据一些规则将日志保存到不同文件中。

  • 使用 ChangelessFileNameGenerator,你可以指定一个不变的文件名。
日志目录
└──log
  • 使用 LevelFileNameGenerator,根据级别将日志保存到不同文件中。
日志目录
├──VERBOSE
├──DEBUG
├──INFO
├──WARN
└──ERROR
  • 使用 DateFileNameGenerator,根据日期将日志保存到不同文件中。
日志目录
├──2020-01-01
├──2020-01-02
├──2020-01-03
└──2020-01-04
  • 直接实现一个 FileNameGenerator,根据自定义的文件名生成规则来保存日志。
日志目录
├──2020-01-01-<hash1>.log
├──2020-01-01-<hash2>.log
├──2020-01-03-<hash>.log
└──2020-01-05-<hash>.log

默认情况下,会使用 ChangelessFileNameGenerator 将日志保存到一个名叫 log 的文件中。

自定义日志格式

各日志元素(日期,时间,日志级别和消息) 在被保存到日志文件前,需要被“平铺”成一个单独的字符串,你可以使用 Flattener 来做这件事。

我们已经定义了一个 PatternFlattener,足以满足你的大部分需求。你所需要做的只是,传入一个带参的 pattern

支持的参数:

参数 含义
{d} 日期时间。使用默认的日期时间格式 "yyyy-MM-dd HH:mm:ss.SSS"
{d format} 日期时间。使用自定义的日期时间格式
{l} 日志级别的缩写。例如:V/D/I
{L} 日志级别的全称。例如:VERBOSE/DEBUG/INFO
{t} 日志的 TAG
{m} 日志的消息

想象有这么一个日志,级别为 DEBUG,TAG 为 "my_tag",消息为 "简单消息",使用不同的 pattern,平铺后的日志为:

Pattern 平铺后的日志
{d} {l}/{t}: {m} 2016-11-30 13:00:00.000 D/my_tag: 简单消息
{d yyyy-MM-dd HH:mm:ss.SSS} {l}/{t}: {m} 2016-11-30 13:00:00.000 D/my_tag: 简单消息
{d yyyy/MM/dd HH:mm:ss} {l}|{t}: {m} 2016/11/30 13:00:00 D|my_tag: 简单消息
{d yy/MM/dd HH:mm:ss} {l}|{t}: {m} 16/11/30 13:00:00 D|my_tag: 简单消息
{d MM/dd HH:mm} {l}-{t}-{m} 11/30 13:00 D-my_tag-简单消息

如果你不想自己指定所谓的 pattern,可以使用 ClassicFlattener。它实际上是一个使用 {d} {l}/{t}: {m} patternPatternFlattener

默认情况下,FilePrinter 会使用 DefaultFlattener,这个平铺器只会简单地将时间戳和消息连接起来,你应该不会喜欢它,所以你得记得自己指定 Flattener,推荐使用 ClassicFlattener

自动备份

随着时间推移,日志文件可能会变得很大,大到我们不希望的程度。使用 AbstractBackupStrategy2 可以帮助我们在特定条件下创建一个全新的同名日志文件,并继续写入,而旧日志文件会被加上 .bak.n(n 是备份序号)的文件名后缀。以上过程即为“日志备份”

日志目录
├──log
├──log.bak.1
├──log.bak.2
├──log.bak.3
├──...
└──log.bak.n

如果你不喜欢 .bak.n 后缀,你可以直接使用 BackupStrategy2 指定备份文件名。

大部分时候,你只是想在日志文件达到一定大小时,触发备份。 FileSizeBackupStrategy2 刚好可以满足这个要求。

默认情况下,xLog 会使用 FileSizeBackupStrategy(1024*1024),在日志文件大小达到 1M 时触发备份,且同时最多只会有一个正在写入的文件,以及一个备份文件,这意味着你最多只能保存 2M 的日志。

所以,如果你想要保存更多的日志,以及允许更多的备份数量(而不仅仅是默认的一个),请使用 FileSizeBackupStrategy2,它允许多个备份文件同时存在。

自动清理

如果你使用会生成可变名字的 FileNameGenerator,那日志文件夹里就很可能会有不止一个日志,并且可能会越来越多。此外,如果你还使用了不限数量的备份策略,那也可能会让日志数量失控。为了防止占满磁盘,你需要一个 CleanStrategy

通常,你可以使用 FileLastModifiedCleanStrategy,它会在初始化期间自动删掉那些一段时间(如:一周)以来都未被修改的日志文件。

默认情况下,会使用 NeverCleanStrategy,它不会做任何自动清理的工作。

压缩日志文件

仅需调用

LogUtil.compress("<日志目录全路径>", "<要保存的压缩文件全路径>");

一个 zip 文件将会被创建,整个日志文件夹都将被压缩并被写入其中,这样你可以轻松收集到用户日志用于问题调试。

注意:原始的日志文件不会被删除。

拦截和过滤日志

使用 Interceptor,在每条日志被打印之前,你都会有一个机会去修改或过滤掉该日志。

你可以使用一些预定义的 Interceptor,比如 WhitelistTagsFilterInterceptor 只允许带特定 TAG 的日志被打印,BlacklistTagsFilterInterceptor 被用来过滤掉带特定 TAG 的日志。

你可以为单个 Logger 指定多个 Interceptor,这些 Interceptor 将会按被添加的顺序依次获得修改或过滤掉日志的机会。当一条日志被某 Interceptor 过滤掉,后续的 Interceptor 将不再获得处理该日志的机会。

格式化任意类型的对象

当我们直接打印对象时

XLog.d(object);

默认情况下,该对象类型的 toString 将会被调用。

有时候,对象类型的 toString 实现并不是你想要的,所以你需要 ObjectFormatter 来定义这种类型的对象在打印时该如何转化成字符串。

在 Android 平台上,我们为 IntentBundle 类型预定义了 IntentFormatterBundleFormatter

你可以为任意类型实现和添加你自己的 ObjectFormatter

请注意,ObjectFormatter 仅在直接打印一个对象时有效。

类似的库

与其他日志库对比:

  • 很好的文档化
  • 扩展性强,可轻松实现定制和功能增强

兼容性

为了与 Android Log 兼容,xLog 支持 Android Log 的所有方法。

请看 XLog 中定义的 Log 类.

Log.v(String, String);
Log.v(String, String, Throwable);
Log.d(String, String);
Log.d(String, String, Throwable);
Log.i(String, String);
Log.i(String, String, Throwable);
Log.w(String, String);
Log.w(String, String, Throwable);
Log.wtf(String, String);
Log.wtf(String, String, Throwable);
Log.e(String, String);
Log.e(String, String, Throwable);
Log.println(int, String, String);
Log.isLoggable(String, int);
Log.getStackTraceString(Throwable);

迁移

如果你有一个大型项目正在使用 Android Log, 并且很难将所有对 Android Log 的使用都换成 XLog,那么你可以使用兼容 API,简单地把所有 'android.util.Log' 替换成 'com.elvishew.xlog.XLog.Log'。
(为了更好的性能,尽量不要使用兼容 API。)

Linux/Cygwin

grep -rl "android.util.Log" <your-source-directory> | xargs sed -i "s/android.util.Log/com.elvishew.xlog.XLog.Log/g"

Mac

grep -rl "android.util.Log" <your-source-directory> | xargs sed -i "" "s/android.util.Log/com.elvishew.xlog.XLog.Log/g"

Android Studio

  1. 在 'Project' 窗口中,切换到 'Project Files' 标签,然后右键点击你的源码目录。
  2. 在出现的菜单中,点击 'Replace in Path...' 选项。
  3. 在弹出的对话框中,在 'Text to find' 区域填上 'android.util.Log','Replace with' 区域填上 'com.elvishew.xlog.XLog.Log' 然后点击 'Find'。

相比替换掉所有 'android.util.Log',还有另一种方式。你可以使用 LibCat 拦截所有通过 android.util.Log 打印的日志,将他们重定向到 XLogPrinter

Issues

如果你在使用过程中遇到任何问题或者有任何建议,请创建一个 Issue。
在创建 Issue 前,请检查类似 Issue 是否已经存在.

第三方详解

Github: https://github.com/elvishew/xLog

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Timber Google官方Demo使用的日志库 详细用法参考: Timber Timber说明: 默认的Tre...
    A代码搬运工阅读 1,595评论 0 0
  • 一、业务背景 用户使用客户端应用过程中,会遇到各种bug, 包括奔溃、数据显示错误、交互出现问题等等,虽然APP已...
    Jonrencxr阅读 10,718评论 3 6
  • 背景 在Android开发过程中难免会需要日志输出的,日志在开发调试、异常跟踪以及排查问题上都有很大的帮助,但是打...
    Coder蒋阅读 3,244评论 0 1
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,044评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,030评论 0 4