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
## `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 {
......
/*
* 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 @@
* 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
}
\ 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
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