diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c0d57da..344f27e3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Version 1.x +## `1.0.0` 2020/5/21 + + ## `1.0-RC2-1` 2020/5/11 修复一个 `VerifyError` diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 2c1e28cdb..925714242 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -239,7 +239,7 @@ internal object KnownPacketFactories { consumer: PacketConsumer ) { 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 -> diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 1e7856816..1279f4941 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/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 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt index e5495870e..ee9e9b8bc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt +++ b/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 plus(follower: T): T { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileLogger.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileLogger.kt new file mode 100644 index 000000000..be8a620b3 --- /dev/null +++ b/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) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt index 7eb483c3d..0379e8b7f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt +++ b/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"), + + 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 } } -internal val Throwable.stackTraceString get() = ByteArrayOutputStream().run { - printStackTrace(PrintStream(this)) - this.toByteArray().let(::String) -} - -/** - * @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 -} \ No newline at end of file +@get:JvmSynthetic +internal val Throwable.stackTraceString + get() = ByteArrayOutputStream().run { + printStackTrace(PrintStream(this)) + String(this.toByteArray()) + } \ No newline at end of file