Logger庫說明文檔
在日常開發(fā)中,日志收集是個(gè)非常重要的功能,很多時(shí)候,我們都是簡(jiǎn)單的使用Log.d直接打印我們想要的內(nèi)容,這樣開發(fā)當(dāng)然沒問題。包括說簡(jiǎn)單封裝下,統(tǒng)一到一個(gè)接口方法打印,也是很常見的手法。我只是在這個(gè)基礎(chǔ)上面,抽取個(gè)簡(jiǎn)單實(shí)現(xiàn)的庫而已,沒啥特別的。
用法
初始化
private fun initLogger() {
//設(shè)置上下文
FastLogger.initLogger(this)
//控制是否打印
FastLogger.logEnable = true
//控制是否保存到日志文件
FastLogger.logSave2File = true
//自定義打印
FastLogger.setInterceptor(object : AbsLogInterceptChain() {
override fun intercept(
priority: Int,
tag: String,
logMsg: String?,
throwable: Throwable?
) {
//log text
}
})
}
打印
FastLogger.i(TAG,"this is info level log")
FastLogger.d(TAG,"this is debug level log")
FastLogger.w(TAG,"this is warn level log")
FastLogger.e(TAG,"this is error level log",NullPointerException("this is NullPointerException"))
實(shí)現(xiàn)思路
整體實(shí)現(xiàn)思路用到了設(shè)計(jì)模式中的:責(zé)任鏈模式。(假裝牛逼)
語言使用的是 Kotlin。
第一步:定義對(duì)外接口
首先我們定義我們對(duì)外的對(duì)象類 FastLogger,并且定義出我們對(duì)外的方法
object FastLogger {
fun d(tag: String, message: String) {
}
fun e(tag: String, message: String, throwable: Throwable? = null) {
}
fun w(tag: String, message: String) {
}
fun i(tag: String, message: String) {
}
}
這是四個(gè)我們常見的打印方法,我們的所有邏輯都是圍繞這四個(gè)方法進(jìn)行開發(fā)的。
第二步:實(shí)現(xiàn)打印方法
在實(shí)現(xiàn)這個(gè)方法之前,我們跟一下官方打印的調(diào)用流程:
android.util.Log
public static int i(String tag, String msg) {
return println(LOG_ID_MAIN, INFO, tag, msg);
}
public static int d(String tag, String msg) {
return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
public static int w(String tag, String msg) {
return println(LOG_ID_MAIN, WARN, tag, msg);
}
public static int e(String tag, String msg, Throwable tr) {
return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
可以看到,最終都是調(diào)用的println方法
public static int println(int bufID,int priority, String tag, String msg)
那么最終,其實(shí)就是需要以下幾個(gè)參數(shù):
- priority:日志級(jí)別
- tag:TAG
- msg:日志內(nèi)容
所以,我基于此,定義我們的打印方法,并且通過一個(gè)變量控制日志是否可以打印。
object FastLogger {
var logEnable = BuildConfig.DEBUG
fun d(tag: String, message: String) {
log(Log.DEBUG, message, tag)
}
fun e(tag: String, message: String, throwable: Throwable? = null) {
log(Log.ERROR, message, tag, throwable = throwable)
}
fun w(tag: String, message: String) {
log(Log.WARN, message, tag)
}
fun i(tag: String, message: String) {
log(Log.INFO, message, tag)
}
@Synchronized
private fun log(
priority: Int,
message: String,
tag: String,
throwable: Throwable? = null
) {
if (logEnable) {
Log.println(priority, message, tag + '\n' + Log.getStackTraceString(throwable));
}
}
}
在這一步,其實(shí),我們一個(gè)打印庫就封裝好了。
只是在這個(gè)基礎(chǔ)上面,我們實(shí)現(xiàn)的和官方提供的沒啥區(qū)別,就是加了個(gè)開關(guān)控制而已。我們應(yīng)該對(duì)其美化一下。
第三步:使用責(zé)任鏈裝逼
首先定義一個(gè)抽象類
abstract class AbsLogInterceptChain {
var next: AbsLogInterceptChain? = null
open fun intercept(priority: Int, tag: String, logMsg: String?,throwable: Throwable? = null) {
next?.intercept(priority, tag, logMsg,throwable)
}
}
然后實(shí)現(xiàn)我們的默認(rèn)打印類
internal class DefaultLoggerInterceptor : AbsLogInterceptChain() {
override fun intercept(priority: Int, tag: String, logMsg: String?, throwable: Throwable?) {
next?.intercept(priority, tag, logMsg, throwable)
}
}
在這基礎(chǔ)上面,對(duì)我們的日志內(nèi)容進(jìn)行美化下,于是添加裝飾類
internal class LoggerDecorateInterceptor : AbsLogInterceptChain() {
override fun intercept(priority: Int, tag: String, logMsg: String?, throwable: Throwable?) {
val decorateMsg = "ThreadName: ${Thread.currentThread().name} ---> $logMsg"
super.intercept(priority, "Umeox_$tag", decorateMsg, throwable)
}
}
最后實(shí)現(xiàn)真正的打印類
internal class LoggerPrintInterceptor: AbsLogInterceptChain() {
override fun intercept(priority: Int, tag: String, logMsg: String?,throwable: Throwable?) {
when(priority) {
Log.INFO -> {
Log.i(tag, logMsg?:"-")
}
Log.WARN -> {
Log.w(tag, logMsg?:"-")
}
Log.DEBUG -> {
Log.d(tag, logMsg?:"-")
}
Log.ERROR -> {
Log.e(tag, logMsg?:"-",throwable)
}
Log.VERBOSE -> {
Log.w(tag, logMsg?:"-",throwable)
}
}
super.intercept(priority, tag, logMsg, throwable)
}
}
在開發(fā)過程中,還有保存日志到本地的需求,如果又添加了保存到本地的鏈接類
internal class Logger2FileInterceptor(private val context: Context): AbsLogInterceptChain() {
private var file: File? = null
private val handlerThread = HandlerThread("logger_to_file_thread")
init {
handlerThread.start()
}
private fun init() {
Handler(handlerThread.looper).post {
val format = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).format(Date())
context.externalCacheDir?.let {
file = File(it,"$format.txt")
if (!file!!.exists()) {
try {
//在指定的文件夾中創(chuàng)建文件
file!!.createNewFile()
} catch (e: Exception) {
return@post
}
}
//刪除無用文件 只存放三天的數(shù)據(jù)
it.listFiles()?.let { files ->
for (file in files) {
try {
val old = SimpleDateFormat(
"yyyy-MM-dd",
Locale.CHINA
).parse(file.name.replace(".txt", "")) ?: return@let
val timeInMillis = Calendar.getInstance().apply {
time = old
}.timeInMillis
val dDay = (System.currentTimeMillis() - timeInMillis) / 1000 / 60 /60 /24
if (dDay > 3) {
file.delete()
}
} catch (e:Exception) {
}
}
}
}
}
}
override fun intercept(priority: Int, tag: String, logMsg: String?,throwable: Throwable?) {
if (!FastLogger.logSave2File && priority != Log.VERBOSE) {
return
}
Handler(handlerThread.looper).post {
if (file == null) {
init()
intercept(priority, tag, logMsg, throwable)
return@post
}
val date = Date()
val needWriteMessage: String = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(date)
.toString() + " " + tag + " " + logMsg + "\n"
try {
file!!.appendText(needWriteMessage)
} catch (e: Exception) {
return@post
}
}
}
}
通過責(zé)任鏈的關(guān)系,我們FastLogger最終的實(shí)現(xiàn)就是
object FastLogger {
private var initialized = false
private var intercepts:AbsLogInterceptChain = DefaultLoggerInterceptor()
var logEnable = BuildConfig.DEBUG
var logSave2File = false
fun initLogger(context: Context) {
if (initialized) {
initialized = true
return
}
val fileInterceptor = Logger2FileInterceptor(context)
val printInterceptor = LoggerPrintInterceptor()
printInterceptor.next = fileInterceptor
val decorateInterceptor = LoggerDecorateInterceptor()
decorateInterceptor.next = printInterceptor
intercepts.next = decorateInterceptor
}
fun d(tag: String, message: String) {
log(Log.DEBUG, message, tag)
}
fun e(tag: String, message: String, throwable: Throwable? = null) {
log(Log.ERROR, message, tag, throwable = throwable)
}
fun w(tag: String, message: String) {
log(Log.WARN, message, tag)
}
fun i(tag: String, message: String) {
log(Log.INFO, message, tag)
}
fun setInterceptor(interceptor: AbsLogInterceptChain) {
this.intercepts = interceptor
}
@Synchronized
private fun log(
priority: Int,
message: String,
tag: String,
throwable: Throwable? = null
) {
if (logEnable) {
intercepts.intercept(priority, tag, message, throwable)
}
}
}
其實(shí),在這個(gè)基礎(chǔ)上面,我們可以抽取出文本保存地址呀,定期清除的時(shí)長(zhǎng)呀等等參數(shù),這個(gè)你們可以下去繼續(xù)封裝。