Square logcat - 小巧轻量的Kotlin日志API

512 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Square logcat是一款小巧的Kotlin API, 在Android常规Log类基础之上, 进行轻量级的日志打印.

安装

首先, 在build.gradle文件中添加logcat依赖:

dependencies {
  implementation 'com.squareup.logcat:logcat:0.1'
}

之后, 在Application#onCreate函数中进行初始化:

// Log all priorities in debug builds, no-op in release builds.
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = VERBOSE)

Square logcat在release版本中不起任何作用, 仅仅在debug版本中进行日志打印.

API及其使用

logcat()函数有3个参数, 分别是 priority: Int, tag: String, 和生成String的lambda. 其中priority&tag是可选的, 而生成String的lambda则是必选的. 只有在logger已经安装且logger认为当前优先级可以打印日志时, lambda才会被评估.

priority的默认值为 LogPriority.DEBUG.

tag的默认值是日志调用地的类名字, 且没有任何额外的运行时消耗. 之所以没有额外的运行时消耗, 是因为logcat()函数是Any的内联扩展函数, 能够访问Any#this, 而从this当中能够解析出类的名字. 若要从没有this的独立函数中打印日志, 则使用要求tag参数的logcat扩展.

logcat函数并不接收Throwable参数. 相反, 该库提供了Throwable扩展函数: Throwable.asLog(), 该扩展函数返回了一个用于日志输出的String.

import logcat.LogPriority.INFO
import logcat.asLog
import logcat.logcat

class MouseController {

  fun play() {
    val state = "CHEEZBURGER"
    logcat { "I CAN HAZ $state?" }
    // logcat output: D/MouseController: I CAN HAZ CHEEZBURGER?

    logcat(INFO) { "DID U ASK 4 MOAR INFO?" }
    // logcat output: I/MouseController: DID U ASK 4 MOAR INFO?

    logcat { exception.asLog() }
    // logcat output: D/MouseController: java.lang.RuntimeException: FYLEZ KERUPTED
    //                        at sample.MouseController.play(MouseController.kt:22)
    //                        ...

    logcat("Lolcat") { "OH HI" }
    // logcat output: D/Lolcat: OH HI
  }
}

Motivations

该库的产生是为了满足Square应用Point of Sale的特定需求. 在Square logcat之前, 应用Point of Sale重度使用了Timber. Timber拥有简洁的API且它的DebugTree有能力自动地找出在哪个类中调用及将该类的名字作为tag.

之所以要在应用Point of Sale中替换TimberSquare logcat, 是因为如下原因:

  • Kotlin对字符串插入的支持非常好. 将这一点用于日志是一个很令人兴奋的想法. 不幸的是, 如果日志因为什么原因禁止了话, 字符串的插入将会非常昂贵且浪费CPU. 通过使用内联的字符串生成的lambda, 在日志被禁止的时候, logcat()会在没有性能损耗的前提下支持字符串插入.
  • TimberDebugTree通过创建栈迹的方式捕获调用类的名字作为tag, 是非常昂贵的. 通过将logcat()作为Any的扩展函数, 则能够调用this::class.java且可以在没有创建栈迹的情况下获得调用的上下文.
  • 当前的实现使用最简单的外部类名字作为tag, 比如在最后一个.和第一个$之间的字符串. 尽管这并不总是你想要的. 而且, 从抽象类中打印日志时, tag将会是运行时子类的名字. 最终发现这些限制对于我们而言目前为止还算可以.
  • 大多数时间里, 开发人员仅仅在不考虑到优先级的情况下"发送一些东西给logcat". logcat()挑选了"debug"作为恰当的默认值以提供大型项目中的持续性. 将priority作为参数也意味着只需学习一个函数, 而且在写日志之前甚至不必学习/思考优先级的事. 在有几个参数要求重载时, 这变得尤其重要(比如在Timber中, (6个优先级 + 1个泛化日志方法) * 3 个重载 = 21 个方法可供选择).
  • 不要Throwable参数也是有意而为.Throwable参数仅仅创建了更多的重载和混淆而且 (比如, "参数的顺序是什么?"), 因为真正的日志是关于字符串的, 你所需要的所有仅仅是一种简单的方式将Throwable转化为可打印的字符串. 由此产生了Throwable.asLog().
  • logcat()这个名字是故意让人厌烦的, 且与Android命令行工具完全相同. 这使得记忆更加容易, 开发者准确地知道这是做什么的, 比如, 写入本地设备. 开发者可以安装自定义logger在release构建中发送日志到远端, 但并不推荐这么做: 根据我们的经验, 远程日志在代码上应该与本地日志不同, 并且可以清楚地识别, 因为其对容量和性能的影响应该非常明显.
  • 安装logger的API不同于写日志的API, 因为这些操作出现在完全不同的上下文.

以上就是在已经重度使用Timber的情况下, 我们使用选择换到Square logcat的原因.