ReportGenerator

This commit is contained in:
Karlatemp 2020-11-25 13:56:21 +08:00
parent b0722703cf
commit 96be869e7f
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
3 changed files with 231 additions and 3 deletions

View File

@ -72,7 +72,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
MiraiConsole {
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
internal val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
override val version: SemVersion by MiraiConsoleBuildConstants::version
override val rootPath: Path by instance::rootPath

View File

@ -12,6 +12,7 @@ package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.internal.util.report.ReportGenerator
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SilentLogger
@ -71,17 +72,40 @@ internal open class MultiFilePluginDataStorageImpl(
@ConsoleExperimentalApi
public override fun store(holder: PluginDataHolder, instance: PluginData) {
var yamlRendered: String? = null
getPluginDataFile(holder, instance).writeText(
kotlin.runCatching {
yaml.encodeToString(instance.updaterSerializer, Unit).also {
yamlRendered = it
yaml.decodeAnyFromString(it) // test yaml
error("Test error")
}
}.recoverCatching {
}.recoverCatching { exception ->
// Just use mainLogger for convenience.
MiraiConsole.mainLogger.warning(
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new",
it
exception
)
val reportPath = ReportGenerator.generateReport("YamlKt-Format-") {
pw.println("Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. ")
pw.println("Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new")
pw.println()
yamlRendered?.let {
title("Rendered YAML")
pw.println(it)
pw.println()
}
title("Exception")
renderException(exception)
renderCurrentThread()
}
MiraiConsole.mainLogger.warning(
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new"
)
MiraiConsole.mainLogger.warning(
"Error Report location: $reportPath"
)
json.encodeToString(instance.updaterSerializer, Unit)
}.getOrElse {

View File

@ -0,0 +1,204 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.util.report
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.data.isDirectory
import net.mamoe.mirai.console.internal.data.isFile
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description
import java.io.*
import java.lang.management.LockInfo
import java.lang.management.ManagementFactory
import java.lang.management.MonitorInfo
import java.lang.management.ThreadInfo
import java.nio.file.Files
import java.nio.file.Path
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Suppress("unused")
internal class ReportGenerator(
val pw: PrintWriter
) : Closeable {
companion object {
internal val threadMXBean = ManagementFactory.getThreadMXBean()
internal val directory by lazy {
MiraiConsole.rootPath.resolve("error-reports")
}
fun ThreadInfo.dumpTo(sb: Appendable) {
sb.run {
append('\"')
append(threadName)
append("\" Id=")
append(threadId.toString())
append(" ")
append(threadState.toString())
lockName?.let { append(" on ").append(it) }
lockOwnerName?.let { append(" owned by \"").append(it).append("\" Id=").append(lockOwnerId.toString()) }
if (isSuspended) {
sb.append(" (suspended)")
}
if (isInNative) {
sb.append(" (in native)")
}
sb.append('\n')
var i = 0
while (i < stackTrace.size) {
val ste: StackTraceElement = stackTrace[i]
sb.append("\tat $ste")
sb.append('\n')
if (i == 0 && lockInfo != null) {
when (threadState) {
Thread.State.BLOCKED -> {
sb.append("\t- blocked on $lockInfo")
sb.append('\n')
}
Thread.State.WAITING -> {
sb.append("\t- waiting on $lockInfo")
sb.append('\n')
}
Thread.State.TIMED_WAITING -> {
sb.append("\t- waiting on $lockInfo")
sb.append('\n')
}
else -> {
}
}
}
for (mi: MonitorInfo in lockedMonitors) {
if (mi.lockedStackDepth == i) {
sb.append("\t- locked $mi")
sb.append('\n')
}
}
i++
}
val locks: Array<LockInfo> = lockedSynchronizers
if (locks.isNotEmpty()) {
sb.append("\n\tNumber of locked synchronizers = " + locks.size)
sb.append('\n')
for (li: LockInfo in locks) {
sb.append("\t- $li")
sb.append('\n')
}
}
}
}
fun generateToString(action: ReportGenerator.() -> Unit): String {
return StringWriter().apply {
ReportGenerator(PrintWriter(this)).use(action)
}.toString()
}
fun generateReport(
prefix: String = "",
action: ReportGenerator.() -> Unit
): Path {
val now = System.currentTimeMillis()
var counter = 0
var outputName = "$prefix$now.log"
directory.mkdir()
var path: Path
do {
path = directory.resolve(outputName)
if (!path.isFile && !path.isDirectory) {
break
}
outputName = "$prefix$now-$counter.log"
counter++
} while (true)
ReportGenerator(PrintWriter(BufferedWriter(OutputStreamWriter(Files.newOutputStream(path)))))
.use(action)
return path
}
}
fun renderCurrentThread() {
title("Current Thread")
renderThread(Thread.currentThread())
}
fun renderThread(thread: Thread) {
threadMXBean.getThreadInfo(
longArrayOf(thread.id),
true,
true
)[0].dumpTo(pw)
}
fun title(title: String) {
pw.append("=============== [ ").append(title).append(" ] ===============")
pw.println()
}
fun dumpSystemEnv() {
title("System Env")
pw.println("SysEnv")
pw.println()
pw.println("```")
System.getenv().forEach { (key, value) ->
pw.println("$key\t=\t$value")
}
pw.println("```")
pw.println()
pw.println("JavaProp")
pw.println()
pw.println("```")
System.getProperties().store(pw, null)
pw.println("```")
pw.println()
}
fun dumpConsoleEnv() {
title("Mirai Console Env")
val buildDateFormatted =
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
pw.append("MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ")
.append(buildDateFormatted)
.append(".\n")
pw.println("FrontEnd:")
pw.append("\t").println(MiraiConsoleImplementationBridge.instance.javaClass.name)
pw.append("\t").println(MiraiConsoleImplementationBridge.frontEndDescription.render())
pw.println()
pw.println("Plugins:")
PluginManagerImpl.resolvedPlugins.forEach { plugin ->
val desc = plugin.description
pw.append("\t").append(desc.name).append(" v").append(desc.version.toString()).append(" by ").append(desc.author).println()
pw.append("\t\t `-- ").println(plugin.javaClass.name)
}
pw.println()
pw.println("PermissionService: ")
pw.append("\t").println(PermissionService.INSTANCE)
pw.append("\t\t`- ").println(PermissionService.INSTANCE.javaClass)
}
fun renderException(throwable: Throwable) {
throwable.printStackTrace(pw)
pw.println()
}
fun hr() {
pw.println("====================================================")
}
override fun close() {
pw.close()
}
}