前言

在Android中记载日志一般是运用Android自带的Log工具,

可是实际项目中,咱们常常需要共同操控项目的日志开关过滤等级,有时还需要将日志写入到文件中。

目前除了自己完成外,根本都是经过第三方的Log日志库比方JakeWharton/timber,orhanobut/logger ,可是有些状况仍然完成起来困难。

比方:

  • 多模块或封装SDK的状况,子模块也用到日志结构,如何独自办理子模块的日志开关?
  • Java Library中没有Android Log类引进日志模块报错,同理运用JUnit做单元测试的时候日志模块报错。

而且替换项目中的Log是苦楚的,因而就需要一个可拓宽的日志结构尤为重要。

和大多数流行的Logger结构不同,本文完成的Logger不是对各种数据进行美化输出,本文充分利用Kotlin的拓宽函数完成了一个灵敏装备的轻量Logger,它具有以下特性:

  • 操控模块中的Logger装备
  • 输出到文件等多个方针,操控输出线程
  • 支撑阻拦器,完成过滤,格局化
  • 支撑Jvm运用,支撑JUnit

项目地址 Logger

运用

核心只要3个类

  1. Logger 日志的操作入口:首要确保安稳简单易用,用作门面。

  2. LogPrinter 日志输出方针:装备日志输出。

  3. LogLevel 日志输出等级:(VERBOSE,DEBUG,INFO,WARNING,ERROR,WTF)

Kotlin特性实现高拓展性Logger

日志记载

经过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 默认状况,子LoggerlevellogPrinter均承继自父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的工厂,默认状况,子LoggerlevellogPrinter均承继自父Logger,tag"$父tag-$subTag"

如下示例装备了顶级Loggerleveltag,输出方针为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中输出的日志进行过滤,能够根据tagmessagelevelthrowable进行组合判断来过滤。

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程序中运用。