Commit f8484732 authored by Him188's avatar Him188

Improve loggers:

Make SimpleLogger open;
Simplify PlatformLogger;
PlatformLogger on JVM now has `isColored` param and prints exception canonically;
Unify log format;
parent b4a3d130
# Version 1.x # Version 1.x
## `1.0.0` 2020/5/21
## `1.0-RC2-1` 2020/5/11 ## `1.0-RC2-1` 2020/5/11
修复一个 `VerifyError` 修复一个 `VerifyError`
......
...@@ -239,7 +239,7 @@ internal object KnownPacketFactories { ...@@ -239,7 +239,7 @@ internal object KnownPacketFactories {
consumer: PacketConsumer<T> consumer: PacketConsumer<T>
) { ) {
if (it.packetFactory == null) { 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.warning { "找不到 PacketFactory" }
PacketLogger.verbose { PacketLogger.verbose {
"传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length ->
......
...@@ -17,22 +17,48 @@ import kotlin.coroutines.CoroutineContext ...@@ -17,22 +17,48 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
/** /**
* [Bot] 配置 * [Bot] 配置.
*
* Kotlin 使用方法:
* ```
* val bot = Bot(...) {
* // 在这里配置 Bot
*
* bogLoggerSupplier = { bot -> ... }
* fileBasedDeviceInfo()
* inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job
* }
* ```
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
open class BotConfiguration { 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 { ...@@ -99,26 +125,34 @@ open class BotConfiguration {
val Default = BotConfiguration() val Default = BotConfiguration()
} }
/** /** 不显示网络日志 */
* 不显示网络日志
*/
@ConfigurationDsl @ConfigurationDsl
fun noNetworkLog() { fun noNetworkLog() {
networkLoggerSupplier = { _ -> SilentLogger } networkLoggerSupplier = { _ -> SilentLogger }
} }
/** /**
* 使用文件存储设备信息 * 使用文件存储设备信息.
* *
* 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常. * 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常.
* @param filepath 文件路径. 可相对于程序运行路径 (`user.dir`), 也可以是绝对路径. * @param filepath 文件路径. 可相对于程序运行路径 (`user.dir`), 也可以是绝对路径.
* @see deviceInfo
*/ */
@ConfigurationDsl @ConfigurationDsl
@JvmOverloads
fun fileBasedDeviceInfo(filepath: String = "device.json") { fun fileBasedDeviceInfo(filepath: String = "device.json") {
deviceInfo = getFileBasedDeviceInfoSupplier(filepath) deviceInfo = getFileBasedDeviceInfoSupplier(filepath)
} }
/**
* 使用随机设备信息.
*
* @see deviceInfo
*/
@ConfigurationDsl
fun randomDeviceInfo() {
deviceInfo = null
}
/** /**
* 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext]. * 使用当前协程的 [coroutineContext] 作为 [parentCoroutineContext].
* *
...@@ -171,6 +205,7 @@ open class BotConfiguration { ...@@ -171,6 +205,7 @@ open class BotConfiguration {
* *
* @see parentCoroutineContext * @see parentCoroutineContext
*/ */
@JvmSynthetic
@ConfigurationDsl @ConfigurationDsl
suspend inline fun inheritCoroutineContext() { suspend inline fun inheritCoroutineContext() {
parentCoroutineContext = coroutineContext parentCoroutineContext = coroutineContext
......
...@@ -21,6 +21,7 @@ import kotlin.jvm.JvmOverloads ...@@ -21,6 +21,7 @@ import kotlin.jvm.JvmOverloads
/** /**
* 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器. * 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器.
*
* 可直接修改这个变量的值来重定向日志输出. * 可直接修改这个变量的值来重定向日志输出.
* *
* **注意:** 请务必将所有的输出定向到日志记录系统, 否则在某些情况下 (如 web 控制台中) 将无法接收到输出 * **注意:** 请务必将所有的输出定向到日志记录系统, 否则在某些情况下 (如 web 控制台中) 将无法接收到输出
...@@ -138,6 +139,9 @@ interface MiraiLogger { ...@@ -138,6 +139,9 @@ interface MiraiLogger {
fun error(e: Throwable?) = error(null, e) fun error(e: Throwable?) = error(null, e)
fun error(message: String?, e: Throwable?) fun error(message: String?, e: Throwable?)
/** 根据优先级调用对应函数 */
fun call(priority: SimpleLogger.LogPriority, message: String? = null, e: Throwable? = null) =
priority.correspondingFunction(this, message, e)
/** /**
* 添加一个 [follower], 返回 [follower] * 添加一个 [follower], 返回 [follower]
...@@ -208,6 +212,16 @@ inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) { ...@@ -208,6 +212,16 @@ inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) {
* 在 _Android_ 端的实现为 `android.util.Log` * 在 _Android_ 端的实现为 `android.util.Log`
* *
* 不应该直接构造这个类的实例. 请使用 [DefaultLogger] * 不应该直接构造这个类的实例. 请使用 [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 expect open class PlatformLogger @JvmOverloads constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
...@@ -235,20 +249,21 @@ object SilentLogger : PlatformLogger() { ...@@ -235,20 +249,21 @@ object SilentLogger : PlatformLogger() {
/** /**
* 简易日志记录, 所有类型日志都会被重定向 [logger] * 简易日志记录, 所有类型日志都会被重定向 [logger]
*/ */
class SimpleLogger( open class SimpleLogger(
override val identity: String?, final override val identity: String?,
private val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit protected open val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit
) : MiraiLoggerPlatformBase() { ) : MiraiLoggerPlatformBase() {
enum class LogPriority( enum class LogPriority(
@MiraiExperimentalAPI val nameAligned: String, @MiraiExperimentalAPI val nameAligned: String,
@MiraiExperimentalAPI val simpleName: String @MiraiExperimentalAPI val simpleName: String,
@MiraiExperimentalAPI val correspondingFunction: MiraiLogger.(message: String?, e: Throwable?) -> Unit
) { ) {
VERBOSE("VERBOSE", "VBSE"), VERBOSE("VERBOSE", "V", MiraiLogger::verbose),
DEBUG(" DEBUG ", "DEBG"), DEBUG(" DEBUG ", "D", MiraiLogger::debug),
INFO(" INFO ", "INFO"), INFO(" INFO ", "I", MiraiLogger::info),
WARNING("WARNING", "WARN"), WARNING("WARNING", "W", MiraiLogger::warning),
ERROR(" ERROR ", "EROR") ERROR(" ERROR ", "E", MiraiLogger::error)
} }
companion object { companion object {
...@@ -320,10 +335,13 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg ...@@ -320,10 +335,13 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg
/** /**
* 日志基类. 实现了 [follower] 的调用传递. * 日志基类. 实现了 [follower] 的调用传递.
* 若 Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数. * 若 Mirai 自带的日志系统无法满足需求, 请继承这个类或 [PlatformLogger] 并实现其抽象函数.
* *
* 这个类不应该被用作变量的类型定义. 只应被作为继承对象. * 这个类不应该被用作变量的类型定义. 只应被作为继承对象.
* 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch]. * 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch].
*
* @see PlatformLogger
* @see SimpleLogger
*/ */
abstract class MiraiLoggerPlatformBase : MiraiLogger { abstract class MiraiLoggerPlatformBase : MiraiLogger {
override val isEnabled: Boolean get() = true override val isEnabled: Boolean get() = true
...@@ -389,15 +407,15 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger { ...@@ -389,15 +407,15 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
error0(message, e) 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 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 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 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 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?) protected abstract fun error0(message: String?, e: Throwable?)
override operator fun <T : MiraiLogger> plus(follower: T): T { override operator fun <T : MiraiLogger> plus(follower: T): T {
......
/*
* 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
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:JvmName("Utils")
@file:JvmMultifileClass
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
...@@ -16,83 +20,107 @@ import java.util.* ...@@ -16,83 +20,107 @@ import java.util.*
/** /**
* JVM 控制台日志实现 * 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", override val identity: String? = "Mirai",
open val output: (String) -> Unit open val output: (String) -> Unit,
val isColored: Boolean = true
) : MiraiLoggerPlatformBase() { ) : MiraiLoggerPlatformBase() {
actual constructor(identity: String?) : this(identity, ::println) 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?) { override fun verbose0(message: String?, e: Throwable?) {
if (message != null) verbose(message.toString()) if (e != null) verbose(message ?: e.toString() + "\n${e.stackTraceString}")
e?.stackTraceString?.let(output) 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?) { override fun info0(message: String?, e: Throwable?) {
if (message != null) info(message.toString()) if (e != null) info(message ?: e.toString() + "\n${e.stackTraceString}")
e?.stackTraceString?.let(output) 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?) { override fun warning0(message: String?, e: Throwable?) {
if (message != null) warning(message.toString()) if (e != null) warning(message ?: e.toString() + "\n${e.stackTraceString}")
e?.stackTraceString?.let(output) 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?) { override fun error0(message: String?, e: Throwable?) {
if (message != null) error(message.toString()) if (e != null) error(message ?: e.toString() + "\n${e.stackTraceString}")
e?.stackTraceString?.let(output) 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?) { override fun debug0(message: String?, e: Throwable?) {
if (message != null) debug(message.toString()) if (e != null) debug(message ?: e.toString() + "\n${e.stackTraceString}")
e?.stackTraceString?.let(output) else debug(message.toString())
} }
private val format = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE) private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
private fun println(value: String?, color: LoggerTextFormat) { private val currentTimeFormatted = timeFormat.format(Date())
val time = format.format(Date())
if (identity == null) { /**
output("$color$time : $value") * @author NaturalHG
} else { */
output("$color$identity $time : $value") @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 { GRAY("\u001b[90m"),
printStackTrace(PrintStream(this)) LIGHT_RED("\u001b[91m"),
this.toByteArray().let(::String) 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
* @author NaturalHG internal val Throwable.stackTraceString
*/ get() = ByteArrayOutputStream().run {
@Suppress("unused") printStackTrace(PrintStream(this))
internal enum class LoggerTextFormat(private val format: String) { String(this.toByteArray())
RESET("\u001b[0m"), }
\ No newline at end of file
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment