2
0
Эх сурвалжийг харах

Improve loggers:
Make SimpleLogger open;
Simplify PlatformLogger;
PlatformLogger on JVM now has `isColored` param and prints exception canonically;
Unify log format;

Him188 5 жил өмнө
parent
commit
f848473289

+ 3 - 0
CHANGELOG.md

@@ -1,5 +1,8 @@
 # Version 1.x
 
+## `1.0.0` 2020/5/21
+
+
 ## `1.0-RC2-1` 2020/5/11
 修复一个 `VerifyError`
 

+ 1 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt

@@ -239,7 +239,7 @@ internal object KnownPacketFactories {
         consumer: PacketConsumer<T>
     ) {
         if (it.packetFactory == null) {
-            bot.network.logger.debug("Received commandName: ${it.commandName}")
+            bot.network.logger.debug { "Received unknown commandName: ${it.commandName}" }
             PacketLogger.warning { "找不到 PacketFactory" }
             PacketLogger.verbose {
                 "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length ->

+ 46 - 11
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt

@@ -17,22 +17,48 @@ import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.coroutines.coroutineContext
 import kotlin.jvm.JvmField
-import kotlin.jvm.JvmOverloads
 import kotlin.jvm.JvmStatic
+import kotlin.jvm.JvmSynthetic
 
 /**
- * [Bot] 配置
+ * [Bot] 配置.
+ *
+ * Kotlin 使用方法:
+ * ```
+ * val bot = Bot(...) {
+ *    // 在这里配置 Bot
+ *
+ *    bogLoggerSupplier = { bot -> ... }
+ *    fileBasedDeviceInfo()
+ *    inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
+ * }
+ * ```
  */
 @Suppress("PropertyName")
 open class BotConfiguration {
-    /** 日志记录器 */
-    var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.id})") }
+    /**
+     * 日志记录器
+     *
+     * - 默认打印到标准输出, 通过 [DefaultLogger]
+     * - 忽略所有日志: `noNetworkLogger()`
+     * - 重定向到一个目录: `networkLoggerSupplier = { bot -> DirectoryLogger("Network ${it.id}") }`
+     * - 重定向到一个文件: `networkLoggerSupplier = { bot -> SingleFileLogger("Network ${it.id}") }`
+     *
+     * @see MiraiLogger
+     */
+    var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot ${it.id}") }
 
     /**
      * 网络层日志构造器
-     * @see noNetworkLog 不显示网络日志
+     *
+     * - 默认打印到标准输出, 通过 [DefaultLogger]
+     * - 忽略所有日志: `noNetworkLogger()`
+     * - 重定向到一个目录: `networkLoggerSupplier = { bot -> DirectoryLogger("Network ${it.id}") }`
+     * - 重定向到一个文件: `networkLoggerSupplier = { bot -> SingleFileLogger("Network ${it.id}") }`
+     *
+     * @see MiraiLogger
      */
-    var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Network(${it.id})") }
+    var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Network ${it.id}") }
 
     /**
      * 设备信息覆盖. 默认使用随机的设备信息.
@@ -99,26 +125,34 @@ open class BotConfiguration {
         val Default = BotConfiguration()
     }
 
-    /**
-     * 不显示网络日志
-     */
+    /** 不显示网络日志 */
     @ConfigurationDsl
     fun noNetworkLog() {
         networkLoggerSupplier = { _ -> SilentLogger }
     }
 
     /**
-     * 使用文件存储设备信息
+     * 使用文件存储设备信息.
      *
      * 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常.
      * @param filepath 文件路径. 可相对于程序运行路径 (`user.dir`), 也可以是绝对路径.
+     * @see deviceInfo
      */
     @ConfigurationDsl
-    @JvmOverloads
     fun fileBasedDeviceInfo(filepath: String = "device.json") {
         deviceInfo = getFileBasedDeviceInfoSupplier(filepath)
     }
 
+    /**
+     * 使用随机设备信息.
+     *
+     * @see deviceInfo
+     */
+    @ConfigurationDsl
+    fun randomDeviceInfo() {
+        deviceInfo = null
+    }
+
     /**
      * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
      *
@@ -171,6 +205,7 @@ open class BotConfiguration {
      *
      * @see parentCoroutineContext
      */
+    @JvmSynthetic
     @ConfigurationDsl
     suspend inline fun inheritCoroutineContext() {
         parentCoroutineContext = coroutineContext

+ 33 - 15
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt

@@ -21,6 +21,7 @@ import kotlin.jvm.JvmOverloads
 
 /**
  * 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器.
+ *
  * 可直接修改这个变量的值来重定向日志输出.
  *
  * **注意:** 请务必将所有的输出定向到日志记录系统, 否则在某些情况下 (如 web 控制台中) 将无法接收到输出
@@ -138,6 +139,9 @@ interface MiraiLogger {
     fun error(e: Throwable?) = error(null, e)
     fun error(message: String?, e: Throwable?)
 
+    /** 根据优先级调用对应函数 */
+    fun call(priority: SimpleLogger.LogPriority, message: String? = null, e: Throwable? = null) =
+        priority.correspondingFunction(this, message, e)
 
     /**
      * 添加一个 [follower], 返回 [follower]
@@ -208,6 +212,16 @@ inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) {
  * 在 _Android_ 端的实现为 `android.util.Log`
  *
  * 不应该直接构造这个类的实例. 请使用 [DefaultLogger]
+ *
+ *
+ * 单条日志格式 (正则) 为:
+ * ```regex
+ * ^([\w-]*\s[\w:]*)\s\[(\w\])\s(.*?):\s(.+)$
+ * ```
+ * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容.
+ *
+ * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`,
+ * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error
  */
 expect open class PlatformLogger @JvmOverloads constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
 
@@ -235,20 +249,21 @@ object SilentLogger : PlatformLogger() {
 /**
  * 简易日志记录, 所有类型日志都会被重定向 [logger]
  */
-class SimpleLogger(
-    override val identity: String?,
-    private val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit
+open class SimpleLogger(
+    final override val identity: String?,
+    protected open val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit
 ) : MiraiLoggerPlatformBase() {
 
     enum class LogPriority(
         @MiraiExperimentalAPI val nameAligned: String,
-        @MiraiExperimentalAPI val simpleName: String
+        @MiraiExperimentalAPI val simpleName: String,
+        @MiraiExperimentalAPI val correspondingFunction: MiraiLogger.(message: String?, e: Throwable?) -> Unit
     ) {
-        VERBOSE("VERBOSE", "VBSE"),
-        DEBUG(" DEBUG ", "DEBG"),
-        INFO("  INFO ", "INFO"),
-        WARNING("WARNING", "WARN"),
-        ERROR(" ERROR ", "EROR")
+        VERBOSE("VERBOSE", "V", MiraiLogger::verbose),
+        DEBUG(" DEBUG ", "D", MiraiLogger::debug),
+        INFO("  INFO ", "I", MiraiLogger::info),
+        WARNING("WARNING", "W", MiraiLogger::warning),
+        ERROR(" ERROR ", "E", MiraiLogger::error)
     }
 
     companion object {
@@ -320,10 +335,13 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg
 
 /**
  * 日志基类. 实现了 [follower] 的调用传递.
- * 若 Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数.
+ * 若 Mirai 自带的日志系统无法满足需求, 请继承这个类或 [PlatformLogger] 并实现其抽象函数.
  *
  * 这个类不应该被用作变量的类型定义. 只应被作为继承对象.
  * 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch].
+ *
+ * @see PlatformLogger
+ * @see SimpleLogger
  */
 abstract class MiraiLoggerPlatformBase : MiraiLogger {
     override val isEnabled: Boolean get() = true
@@ -389,15 +407,15 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
         error0(message, e)
     }
 
-    protected abstract fun verbose0(message: String?)
+    protected open fun verbose0(message: String?) = verbose0(message, null)
     protected abstract fun verbose0(message: String?, e: Throwable?)
-    protected abstract fun debug0(message: String?)
+    protected open fun debug0(message: String?) = debug0(message, null)
     protected abstract fun debug0(message: String?, e: Throwable?)
-    protected abstract fun info0(message: String?)
+    protected open fun info0(message: String?) = info0(message, null)
     protected abstract fun info0(message: String?, e: Throwable?)
-    protected abstract fun warning0(message: String?)
+    protected open fun warning0(message: String?) = warning0(message, null)
     protected abstract fun warning0(message: String?, e: Throwable?)
-    protected abstract fun error0(message: String?)
+    protected open fun error0(message: String?) = error0(message, null)
     protected abstract fun error0(message: String?, e: Throwable?)
 
     override operator fun <T : MiraiLogger> plus(follower: T): T {

+ 78 - 0
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileLogger.kt

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.utils
+
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+private val currentDay = Calendar.getInstance()[Calendar.DAY_OF_MONTH]
+private val currentDate = SimpleDateFormat("yyyy-MM-dd").format(Date())
+
+/**
+ * 将日志写入('append')到特定文件.
+ *
+ * @see PlatformLogger 查看格式信息
+ */
+class SingleFileLogger @JvmOverloads constructor(identity: String, file: File = File("$identity-$currentDate.log")) :
+    PlatformLogger(identity, { file.appendText(it) }, false) {
+
+    init {
+        file.createNewFile()
+        require(file.isFile) { "Log file must be a file: $file" }
+        require(file.canWrite()) { "Log file must be write: $file" }
+    }
+}
+
+private val STUB: (priority: SimpleLogger.LogPriority, message: String?, e: Throwable?) -> Unit =
+    { _: SimpleLogger.LogPriority, _: String?, _: Throwable? -> error("stub") }
+
+/**
+ * 将日志写入('append')到特定文件夹中的文件. 每日日志独立保存.
+ *
+ * @see PlatformLogger 查看格式信息
+ */
+class DirectoryLogger @JvmOverloads constructor(
+    identity: String,
+    private val directory: File = File(identity),
+    /**
+     * 保留日志文件多长时间. 毫秒数
+     */
+    private val retain: Long = 1.weeksToMillis
+) : SimpleLogger("", STUB) {
+    init {
+        directory.mkdirs()
+    }
+
+    private fun checkOutdated() {
+        val current = currentTimeMillis
+        directory.walk().filter(File::isFile).filter { current - it.lastModified() > retain }.forEach {
+            it.delete()
+        }
+    }
+
+    private var day = currentDay
+
+    private var delegate: SingleFileLogger = SingleFileLogger(identity, File(directory, "$currentDate.log"))
+        get() {
+            val currentDay = currentDay
+            if (day != currentDay) {
+                day = currentDay
+                checkOutdated()
+                field = SingleFileLogger(identity!!, File(directory, "$currentDate.log"))
+            }
+            return field
+        }
+
+    override val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit =
+        { priority: LogPriority, message: String?, e: Throwable? ->
+            delegate.call(priority, message, e)
+        }
+}

+ 84 - 56
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt

@@ -7,6 +7,10 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+@file:JvmName("Utils")
+@file:JvmMultifileClass
+@file:Suppress("MemberVisibilityCanBePrivate")
+
 package net.mamoe.mirai.utils
 
 import java.io.ByteArrayOutputStream
@@ -16,83 +20,107 @@ import java.util.*
 
 /**
  * JVM 控制台日志实现
+ *
+ * 不应该直接构造这个类的实例. 请使用 [DefaultLogger]
+ *
+ *
+ * 单条日志格式 (正则) 为:
+ * ```regex
+ * ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$
+ * ```
+ * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容.
+ *
+ * 示例:
+ * ```log
+ * 2020-05-21 19:51:09 V/Bot 1994701021: Send: OidbSvc.0x88d_7
+ * ```
+ *
+ * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`,
+ *
+ * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error
+ *
+ * @param isColored 是否添加 ANSI 颜色
+ *
+ * @see SingleFileLogger 使用单一文件记录日志
+ * @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志
  */
-actual open class PlatformLogger constructor(
+actual open class PlatformLogger @JvmOverloads constructor(
     override val identity: String? = "Mirai",
-    open val output: (String) -> Unit
+    open val output: (String) -> Unit,
+    val isColored: Boolean = true
 ) : MiraiLoggerPlatformBase() {
     actual constructor(identity: String?) : this(identity, ::println)
 
-    override fun verbose0(message: String?) = println(message, LoggerTextFormat.RESET)
+    private fun out(message: String?, priority: String, color: Color) {
+        if (isColored) output("$color$currentTimeFormatted $priority/$identity: $message")
+        else output("$currentTimeFormatted $priority/$identity: $message")
+    }
+
+    override fun verbose0(message: String?) = out(message, "V", Color.RESET)
+
     override fun verbose0(message: String?, e: Throwable?) {
-        if (message != null) verbose(message.toString())
-        e?.stackTraceString?.let(output)
+        if (e != null) verbose(message ?: e.toString() + "\n${e.stackTraceString}")
+        else verbose(message.toString())
     }
 
-    override fun info0(message: String?) = println(message, LoggerTextFormat.LIGHT_GREEN)
+    override fun info0(message: String?) = out(message, "I", Color.LIGHT_GREEN)
     override fun info0(message: String?, e: Throwable?) {
-        if (message != null) info(message.toString())
-        e?.stackTraceString?.let(output)
+        if (e != null) info(message ?: e.toString() + "\n${e.stackTraceString}")
+        else info(message.toString())
     }
 
-    override fun warning0(message: String?) = println(message, LoggerTextFormat.LIGHT_RED)
+    override fun warning0(message: String?) = out(message, "W", Color.LIGHT_RED)
     override fun warning0(message: String?, e: Throwable?) {
-        if (message != null) warning(message.toString())
-        e?.stackTraceString?.let(output)
+        if (e != null) warning(message ?: e.toString() + "\n${e.stackTraceString}")
+        else warning(message.toString())
     }
 
-    override fun error0(message: String?) = println(message, LoggerTextFormat.RED)
+    override fun error0(message: String?) = out(message, "E", Color.RED)
     override fun error0(message: String?, e: Throwable?) {
-        if (message != null) error(message.toString())
-        e?.stackTraceString?.let(output)
+        if (e != null) error(message ?: e.toString() + "\n${e.stackTraceString}")
+        else error(message.toString())
     }
 
-    override fun debug0(message: String?) = println(message, LoggerTextFormat.LIGHT_CYAN)
+    override fun debug0(message: String?) = out(message, "D", Color.LIGHT_CYAN)
     override fun debug0(message: String?, e: Throwable?) {
-        if (message != null) debug(message.toString())
-        e?.stackTraceString?.let(output)
+        if (e != null) debug(message ?: e.toString() + "\n${e.stackTraceString}")
+        else debug(message.toString())
     }
 
-    private val format = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
-    private fun println(value: String?, color: LoggerTextFormat) {
-        val time = format.format(Date())
+    private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
+    private val currentTimeFormatted = timeFormat.format(Date())
 
-        if (identity == null) {
-            output("$color$time : $value")
-        } else {
-            output("$color$identity $time : $value")
-        }
-    }
-}
+    /**
+     * @author NaturalHG
+     */
+    @Suppress("unused")
+    private enum class Color(private val format: String) {
+        RESET("\u001b[0m"),
+
+        WHITE("\u001b[30m"),
+        RED("\u001b[31m"),
+        EMERALD_GREEN("\u001b[32m"),
+        GOLD("\u001b[33m"),
+        BLUE("\u001b[34m"),
+        PURPLE("\u001b[35m"),
+        GREEN("\u001b[36m"),
 
-internal val Throwable.stackTraceString get() = ByteArrayOutputStream().run {
-    printStackTrace(PrintStream(this))
-    this.toByteArray().let(::String)
+        GRAY("\u001b[90m"),
+        LIGHT_RED("\u001b[91m"),
+        LIGHT_GREEN("\u001b[92m"),
+        LIGHT_YELLOW("\u001b[93m"),
+        LIGHT_BLUE("\u001b[94m"),
+        LIGHT_PURPLE("\u001b[95m"),
+        LIGHT_CYAN("\u001b[96m")
+        ;
+
+        override fun toString(): String = format
+    }
 }
 
-/**
- * @author NaturalHG
- */
-@Suppress("unused")
-internal enum class LoggerTextFormat(private val format: String) {
-    RESET("\u001b[0m"),
-
-    WHITE("\u001b[30m"),
-    RED("\u001b[31m"),
-    EMERALD_GREEN("\u001b[32m"),
-    GOLD("\u001b[33m"),
-    BLUE("\u001b[34m"),
-    PURPLE("\u001b[35m"),
-    GREEN("\u001b[36m"),
-
-    GRAY("\u001b[90m"),
-    LIGHT_RED("\u001b[91m"),
-    LIGHT_GREEN("\u001b[92m"),
-    LIGHT_YELLOW("\u001b[93m"),
-    LIGHT_BLUE("\u001b[94m"),
-    LIGHT_PURPLE("\u001b[95m"),
-    LIGHT_CYAN("\u001b[96m")
-    ;
-
-    override fun toString(): String = format
-}
+@get:JvmSynthetic
+internal val Throwable.stackTraceString
+    get() = ByteArrayOutputStream().run {
+        printStackTrace(PrintStream(this))
+        String(this.toByteArray())
+    }