mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-03 01:39:35 +08:00
Improve loggers:
Make SimpleLogger open; Simplify PlatformLogger; PlatformLogger on JVM now has `isColored` param and prints exception canonically; Unify log format;
This commit is contained in:
parent
b4a3d13011
commit
f848473289
@ -1,5 +1,8 @@
|
||||
# Version 1.x
|
||||
|
||||
## `1.0.0` 2020/5/21
|
||||
|
||||
|
||||
## `1.0-RC2-1` 2020/5/11
|
||||
修复一个 `VerifyError`
|
||||
|
||||
|
@ -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 ->
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
@get:JvmSynthetic
|
||||
internal val Throwable.stackTraceString
|
||||
get() = ByteArrayOutputStream().run {
|
||||
printStackTrace(PrintStream(this))
|
||||
String(this.toByteArray())
|
||||
}
|
Loading…
Reference in New Issue
Block a user