前言
在Android中记载日志一般是运用Android自带的Log工具,
可是实际项目中,咱们常常需要共同操控项目的日志开关或过滤等级,有时还需要将日志写入到文件中。
目前除了自己完成外,根本都是经过第三方的Log日志库比方JakeWharton/timber,orhanobut/logger ,可是有些状况仍然完成起来困难。
比方:
- 多模块或封装SDK的状况,子模块也用到日志结构,如何独自办理子模块的日志开关?
- Java Library中没有Android Log类引进日志模块报错,同理运用JUnit做单元测试的时候日志模块报错。
而且替换项目中的Log是苦楚的,因而就需要一个可拓宽的日志结构尤为重要。
和大多数流行的Logger结构不同,本文完成的Logger不是对各种数据进行美化输出,本文充分利用Kotlin的拓宽函数完成了一个灵敏装备的轻量Logger,它具有以下特性:
- 操控模块中的Logger装备
- 输出到文件等多个方针,操控输出线程
- 支撑阻拦器,完成过滤,格局化
- 支撑Jvm运用,支撑JUnit
项目地址 Logger
运用
核心只要3个类
-
Logger
日志的操作入口:首要确保安稳简单易用,用作门面。 -
LogPrinter
日志输出方针:装备日志输出。 -
LogLevel
日志输出等级:(VERBOSE,DEBUG,INFO,WARNING,ERROR,WTF)
日志记载
经过Logger
能够获取大局实例。
输出日志有许多等级,和android的Log等级是共同的:
Logger.v("Test verbose")
Logger.d("Test debug")
Logger.i("Test info")
Logger.w("Test warning")
Logger.e("Test error")
Logger.wtf("Test wtf")
Logger.v { "Test verbose" }
Logger.d { "Test debug" }
Logger.i { "Test info" }
Logger.w { "Test warning" }
Logger.e { "Test error" }
Logger.wtf { "Test wtf" }
经过Logger["subTag"]
能够生成子Logger
默认状况,子Logger
的level
、logPrinter
均承继自父Logger
,tag
为"$父tag-$subTag"
Logger.tag = "App" //大局Logger的tag设置为“APP”
class XxActivity {
val logger = Logger["XxActivity"] //二级Logger的tag为“APP-XxActivity”
val logger = loggerForClass() //运用当时类名生成二级Logger的tag为“APP-XxActivity”
inner class XxFragment {
val fragmentLogger = logger["XxFragment"]//三级Logger的tag为“APP-XxActivity-XxFragment”
}
}
日志装备
Logger
一共只要4个特点:
-
level
过滤等级(VERBOSE,DEBUG,INFO,WARNING,ERROR,WTF) -
tag
日志TAG -
logPrinter
日志输出方针,比方Android Logcat,文件,标示输出流,Socket等。 -
loggerFactory
生产子Logger
的工厂,默认状况,子Logger
的level
、logPrinter
均承继自父Logger
,tag
为"$父tag-$subTag"
如下示例装备了顶级Logger
的level
和tag
,输出方针为AndroidLogcat
,一起在子线程把WARNING
等级的日志按一定格局输出到文件"warning.log"
中,
一起"SubModule"
子Logger
的输出等级设置为ERROR
等级。
Logger.level = LogLevel.VERBOSE
Logger.tag = "AppName"
Logger.logPrinter = AndroidLogPrinter()
.logAlso(FileLogPrinter({ File("warning.log") })
.format { _, _, messageAny, _ -> "【${Thread.currentThread().name}】:$messageAny" }
.logAt(Executors.newSingleThreadExecutor())
.filterLevel(LogLevel.WARNING)
)
Logger.loggerFactory = { subTag ->
Logger("$tag-$it", level, logPrinter).also { child ->
if (child.tag == "SubModule") {
logger.level = LogLevel.ERROR
}
}
}
完成
Logger功用完成:
由于Logger
是门面,所以供给便捷的办法来运用,而真实的写入日志代理给LogPrinter
open class Logger(
var tag: String = "LOG",
var level: LogLevel = LogLevel.VERBOSE,
var logPrinter: LogPrinter = createPlatformDefaultLogPrinter(),
) {
fun v(message: Any?) = log(LogLevel.VERBOSE, message, null)
fun log(level: LogLevel, message: Any?, throwable: Throwable? = null) {
if (this.level <= level) {
logPrinter.log(level, tag, message, throwable)
}
}
//省略其他等级...
var loggerFactory: (childTag: String) -> Logger = ::defaultLoggerFactory
/**
* 创建子Logger
* @param subTag 次级tag,一般为模块名
*/
operator fun get(subTag: String): Logger = loggerFactory(subTag)
companion object INSTANCE : Logger(tag = "Logger")
}
enum class LogLevel(val shortName: String) {
VERBOSE("V"),
DEBUG("D"),
INFO("I"),
WARNING("W"),
ERROR("E"),
WTF("WTF")
}
fun interface LogPrinter {
fun log(level: LogLevel, tag: String, messageAny: Any?, throwable: Throwable?)
}
LogPrinter拓宽完成
首要完成对LogPrinter
进行阻拦,后续的功用都经过阻拦器完成。
阻拦器
/**
* 阻拦器
* logPrinter 被阻拦对象
*/
typealias LogPrinterInterceptor = (logPrinter: Logger.LogPrinter, level: Logger.LogLevel, tag: String, messageAny: Any?, throwable: Throwable?) -> Unit
inline fun Logger.LogPrinter.intercept(crossinline interceptor: LogPrinterInterceptor) =
Logger.LogPrinter { level, tag, messageAny, throwable ->
interceptor(this@intercept, level, tag, messageAny, throwable)
}
增加额定的LogPrinter
增加一个额定的LogPrinter
,也可看作将2个LogPrinter
合并成1个。想要增加多个输出方针时运用。
fun Logger.LogPrinter.logAlso(other: Logger.LogPrinter) =
intercept { logPrinter, level, tag, messageAny, throwable ->
logPrinter.log(level, tag, messageAny, throwable)
other.log(level, tag, messageAny, throwable)
}
设置日志记载线程
操控LogPrinter
的输出线程
fun Logger.LogPrinter.logAt(executor: Executor) =
intercept { logPrinter, level, tag, messageAny, throwable ->
executor.execute {
logPrinter.log(level, tag, messageAny, throwable)
}
}
格局化
操控LogPrinter
的输出的格局,比方csv格局,Json格局等。
typealias LogFormatter = (level: Logger.LogLevel, tag: String, messageAny: Any?, throwable: Throwable?) -> String
fun Logger.LogPrinter.format(formatter: LogFormatter) =
intercept { logPrinter, level, tag, messageAny, throwable ->
val formattedMessage = formatter(level, tag, messageAny, throwable)
logPrinter.log(level, tag, formattedMessage, throwable)
}
日志过滤
对LogPrinter
中输出的日志进行过滤,能够根据tag
、message
、level
、throwable
进行组合判断来过滤。
fun Logger.LogPrinter.filter(
predicate: (
level: Logger.LogLevel,
tag: String,
messageAny: Any?,
throwable: Throwable?
) -> Boolean
) =
intercept { logPrinter, level, tag, messageAny, throwable ->
if (predicate(level, tag, messageAny, throwable)) {
logPrinter.log(level, tag, messageAny, throwable)
}
}
Logger复制
以Logger
为原型复制一个新Logger
,和生成子Logger
不同,它并不是经过loggerFactory
生成的,而且tag
也是复制的。
/**
* 复制
*/
fun Logger.copy(
tag: String = this.tag,
level: Logger.LogLevel = this.level,
logPrinter: Logger.LogPrinter = this.logPrinter,
loggerFactory: (childTag: String) -> Logger = ::defaultLoggerFactory,
) = Logger(tag, level, logPrinter).also { it.loggerFactory = loggerFactory }
Json格局化
由于并没有引进任何Android类和Json序列化库,所以没有内置。在此供给Gson示例
方式1,运用LogPrinter拓宽
适用于该Logger
所有日志都需要转Json的状况
val gson = GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create()
fun Logger.jsonLogger() =
copy(logPrinter = logPrinter.format { _, _, messageAny, _ -> gson.toJson(messageAny) })
//运用
fun testLogJsonLogger() {
val logger = Logger.jsonLogger()
logger.d {
arrayOf("hello", "world")
}
logger.i {
mapOf(
"name" to "tom",
"age" to 19,
)
}
}
方式2,拓宽Logger办法
经过拓宽Logger
办法完成,适用于Logger
的部分数据需要输出为Json模式。
inline fun Logger.logJson(level: Logger.LogLevel = Logger.LogLevel.INFO, any: () -> Any) {
log(level, block = { gson.toJson(any()) })
}
//运用
fun testLogJsonExt() {
Logger.logJson {
mapOf(
"name" to "tom",
"age" to 19,
)
}
}
拓宽运用
这些拓宽办法能够连续调用,就像运用RxJava相同。
Logger.logPrinter = ConsoleLogPrinter()
.format { _, tag, messageAny, _ -> "$tag : $messageAny\n" }
.logAlso(ConsoleLogPrinter()
.format { _, tag, messageAny, _ ->//增加分割线 tag,时刻,message转json,一起加上仓库信息
"""---------------------------\n $tag ${currentTime()} ${Json.toJson(messageAny)} \n${Thread.currentThread().stackTrace.contentToString()}"""
}
.filterLevel(LogLevel.INFO))//仅记载level在INFO及以上的
.logAlso(ConsoleLogPrinter()
.format { _, tag, messageAny, _ -> "$tag :《$messageAny》\n" }
.filter { _, tag, _, _ -> tag.contains("CHILD") })//仅记载tag包括CHILD
混杂
假如经过混杂去除日志信息,可按如下装备。
-assumenosideeffects class me.lwb.logger.Logger {
public *** d(...);
public *** e(...);
public *** i(...);
public *** v(...);
public *** log(...);
public *** w(...);
public *** wtf(...);
}
总结
本文首要运用了Kotlin拓宽和高阶函数完成了一个拓宽性高的Logger库,经过拓宽办法完成线程切换,多输出,格局化等,一起经过装备大局logFactory
的办法能够在不修改子模块代码的状况下去操控子模块Logger的level等信息。
该库十分精简,加上拓宽和默认完成总代码小于300行,不依赖Android库第三方库,能够在纯Jvm程序中运用,也可在Android程序中运用。