Quellcode durchsuchen

Improve logging: use Log4j internally and support markers
Migrate usages of `MiraiLogger.create` to new API
apiDump for new logging

Him188 vor 4 Jahren
Ursprung
Commit
5950e9e1e8
33 geänderte Dateien mit 620 neuen und 169 gelöschten Zeilen
  1. 30 3
      binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
  2. 25 3
      binary-compatibility-validator/api/binary-compatibility-validator.api
  3. 5 2
      buildSrc/src/main/kotlin/Versions.kt
  4. 8 2
      mirai-core-api/build.gradle.kts
  5. 1 1
      mirai-core-api/src/commonMain/kotlin/event/Event.kt
  6. 1 1
      mirai-core-api/src/commonMain/kotlin/internal/utils/ExternalResourceLeakObserver.kt
  7. 67 23
      mirai-core-api/src/commonMain/kotlin/internal/utils/LoggerAdapterImpls.kt
  8. 70 0
      mirai-core-api/src/commonMain/kotlin/internal/utils/MarkedMiraiLogger.kt
  9. 6 2
      mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt
  10. 1 0
      mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt
  11. 52 6
      mirai-core-api/src/commonMain/kotlin/utils/LoggerAdapters.kt
  12. 176 33
      mirai-core-api/src/commonMain/kotlin/utils/MiraiLogger.kt
  13. 88 0
      mirai-core-api/src/commonTest/kotlin/logging/Log4j2LoggingTest.kt
  14. 13 0
      mirai-core-api/src/commonTest/resources/log4j.properties
  15. 10 5
      mirai-core-utils/src/androidMain/kotlin/Actuals.kt
  16. 6 1
      mirai-core-utils/src/commonMain/kotlin/Services.kt
  17. 10 5
      mirai-core-utils/src/jvmMain/kotlin/Actuals.kt
  18. 1 1
      mirai-core/build.gradle.kts
  19. 1 1
      mirai-core/src/androidTest/kotlin/test/initPlatform.android.kt
  20. 5 2
      mirai-core/src/commonMain/kotlin/message/imagesImpl.kt
  21. 1 1
      mirai-core/src/commonMain/kotlin/network/components/PacketCodec.kt
  22. 2 2
      mirai-core/src/commonMain/kotlin/network/components/ServerList.kt
  23. 2 1
      mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt
  24. 4 4
      mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt
  25. 4 52
      mirai-core/src/commonMain/kotlin/utils/SubLogger.kt
  26. 2 1
      mirai-core/src/commonMain/kotlin/utils/contentToString.kt
  27. 4 1
      mirai-core/src/commonTest/kotlin/network/component/EventDispatcherTest.kt
  28. 4 3
      mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt
  29. 1 1
      mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt
  30. 3 3
      mirai-core/src/commonTest/kotlin/network/framework/TestNetworkHandlerContext.kt
  31. 1 1
      mirai-core/src/commonTest/kotlin/network/handler/KeepAliveNetworkHandlerSelectorTest.kt
  32. 10 3
      mirai-core/src/commonTest/kotlin/test/initPlatform.common.kt
  33. 6 5
      mirai-core/src/commonTest/kotlin/test/printing.kt

+ 30 - 3
binary-compatibility-validator/android/api/binary-compatibility-validator-android.api

@@ -5651,15 +5651,20 @@ public abstract interface class net/mamoe/mirai/utils/MiraiLogger {
 	public abstract fun error (Ljava/lang/String;)V
 	public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun error (Ljava/lang/Throwable;)V
-	public abstract fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger;
 	public abstract fun getIdentity ()Ljava/lang/String;
 	public abstract fun info (Ljava/lang/String;)V
 	public abstract fun info (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun info (Ljava/lang/Throwable;)V
+	public fun isDebugEnabled ()Z
 	public abstract fun isEnabled ()Z
-	public abstract fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun isErrorEnabled ()Z
+	public fun isInfoEnabled ()Z
+	public fun isVerboseEnabled ()Z
+	public fun isWarningEnabled ()Z
+	public fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger;
 	public static fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V
-	public abstract fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V
+	public fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V
 	public abstract fun verbose (Ljava/lang/String;)V
 	public abstract fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun verbose (Ljava/lang/Throwable;)V
@@ -5674,6 +5679,23 @@ public final class net/mamoe/mirai/utils/MiraiLogger$Companion {
 	public final fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V
 }
 
+public abstract interface class net/mamoe/mirai/utils/MiraiLogger$Factory {
+	public static final field INSTANCE Lnet/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE;
+	public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public abstract fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Ljava/lang/Class;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Lkotlin/reflect/KClass;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger;
+}
+
+public final class net/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE : net/mamoe/mirai/utils/MiraiLogger$Factory {
+	public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+}
+
 public abstract class net/mamoe/mirai/utils/MiraiLoggerPlatformBase : net/mamoe/mirai/utils/MiraiLogger {
 	public fun <init> ()V
 	public final fun debug (Ljava/lang/String;)V
@@ -5939,7 +5961,12 @@ public final class net/mamoe/mirai/utils/SingleFileLogger : net/mamoe/mirai/util
 	public fun info (Ljava/lang/String;)V
 	public fun info (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun info (Ljava/lang/Throwable;)V
+	public fun isDebugEnabled ()Z
 	public fun isEnabled ()Z
+	public fun isErrorEnabled ()Z
+	public fun isInfoEnabled ()Z
+	public fun isVerboseEnabled ()Z
+	public fun isWarningEnabled ()Z
 	public fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger;
 	public fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V
 	public fun verbose (Ljava/lang/String;)V

+ 25 - 3
binary-compatibility-validator/api/binary-compatibility-validator.api

@@ -5651,15 +5651,20 @@ public abstract interface class net/mamoe/mirai/utils/MiraiLogger {
 	public abstract fun error (Ljava/lang/String;)V
 	public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun error (Ljava/lang/Throwable;)V
-	public abstract fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger;
 	public abstract fun getIdentity ()Ljava/lang/String;
 	public abstract fun info (Ljava/lang/String;)V
 	public abstract fun info (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun info (Ljava/lang/Throwable;)V
+	public fun isDebugEnabled ()Z
 	public abstract fun isEnabled ()Z
-	public abstract fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun isErrorEnabled ()Z
+	public fun isInfoEnabled ()Z
+	public fun isVerboseEnabled ()Z
+	public fun isWarningEnabled ()Z
+	public fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger;
 	public static fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V
-	public abstract fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V
+	public fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V
 	public abstract fun verbose (Ljava/lang/String;)V
 	public abstract fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V
 	public fun verbose (Ljava/lang/Throwable;)V
@@ -5674,6 +5679,23 @@ public final class net/mamoe/mirai/utils/MiraiLogger$Companion {
 	public final fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V
 }
 
+public abstract interface class net/mamoe/mirai/utils/MiraiLogger$Factory {
+	public static final field INSTANCE Lnet/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE;
+	public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public abstract fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Ljava/lang/Class;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Lkotlin/reflect/KClass;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger;
+}
+
+public final class net/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE : net/mamoe/mirai/utils/MiraiLogger$Factory {
+	public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger;
+	public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger;
+}
+
 public abstract class net/mamoe/mirai/utils/MiraiLoggerPlatformBase : net/mamoe/mirai/utils/MiraiLogger {
 	public fun <init> ()V
 	public final fun debug (Ljava/lang/String;)V

+ 5 - 2
buildSrc/src/main/kotlin/Versions.kt

@@ -40,7 +40,7 @@ object Versions {
     const val shadow = "6.1.0"
 
     const val slf4j = "1.7.30"
-    const val log4j = "2.13.3"
+    const val log4j = "2.14.1"
     const val asm = "9.1"
     const val difflib = "1.3.0"
     const val netty = "4.1.63.Final"
@@ -82,12 +82,15 @@ val `ktor-client-core` = ktor("client-core", Versions.ktor)
 val `ktor-client-cio` = ktor("client-cio", Versions.ktor)
 val `ktor-client-okhttp` = ktor("client-okhttp", Versions.ktor)
 val `ktor-client-android` = ktor("client-android", Versions.ktor)
+val `ktor-client-logging` = ktor("client-logging", Versions.ktor)
 val `ktor-network` = ktor("network", Versions.ktor)
 val `ktor-client-serialization-jvm` = ktor("client-serialization-jvm", Versions.ktor)
 
-const val slf4j = "org.slf4j:slf4j-api:" + Versions.slf4j
+const val `slf4j-api` = "org.slf4j:slf4j-api:" + Versions.slf4j
 const val `slf4j-simple` = "org.slf4j:slf4j-simple:" + Versions.slf4j
 const val `log4j-api` = "org.apache.logging.log4j:log4j-api:" + Versions.log4j
+const val `log4j-core` = "org.apache.logging.log4j:log4j-core:" + Versions.log4j
+const val `log4j-slf4j-impl` = "org.apache.logging.log4j:log4j-slf4j-impl:" + Versions.log4j
 
 val ATTRIBUTE_MIRAI_TARGET_PLATFORM: Attribute<String> = Attribute.of("mirai.target.platform", String::class.java)
 

+ 8 - 2
mirai-core-api/build.gradle.kts

@@ -66,8 +66,8 @@ kotlin {
                 api(`ktor-client-core`)
                 api(`ktor-network`)
 
-                compileOnly(`log4j-api`)
-                compileOnly(slf4j)
+                implementation(`log4j-api`)
+                compileOnly(`slf4j-api`)
 
 
                 // they use Kotlin 1.3 so we need to ignore transitive dependencies
@@ -77,6 +77,12 @@ kotlin {
             }
         }
 
+        commonTest {
+            dependencies {
+                runtimeOnly(`log4j-core`)
+            }
+        }
+
         if (isAndroidSDKAvailable) {
             val androidMain by getting {
                 dependsOn(commonMain)

+ 1 - 1
mirai-core-api/src/commonMain/kotlin/event/Event.kt

@@ -190,7 +190,7 @@ internal open class _EventBroadcast {
         }
     }
 
-    private val topLevelEventLogger by lazy { MiraiLogger.create("EventPipeline") }
+    private val topLevelEventLogger by lazy { MiraiLogger.Factory.create(Event::class, "EventPipeline") }
 }
 
 /**

+ 1 - 1
mirai-core-api/src/commonMain/kotlin/internal/utils/ExternalResourceLeakObserver.kt

@@ -21,7 +21,7 @@ internal object ExternalResourceLeakObserver : Runnable {
     private val queue = ReferenceQueue<Any>()
     private val references = ConcurrentLinkedDeque<ERReference>()
     private val logger by lazy {
-        MiraiLogger.create("ExternalResourceLeakObserver")
+        MiraiLogger.Factory.create(ExternalResourceLeakObserver::class)
     }
 
     internal class ERReference(

+ 67 - 23
mirai-core-api/src/commonMain/kotlin/internal/utils/LoggerAdapterImpls.kt

@@ -10,83 +10,127 @@
 package net.mamoe.mirai.internal.utils
 
 import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
-import org.slf4j.Logger
-import java.util.logging.Level
+import org.apache.logging.log4j.Marker
+import org.apache.logging.log4j.MarkerManager
+import java.util.logging.Level as JulLevel
+import java.util.logging.Logger as JulLogger
 
-internal class Log4jLogger(private val logger: org.apache.logging.log4j.Logger) : MiraiLoggerPlatformBase() {
+internal class Log4jLoggerAdapter(
+    private val logger: org.apache.logging.log4j.Logger,
+    override val marker: Marker?,
+) : MiraiLoggerPlatformBase(), MarkedMiraiLogger {
 
     override fun verbose0(message: String?, e: Throwable?) {
-        logger.trace(message, e)
+        val marker = marker
+        if (marker != null) logger.trace(marker, message, e)
+        else logger.trace(message, e)
     }
 
     override fun debug0(message: String?, e: Throwable?) {
-        logger.debug(message, e)
+        val marker = marker
+        if (marker != null) logger.debug(marker, message, e)
+        else logger.debug(message, e)
     }
 
     override fun info0(message: String?, e: Throwable?) {
-        logger.info(message, e)
+        val marker = marker
+        if (marker != null) logger.info(marker, message, e)
+        else logger.info(message, e)
     }
 
     override fun warning0(message: String?, e: Throwable?) {
-        logger.warn(message, e)
+        val marker = marker
+        if (marker != null) logger.warn(marker, message, e)
+        else logger.warn(message, e)
     }
 
     override fun error0(message: String?, e: Throwable?) {
-        logger.error(message, e)
+        val marker = marker
+        if (marker != null) logger.error(marker, message, e)
+        else logger.error(message, e)
     }
 
-    override val identity: String?
-        get() = logger.name
+    override val isVerboseEnabled: Boolean get() = logger.isTraceEnabled
+    override val isDebugEnabled: Boolean get() = logger.isDebugEnabled
+    override val isInfoEnabled: Boolean get() = logger.isInfoEnabled
+    override val isWarningEnabled: Boolean get() = logger.isWarnEnabled
+    override val isErrorEnabled: Boolean get() = logger.isErrorEnabled
+
+    override val identity: String? get() = logger.name
+
+    override fun subLogger(name: String): MarkedMiraiLogger {
+        return Log4jLoggerAdapter(logger, Marker(name, marker))
+    }
 
 }
 
-internal class Slf4jLogger(private val logger: Logger) : MiraiLoggerPlatformBase() {
+internal val MARKER_MIRAI by lazy { MarkerManager.getMarker("mirai") }
+
+internal class Slf4jLoggerAdapter(private val logger: org.slf4j.Logger, private val marker: org.slf4j.Marker?) :
+    MiraiLoggerPlatformBase() {
     override fun verbose0(message: String?, e: Throwable?) {
-        logger.trace(message, e)
+        if (marker == null) logger.trace(message, e)
+        else logger.trace(marker, message, e)
     }
 
     override fun debug0(message: String?, e: Throwable?) {
-        logger.debug(message, e)
+        if (marker == null) logger.debug(message, e)
+        else logger.debug(marker, message, e)
     }
 
     override fun info0(message: String?, e: Throwable?) {
-        logger.info(message, e)
+        if (marker == null) logger.info(message, e)
+        else logger.info(marker, message, e)
     }
 
     override fun warning0(message: String?, e: Throwable?) {
-        logger.warn(message, e)
+        if (marker == null) logger.warn(message, e)
+        else logger.warn(marker, message, e)
     }
 
     override fun error0(message: String?, e: Throwable?) {
-        logger.error(message, e)
+        if (marker == null) logger.error(message, e)
+        else logger.error(marker, message, e)
     }
 
+    override val isVerboseEnabled: Boolean get() = logger.isTraceEnabled
+    override val isDebugEnabled: Boolean get() = logger.isDebugEnabled
+    override val isInfoEnabled: Boolean get() = logger.isInfoEnabled
+    override val isWarningEnabled: Boolean get() = logger.isWarnEnabled
+    override val isErrorEnabled: Boolean get() = logger.isErrorEnabled
+
     override val identity: String?
         get() = logger.name
 }
 
-internal class JdkLogger(private val logger: java.util.logging.Logger) : MiraiLoggerPlatformBase() {
+internal class JdkLoggerAdapter(private val logger: JulLogger) : MiraiLoggerPlatformBase() {
     override fun verbose0(message: String?, e: Throwable?) {
-        logger.log(Level.FINER, message, e)
+        logger.log(JulLevel.FINEST, message, e)
     }
 
     override fun debug0(message: String?, e: Throwable?) {
-        logger.log(Level.FINEST, message, e)
+        logger.log(JulLevel.FINER, message, e)
 
     }
 
     override fun info0(message: String?, e: Throwable?) {
-        logger.log(Level.INFO, message, e)
+        logger.log(JulLevel.INFO, message, e)
     }
 
     override fun warning0(message: String?, e: Throwable?) {
-        logger.log(Level.WARNING, message, e)
+        logger.log(JulLevel.WARNING, message, e)
     }
 
     override fun error0(message: String?, e: Throwable?) {
-        logger.log(Level.SEVERE, message, e)
+        logger.log(JulLevel.SEVERE, message, e)
     }
 
+    override val isVerboseEnabled: Boolean get() = logger.isLoggable(JulLevel.FINE)
+    override val isDebugEnabled: Boolean get() = logger.isLoggable(JulLevel.FINEST)
+    override val isInfoEnabled: Boolean get() = logger.isLoggable(JulLevel.INFO)
+    override val isWarningEnabled: Boolean get() = logger.isLoggable(JulLevel.WARNING)
+    override val isErrorEnabled: Boolean get() = logger.isLoggable(JulLevel.SEVERE)
+
     override val identity: String?
         get() = logger.name
-}
+}

+ 70 - 0
mirai-core-api/src/commonMain/kotlin/internal/utils/MarkedMiraiLogger.kt

@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019-2021 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/dev/LICENSE
+ */
+
+package net.mamoe.mirai.internal.utils
+
+import net.mamoe.mirai.utils.MiraiLogger
+import org.apache.logging.log4j.Marker
+import org.apache.logging.log4j.MarkerManager
+
+/**
+ * 内部添加 [Marker] 支持, 并兼容旧 [MiraiLogger] API.
+ */
+internal interface MarkedMiraiLogger : MiraiLogger {
+    val marker: Marker?
+
+    /**
+     * Create an implementation-specific [MarkedMiraiLogger].
+     *
+     * Do not call the extension `MiraiLogger.subLogger` inside the function body.
+     */
+    fun subLogger(name: String): MarkedMiraiLogger
+}
+
+internal fun Marker(name: String, parents: Marker?): Marker {
+    return MarkerManager.getMarker(name).apply { if (parents != null) addParents(parents) }
+}
+
+internal fun Marker(name: String, vararg parents: Marker?): Marker {
+    return MarkerManager.getMarker(name).apply {
+        parents.forEach { if (it != null) addParents(it) }
+    }
+}
+
+internal val MiraiLogger.markerOrNull get() = (this as? MarkedMiraiLogger)?.marker
+
+/**
+ * Create a marked logger whose marker is a child of this' marker.
+ *
+ * Calling [MarkedMiraiLogger.subLogger] if possible, and creating [MiraiLoggerMarkedWrapper] otherwise.
+ */
+internal fun MiraiLogger.subLogger(name: String): MarkedMiraiLogger {
+    return subLoggerImpl(this, name)
+}
+
+// used by mirai-core
+internal fun subLoggerImpl(origin: MiraiLogger, name: String): MarkedMiraiLogger {
+    return if (origin is MarkedMiraiLogger) {
+        // origin can be Log4JAdapter or MiraiLoggerMarkedWrapper which delegates a non-Log4JAdapter.
+        origin.subLogger(name) // Log4JAdapter natively supports Markers.
+    } else {
+        // origin does not support Markers, so we add a wrapper for it.
+        MiraiLoggerMarkedWrapper(origin, Marker(name, origin.markerOrNull ?: MARKER_MIRAI))
+    }
+}
+
+/**
+ * 仅当日志系统使用的不是 Log4J 时才会构造 [MiraiLoggerMarkedWrapper].
+ */
+private class MiraiLoggerMarkedWrapper(
+    val origin: MiraiLogger,
+    override val marker: Marker
+) : MiraiLogger by origin, MarkedMiraiLogger {
+    override fun subLogger(name: String): MarkedMiraiLogger = MiraiLoggerMarkedWrapper(origin, Marker(name, marker))
+}

+ 6 - 2
mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt

@@ -326,7 +326,9 @@ public open class BotConfiguration { // open for Java
      *
      * @see MiraiLogger
      */
-    public var botLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Bot ${it.id}") }
+    public var botLoggerSupplier: ((Bot) -> MiraiLogger) = {
+        MiraiLogger.Factory.create(Bot::class, "Bot ${it.id}")
+    }
 
     /**
      * 网络层日志构造器
@@ -338,7 +340,9 @@ public open class BotConfiguration { // open for Java
      *
      * @see MiraiLogger
      */
-    public var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.create("Net ${it.id}") }
+    public var networkLoggerSupplier: ((Bot) -> MiraiLogger) = {
+        MiraiLogger.Factory.create(Bot::class, "Net ${it.id}")
+    }
 
 
     /**

+ 1 - 0
mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt

@@ -60,6 +60,7 @@ public class DeviceInfo(
     )
 
     public companion object {
+        internal val logger = MiraiLogger.Factory.create(DeviceInfo::class, "DeviceInfo")
 
         /**
          * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存.

+ 52 - 6
mirai-core-api/src/commonMain/kotlin/utils/LoggerAdapters.kt

@@ -7,25 +7,71 @@
  *  https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+@file:Suppress("unused")
+
 package net.mamoe.mirai.utils
 
-import net.mamoe.mirai.internal.utils.JdkLogger
-import net.mamoe.mirai.internal.utils.Log4jLogger
-import net.mamoe.mirai.internal.utils.Slf4jLogger
+import net.mamoe.mirai.internal.utils.JdkLoggerAdapter
+import net.mamoe.mirai.internal.utils.Log4jLoggerAdapter
+import net.mamoe.mirai.internal.utils.MARKER_MIRAI
+import net.mamoe.mirai.internal.utils.Slf4jLoggerAdapter
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Marker
+import org.apache.logging.log4j.MarkerManager
 
+/**
+ * [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 到 [MiraiLogger] 的转换器.
+ */
 public object LoggerAdapters {
+    /**
+     * 使用 [LOG4J2][org.apache.logging.log4j.Logger] 接管全局 Mirai 日志系统. 请在调用 Mirai API 任何其他 API 前调用该方法.
+     *
+     * 注意, 若已经通过 service 方式提供 [MiraiLogger.Factory] 来接管日志系统, 则本方法无效.
+     *
+     * @since 2.7
+     */
+    @JvmStatic
+    @MiraiExperimentalApi
+    public fun useLog4j2() {
+        DefaultFactory.override { requester, identity ->
+            val logger = LogManager.getLogger(requester)
+            Log4jLoggerAdapter(logger, MarkerManager.getMarker(identity ?: logger.name).addParents(MARKER_MIRAI))
+        }
+    }
+
+
+    /**
+     * 将 [java.util.logging.Logger] 转换作为 [MiraiLogger] 使用.
+     */
     @JvmStatic
     public fun java.util.logging.Logger.asMiraiLogger(): MiraiLogger {
-        return JdkLogger(this)
+        return JdkLoggerAdapter(this)
     }
 
+    /**
+     * 将 [org.apache.logging.log4j.Logger] 转换作为 [MiraiLogger] 使用.
+     */
     @JvmStatic
     public fun org.apache.logging.log4j.Logger.asMiraiLogger(): MiraiLogger {
-        return Log4jLogger(this)
+        return Log4jLoggerAdapter(this, null)
     }
 
+    /**
+     * 将 [org.slf4j.Logger] 转换作为 [MiraiLogger] 使用.
+     */
     @JvmStatic
     public fun org.slf4j.Logger.asMiraiLogger(): MiraiLogger {
-        return Slf4jLogger(this)
+        return Slf4jLoggerAdapter(this, null)
+    }
+
+    /**
+     * 将 [org.apache.logging.log4j.Logger] 转换作为 [MiraiLogger] 使用.
+     *
+     * @since 2.7
+     */
+    @MiraiExperimentalApi
+    @JvmStatic
+    public fun org.apache.logging.log4j.Logger.asMiraiLogger(marker: Marker): MiraiLogger {
+        return Log4jLoggerAdapter(this, marker)
     }
 }

+ 176 - 33
mirai-core-api/src/commonMain/kotlin/utils/MiraiLogger.kt

@@ -13,7 +13,8 @@
 
 package net.mamoe.mirai.utils
 
-import net.mamoe.mirai.Bot
+import java.util.*
+import kotlin.reflect.KClass
 
 /**
  * 给这个 logger 添加一个开关, 用于控制是否记录 log
@@ -22,57 +23,106 @@ import net.mamoe.mirai.Bot
 public fun MiraiLogger.withSwitch(default: Boolean = true): MiraiLoggerWithSwitch = MiraiLoggerWithSwitch(this, default)
 
 /**
- * 日志记录器. 所有的输出均依赖于它.
- * 不同的对象可拥有只属于自己的 logger. 通过 [identity] 来区分.
+ * 日志记录器.
  *
- * 注意: 如果你需要重新实现日志, 请不要直接实现这个接口, 请继承 [MiraiLoggerPlatformBase]
+ * ## Mirai 日志系统
  *
- * 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch].
+ * Mirai 内建简单的日志系统, 即 [MiraiLogger]. [MiraiLogger] 的实现有 [SimpleLogger], [PlatformLogger], [SilentLogger].
+ *
+ * [MiraiLogger] 仅能处理简单的日志任务, 通常推荐使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 等日志库.
+ *
+ * ## 使用第三方日志库接管 Mirai 日志系统
+ *
+ * 使用 [LoggerAdapters], 将第三方日志 `Logger` 转为 [MiraiLogger]. 然后通过 [MiraiLogger.setDefaultLoggerCreator] 全局覆盖日志.
+ *
+ * ## 实现或使用 [MiraiLogger]
  *
- * Mirai 内建三种日志实现, 分别是 [SimpleLogger], [PlatformLogger], [SilentLogger]
+ * 不建议实现或使用 [MiraiLogger]. 请优先考虑使用上述第三方框架. [MiraiLogger] 仅应用于兼容旧版本代码.
  *
  * @see SimpleLogger 简易 logger, 它将所有的日志记录操作都转移给 lambda `(String?, Throwable?) -> Unit`
  * @see PlatformLogger 各个平台下的默认日志记录实现.
  * @see SilentLogger 忽略任何日志记录操作的 logger 实例.
+ * @see LoggerAdapters
  *
  * @see MiraiLoggerPlatformBase 平台通用基础实现. 若 Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数.
  */
 public interface MiraiLogger {
 
+    /**
+     * 可以 service 实现的方式覆盖.
+     *
+     * @since 2.7
+     */
+    public interface Factory {
+        /**
+         * 创建 [MiraiLogger] 实例.
+         *
+         * @param requester 请求创建 [MiraiLogger] 的对象的 class
+         * @param identity 对象标记 (备注)
+         */
+        public fun create(requester: KClass<*>, identity: String? = null): MiraiLogger =
+            this.create(requester.java, identity)
+
+        /**
+         * 创建 [MiraiLogger] 实例.
+         *
+         * @param requester 请求创建 [MiraiLogger] 的对象的 class
+         * @param identity 对象标记 (备注)
+         */
+        public fun create(requester: Class<*>, identity: String? = null): MiraiLogger
+
+        /**
+         * 创建 [MiraiLogger] 实例.
+         *
+         * @param requester 请求创建 [MiraiLogger] 的对象
+         */
+        public fun create(requester: KClass<*>): MiraiLogger = create(requester, null)
+
+        /**
+         * 创建 [MiraiLogger] 实例.
+         *
+         * @param requester 请求创建 [MiraiLogger] 的对象
+         */
+        public fun create(requester: Class<*>): MiraiLogger = create(requester, null)
+
+        public companion object INSTANCE : Factory by loadService({ DefaultFactory() })
+    }
+
     public companion object {
         /**
          * 顶层日志, 仅供 Mirai 内部使用.
          */
         @MiraiInternalApi
         @MiraiExperimentalApi
-        public val TopLevel: MiraiLogger by lazy { create("Mirai") }
-
-        @Volatile
-        private var defaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger(it) }
+        @Deprecated("Deprecated.")
+        public val TopLevel: MiraiLogger by lazy { Factory.create(MiraiLogger::class, "Mirai") }
 
         /**
-         * 可直接修改这个变量的值来重定向日志输出.
+         * 已弃用, 请实现 service [net.mamoe.mirai.utils.MiraiLogger.Factory] 并以 [ServiceLoader] 支持的方式提供.
          */
+        @Suppress("DeprecatedCallableAddReplaceWith")
+        @Deprecated(
+            "Please set factory by providing an service of type net.mamoe.mirai.utils.MiraiLogger.Factory",
+            level = DeprecationLevel.WARNING
+        )
         @JvmStatic
         public fun setDefaultLoggerCreator(creator: (identity: String?) -> MiraiLogger) {
-            defaultLogger = creator
+            DefaultFactory.override { _, identity -> creator(identity) }
         }
 
         /**
-         * 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, 如 [Bot], 都会通过这个函数构造日志记录器.
-         *
-         * **注意:** 请务必将所有的输出定向到日志记录系统, 否则在某些情况下 (如 web 控制台中) 将无法接收到输出
-         *
-         * **注意:** 请为日志做好分类, 即不同的模块使用不同的 [MiraiLogger].
-         * 如, [Bot] 中使用 `identity` 为 "Bot(qqId)" 的 [MiraiLogger]
-         * 而 [Bot] 的网络处理中使用 `identity` 为 "BotNetworkHandler".
+         * 旧版本用于创建 [MiraiLogger]. 已弃用. 请使用 [MiraiLogger.Factory.INSTANCE.create].
          *
          * @see setDefaultLoggerCreator
          */
+        @Deprecated(
+            "Please use MiraiLogger.Factory.create", ReplaceWith(
+                "MiraiLogger.Factory.create(YourClass::class, identity)",
+                "net.mamoe.mirai.utils.MiraiLogger"
+            ), level = DeprecationLevel.WARNING
+        )
         @JvmStatic
-        public fun create(identity: String?): MiraiLogger {
-            return defaultLogger.invoke(identity)
-        }
+        public fun create(identity: String?): MiraiLogger = Factory.create(MiraiLogger::class, identity)
     }
 
     /**
@@ -92,6 +142,61 @@ public interface MiraiLogger {
      */
     public val isEnabled: Boolean
 
+    /**
+     * 当 VERBOSE 级别的日志启用时返回 `true`.
+     *
+     * 若 [isEnabled] 为 `false`, 返回 `false`.
+     * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值.
+     * 其他情况下返回 [isEnabled] 的值.
+     *
+     * @since 2.7
+     */
+    public val isVerboseEnabled: Boolean get() = isEnabled
+
+    /**
+     * 当 DEBUG 级别的日志启用时返回 `true`
+     *
+     * 若 [isEnabled] 为 `false`, 返回 `false`.
+     * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值.
+     * 其他情况下返回 [isEnabled] 的值.
+     *
+     * @since 2.7
+     */
+    public val isDebugEnabled: Boolean get() = isEnabled
+
+    /**
+     * 当 INFO 级别的日志启用时返回 `true`
+     *
+     * 若 [isEnabled] 为 `false`, 返回 `false`.
+     * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值.
+     * 其他情况下返回 [isEnabled] 的值.
+     *
+     * @since 2.7
+     */
+    public val isInfoEnabled: Boolean get() = isEnabled
+
+    /**
+     * 当 WARNING 级别的日志启用时返回 `true`
+     *
+     * 若 [isEnabled] 为 `false`, 返回 `false`.
+     * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值.
+     * 其他情况下返回 [isEnabled] 的值.
+     *
+     * @since 2.7
+     */
+    public val isWarningEnabled: Boolean get() = isEnabled
+
+    /**
+     * 当 ERROR 级别的日志启用时返回 `true`
+     *
+     * 若 [isEnabled] 为 `false`, 返回 `false`.
+     * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值.
+     * 其他情况下返回 [isEnabled] 的值.
+     *
+     * @since 2.7
+     */
+    public val isErrorEnabled: Boolean get() = isEnabled
+
     /**
      * 随从. 在 this 中调用所有方法后都应继续往 [follower] 传递调用.
      * [follower] 的存在可以让一次日志被多个日志记录器记录.
@@ -102,7 +207,11 @@ public interface MiraiLogger {
      *
      * 当然, 多个 logger 也可以加在一起: `val logger = bot.logger + MynLogger() + MyLogger2()`
      */
+    @Suppress("UNUSED_PARAMETER")
+    @Deprecated("follower 设计不佳, 请避免使用", level = DeprecationLevel.WARNING) // deprecated since 2.7
     public var follower: MiraiLogger?
+        get() = null
+        set(value) {}
 
     /**
      * 记录一个 `verbose` 级别的日志.
@@ -164,48 +273,50 @@ public interface MiraiLogger {
      *
      * @return [follower]
      */
-    public operator fun <T : MiraiLogger> plus(follower: T): T
+    @Suppress("DeprecatedCallableAddReplaceWith")
+    @Deprecated("plus 设计不佳, 请避免使用.", level = DeprecationLevel.WARNING) // deprecated since 2.7
+    public operator fun <T : MiraiLogger> plus(follower: T): T = follower
 }
 
 
 public inline fun MiraiLogger.verbose(message: () -> String) {
-    if (isEnabled) verbose(message())
+    if (isVerboseEnabled) verbose(message())
 }
 
 public inline fun MiraiLogger.verbose(message: () -> String, e: Throwable?) {
-    if (isEnabled) verbose(message(), e)
+    if (isVerboseEnabled) verbose(message(), e)
 }
 
 public inline fun MiraiLogger.debug(message: () -> String?) {
-    if (isEnabled) debug(message())
+    if (isDebugEnabled) debug(message())
 }
 
 public inline fun MiraiLogger.debug(message: () -> String?, e: Throwable?) {
-    if (isEnabled) debug(message(), e)
+    if (isDebugEnabled) debug(message(), e)
 }
 
 public inline fun MiraiLogger.info(message: () -> String?) {
-    if (isEnabled) info(message())
+    if (isInfoEnabled) info(message())
 }
 
 public inline fun MiraiLogger.info(message: () -> String?, e: Throwable?) {
-    if (isEnabled) info(message(), e)
+    if (isInfoEnabled) info(message(), e)
 }
 
 public inline fun MiraiLogger.warning(message: () -> String?) {
-    if (isEnabled) warning(message())
+    if (isWarningEnabled) warning(message())
 }
 
 public inline fun MiraiLogger.warning(message: () -> String?, e: Throwable?) {
-    if (isEnabled) warning(message(), e)
+    if (isWarningEnabled) warning(message(), e)
 }
 
 public inline fun MiraiLogger.error(message: () -> String?) {
-    if (isEnabled) error(message())
+    if (isErrorEnabled) error(message())
 }
 
 public inline fun MiraiLogger.error(message: () -> String?, e: Throwable?) {
-    if (isEnabled) error(message(), e)
+    if (isErrorEnabled) error(message(), e)
 }
 
 /**
@@ -353,8 +464,12 @@ public class MiraiLoggerWithSwitch internal constructor(private val delegate: Mi
  * @see PlatformLogger
  * @see SimpleLogger
  */
+@Suppress("DEPRECATION")
 public abstract class MiraiLoggerPlatformBase : MiraiLogger {
     public override val isEnabled: Boolean get() = true
+
+    @Suppress("OverridingDeprecatedMember")
+    @Deprecated("follower 设计不佳, 请避免使用", level = DeprecationLevel.WARNING) // deprecated since 2.7
     public final override var follower: MiraiLogger? = null
 
     public final override fun verbose(message: String?) {
@@ -428,8 +543,36 @@ public abstract class MiraiLoggerPlatformBase : MiraiLogger {
     protected open fun error0(message: String?): Unit = error0(message, null)
     protected abstract fun error0(message: String?, e: Throwable?)
 
+    @Suppress("OverridingDeprecatedMember")
+    @Deprecated("plus 设计不佳, 请避免使用.", level = DeprecationLevel.WARNING) // deprecated since 2.7
     public override operator fun <T : MiraiLogger> plus(follower: T): T {
         this.follower = follower
         return follower
     }
 }
+
+
+internal class DefaultFactory : MiraiLogger.Factory {
+    init {
+        INSTANCE = this
+    }
+
+    companion object {
+        lateinit var INSTANCE: DefaultFactory
+            private set
+
+        fun override(lambda: (requester: Class<*>, identity: String?) -> MiraiLogger) {
+            INSTANCE.override = lambda
+        }
+    }
+
+    private var override: ((requester: Class<*>, identity: String?) -> MiraiLogger)? =
+        null // 支持 LoggerAdapters 以及兼容旧版本
+
+    override fun create(requester: Class<*>, identity: String?): MiraiLogger {
+        val override = override
+        return if (override != null) override(requester, identity) else PlatformLogger(
+            identity ?: requester.kotlin.simpleName ?: requester.simpleName
+        )
+    }
+}

+ 88 - 0
mirai-core-api/src/commonTest/kotlin/logging/Log4j2LoggingTest.kt

@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019-2021 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/dev/LICENSE
+ */
+
+package net.mamoe.mirai.logging
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.internal.utils.Log4jLoggerAdapter
+import net.mamoe.mirai.internal.utils.MARKER_MIRAI
+import net.mamoe.mirai.internal.utils.Marker
+import net.mamoe.mirai.internal.utils.subLogger
+import net.mamoe.mirai.utils.DefaultFactory
+import net.mamoe.mirai.utils.LoggerAdapters.asMiraiLogger
+import net.mamoe.mirai.utils.MiraiLogger
+import org.apache.logging.log4j.LogManager
+import org.junit.jupiter.api.BeforeEach
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertSame
+
+internal class Log4j2LoggingTest {
+    @BeforeEach
+    fun init() {
+        DefaultFactory.override { requester, identity ->
+            LogManager.getLogger(requester).asMiraiLogger(Marker(identity ?: requester.simpleName, MARKER_MIRAI))
+        }
+    }
+
+    private fun MiraiLogger.cast(): Log4jLoggerAdapter = this as Log4jLoggerAdapter
+
+    @Test
+    fun `created is Log4jLoggerAdapter`() {
+        val logger = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1")
+        assertIs<Log4jLoggerAdapter>(logger)
+    }
+
+    @Test
+    fun `identity is considered as marker`() {
+        val logger = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1")
+        assertEquals("test1", logger.cast().marker!!.name)
+    }
+
+
+    @Test
+    fun `test subLogger Marker`() {
+        val parent = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1")
+        val parentMarker = parent.cast().marker!!
+
+        val child = parent.subLogger("sub")
+        val childMarker = child.marker!!
+
+        assertEquals("test1", parentMarker.name)
+        assertEquals("sub", childMarker.name)
+
+        assertSame(parentMarker, childMarker.parents.single())
+        assertSame("test1", childMarker.parents.single().name)
+    }
+
+    @Test
+    fun `test subLogger Marker 2`() {
+        val parent = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1")
+        val parentMarker = parent.cast().marker!!
+
+        val child = parent.subLogger("sub").subLogger("sub2")
+        val childMarker = child.marker!!
+
+        assertEquals("test1", parentMarker.name)
+        assertEquals("sub2", childMarker.name)
+
+        assertSame("sub", childMarker.parents.single().name)
+        assertSame(parentMarker, childMarker.parents.single().parents.single())
+    }
+
+    @Test
+    fun `logging output test`() {
+        val logger = LogManager.getLogger(Bot::class.java)
+        logger.info("Test")
+        MiraiLogger.Factory.create(Bot::class).run {
+            info("InfoFF")
+        }
+    }
+}

+ 13 - 0
mirai-core-api/src/commonTest/resources/log4j.properties

@@ -0,0 +1,13 @@
+#
+# Copyright 2019-2021 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/dev/LICENSE
+#
+log4j.rootLogger=INFO,stdout
+log4j.logger.mirai=INFO
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t%m%n

+ 10 - 5
mirai-core-utils/src/androidMain/kotlin/Actuals.kt

@@ -47,12 +47,17 @@ public actual inline fun <reified E> Throwable.unwrap(): Throwable {
 public actual fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String?): T {
     var suppressed: Throwable? = null
     return ServiceLoader.load(clazz.java).firstOrNull()
-        ?: runCatching {
-            Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() }
-        }.onFailure {
-            suppressed = it
-        }.getOrNull()
+        ?: runCatching { findCreateInstance<T>(fallbackImplementation) }.onFailure { suppressed = it }.getOrNull()
         ?: throw NoSuchElementException("Could not find an implementation for service class ${clazz.qualifiedName}").apply {
             if (suppressed != null) addSuppressed(suppressed)
         }
+}
+
+private fun <T : Any> findCreateInstance(fallbackImplementation: String?): T {
+    return Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() }
+}
+
+public actual fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String?): T? {
+    return ServiceLoader.load(clazz.java).firstOrNull()
+        ?: runCatching { findCreateInstance<T>(fallbackImplementation) }.getOrNull()
 }

+ 6 - 1
mirai-core-utils/src/commonMain/kotlin/Services.kt

@@ -11,6 +11,11 @@ package net.mamoe.mirai.utils
 
 import kotlin.reflect.KClass
 
+public expect fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String? = null): T?
 public expect fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String? = null): T
+
 public inline fun <reified T : Any> loadService(fallbackImplementation: String? = null): T =
-    loadService(T::class, fallbackImplementation)
+    loadService(T::class, fallbackImplementation)
+
+public inline fun <reified T : Any> loadService(noinline fallbackImplementation: () -> T): T =
+    loadServiceOrNull(T::class) ?: fallbackImplementation()

+ 10 - 5
mirai-core-utils/src/jvmMain/kotlin/Actuals.kt

@@ -35,12 +35,17 @@ public actual inline fun <reified E> Throwable.unwrap(): Throwable {
 public actual fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String?): T {
     var suppressed: Throwable? = null
     return ServiceLoader.load(clazz.java).firstOrNull()
-        ?: runCatching {
-            Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() }
-        }.onFailure {
-            suppressed = it
-        }.getOrNull()
+        ?: runCatching { findCreateInstance<T>(fallbackImplementation) }.onFailure { suppressed = it }.getOrNull()
         ?: throw NoSuchElementException("Could not find an implementation for service class ${clazz.qualifiedName}").apply {
             if (suppressed != null) addSuppressed(suppressed)
         }
+}
+
+private fun <T : Any> findCreateInstance(fallbackImplementation: String?): T {
+    return Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() }
+}
+
+public actual fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String?): T? {
+    return ServiceLoader.load(clazz.java).firstOrNull()
+        ?: runCatching { findCreateInstance<T>(fallbackImplementation) }.getOrNull()
 }

+ 1 - 1
mirai-core/build.gradle.kts

@@ -72,13 +72,13 @@ kotlin {
                 api1(`kotlinx-io-jvm`)
                 implementation1(`kotlinx-coroutines-io`)
                 implementation(`netty-all`)
+                implementation(`log4j-api`)
             }
         }
 
         commonTest {
             dependencies {
                 implementation(kotlin("script-runtime"))
-                runtimeOnly(`slf4j-simple`)
             }
         }
 

+ 1 - 1
mirai-core/src/androidTest/kotlin/test/initPlatform.android.kt

@@ -34,7 +34,7 @@ internal actual class PlatformInitializationTest : AbstractTest() {
     actual fun test() {
         assertTrue {
             @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-            MiraiLogger.create("1") is net.mamoe.mirai.internal.utils.StdoutLogger
+            MiraiLogger.Factory.create(this::class, "1") is net.mamoe.mirai.internal.utils.StdoutLogger
         }
     }
 }

+ 5 - 2
mirai-core/src/commonMain/kotlin/message/imagesImpl.kt

@@ -57,6 +57,9 @@ internal class OnlineGroupImageImpl(
 
 }
 
+private val imageLogger: MiraiLogger by lazy { MiraiLogger.Factory.create(Image::class) }
+internal val Image.Key.logger get() = imageLogger
+
 @Serializable(with = OnlineFriendImageImpl.Serializer::class)
 internal class OnlineFriendImageImpl(
     internal val delegate: ImMsgBody.NotOnlineImage,
@@ -70,7 +73,7 @@ OnlineFriendImage() {
             ?: kotlin.run {
                 if (delegate.picMd5.size == 16) generateImageId(delegate.picMd5, imageType)
                 else {
-                    MiraiLogger.TopLevel.warning(
+                    Image.logger.warning(
                         contextualBugReportException(
                             "Failed to compute friend imageId: resId=${delegate.resId}",
                             delegate._miraiContentToString(),
@@ -115,7 +118,7 @@ internal fun getImageType(id: Int): String {
         2001, 3 -> "png"
         else -> {
             if (UNKNOWN_IMAGE_TYPE_PROMPT_ENABLED) {
-                MiraiLogger.TopLevel.debug(
+                Image.logger.debug(
                     "Unknown image id: $id. Stacktrace:",
                     Exception()
                 )

+ 1 - 1
mirai-core/src/commonMain/kotlin/network/components/PacketCodec.kt

@@ -45,7 +45,7 @@ internal interface PacketCodec {
         val PACKET_DEBUG = systemProp("mirai.network.packet.logger", false)
 
         internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
-            MiraiLogger.create("Packet").withSwitch(PACKET_DEBUG)
+            MiraiLogger.Factory.create(PacketCodec::class, "Packet").withSwitch(PACKET_DEBUG)
         }
     }
 }

+ 2 - 2
mirai-core/src/commonMain/kotlin/network/components/ServerList.kt

@@ -105,10 +105,10 @@ internal class ServerListImpl(
     initial: Collection<ServerAddress> = emptyList()
 ) : ServerList {
     @TestOnly
-    constructor(initial: Collection<ServerAddress>) : this(MiraiLogger.TopLevel, initial)
+    constructor(initial: Collection<ServerAddress>) : this(MiraiLogger.Factory.create(ServerListImpl::class), initial)
 
     @TestOnly
-    constructor() : this(MiraiLogger.TopLevel)
+    constructor() : this(MiraiLogger.Factory.create(ServerListImpl::class))
 
     @Volatile
     private var preferred: Set<ServerAddress> = DEFAULT_SERVER_LIST

+ 2 - 1
mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt

@@ -23,6 +23,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.utils.SingleEntrantLock
 import net.mamoe.mirai.internal.utils.fromMiraiLogger
+import net.mamoe.mirai.internal.utils.subLogger
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.Either.Companion.fold
 import java.util.concurrent.ConcurrentLinkedQueue
@@ -56,7 +57,7 @@ internal abstract class NetworkHandlerSupport(
     }
 
     protected val packetLogger: MiraiLogger by lazy {
-        MiraiLogger.create(context.logger.identity + ".debug").withSwitch(PacketCodec.PACKET_DEBUG)
+        context.logger.subLogger("NetworkDebug").withSwitch(PacketCodec.PACKET_DEBUG)
     }
 
     ///////////////////////////////////////////////////////////////////////////

+ 4 - 4
mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt

@@ -84,14 +84,14 @@ internal class LoggingStateObserver(
             return when (ENABLED) {
                 "full" -> {
                     SafeStateObserver(
-                        LoggingStateObserver(MiraiLogger.create("States"), true),
-                        MiraiLogger.create("LoggingStateObserver errors")
+                        LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States"), true),
+                        MiraiLogger.Factory.create(LoggingStateObserver::class, "LoggingStateObserver errors")
                     )
                 }
                 "on", "true" -> {
                     SafeStateObserver(
-                        LoggingStateObserver(MiraiLogger.create("States"), false),
-                        MiraiLogger.create("LoggingStateObserver errors")
+                        LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States"), false),
+                        MiraiLogger.Factory.create(LoggingStateObserver::class, "LoggingStateObserver errors")
                     )
                 }
                 else -> null

+ 4 - 52
mirai-core/src/commonMain/kotlin/utils/SubLogger.kt

@@ -11,7 +11,8 @@ package net.mamoe.mirai.internal.utils
 
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineExceptionHandler
-import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.coroutineName
 
 
 internal fun CoroutineExceptionHandler.Key.fromMiraiLogger(
@@ -25,54 +26,5 @@ internal fun CoroutineExceptionHandler.Key.fromMiraiLogger(
     }
 }
 
-internal fun MiraiLogger.subLogger(name: String): MiraiLogger {
-    return SubLogger(name, this)
-}
-
-private class SubLogger(
-    private val name: String,
-    private val main: MiraiLogger,
-) : MiraiLoggerPlatformBase() {
-    override val identity: String? get() = main.identity
-    override val isEnabled: Boolean get() = main.isEnabled
-
-    override fun verbose0(message: String?, e: Throwable?) {
-        if (message != null) {
-            main.verbose({ "[$name] $message" }, e)
-        } else {
-            main.verbose(null, e)
-        }
-    }
-
-    override fun debug0(message: String?, e: Throwable?) {
-        if (message != null) {
-            main.debug({ "[$name] $message" }, e)
-        } else {
-            main.debug(null, e)
-        }
-    }
-
-    override fun info0(message: String?, e: Throwable?) {
-        if (message != null) {
-            main.info({ "[$name] $message" }, e)
-        } else {
-            main.info(null, e)
-        }
-    }
-
-    override fun warning0(message: String?, e: Throwable?) {
-        if (message != null) {
-            main.warning({ "[$name] $message" }, e)
-        } else {
-            main.warning(null, e)
-        }
-    }
-
-    override fun error0(message: String?, e: Throwable?) {
-        if (message != null) {
-            main.error({ "[$name] $message" }, e)
-        } else {
-            main.error(null, e)
-        }
-    }
-}
+@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+internal fun MiraiLogger.subLogger(name: String): MiraiLogger = subLoggerImpl(this, name)

+ 2 - 1
mirai-core/src/commonMain/kotlin/utils/contentToString.kt

@@ -12,6 +12,7 @@
 package net.mamoe.mirai.internal.utils
 
 import kotlinx.serialization.Transient
+import net.mamoe.mirai.IMirai
 import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.debug
 import net.mamoe.mirai.utils.toUHexString
@@ -32,7 +33,7 @@ private fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T)
     return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform)
 }
 
-private val SoutvLogger: MiraiLogger by lazy { MiraiLogger.create("soutv") }
+private val SoutvLogger: MiraiLogger by lazy { MiraiLogger.Factory.create(IMirai::class, "soutv") }
 internal fun Any?.soutv(name: String = "unnamed") {
     @Suppress("DEPRECATION")
     SoutvLogger.debug { "$name = ${this._miraiContentToString()}" }

+ 4 - 1
mirai-core/src/commonTest/kotlin/network/component/EventDispatcherTest.kt

@@ -26,7 +26,10 @@ import org.junit.jupiter.api.Test
 internal class EventDispatcherTest : AbstractTest() {
     private class Ev : AbstractEvent()
 
-    private val dispatcher = TestEventDispatcherImpl(SupervisorJob(), MiraiLogger.create("EventDispatcherTest"))
+    private val dispatcher = TestEventDispatcherImpl(
+        SupervisorJob(),
+        MiraiLogger.Factory.create(EventDispatcherTest::class)
+    )
 
     @Test
     fun `can broadcast`() = runBlockingUnit {

+ 4 - 3
mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt

@@ -11,6 +11,7 @@
 
 package net.mamoe.mirai.internal.network.framework
 
+import net.mamoe.mirai.Bot
 import net.mamoe.mirai.internal.MockBot
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
@@ -39,7 +40,7 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT
         nhProvider = { createNetworkHandler() }
         additionalComponentsProvider = { [email protected] }
     }
-    protected val logger = MiraiLogger.create("test")
+    protected val logger = MiraiLogger.Factory.create(Bot::class, "test")
     protected val components = ConcurrentComponentStorage().apply {
         set(SsoProcessor, TestSsoProcessor(bot))
         set(
@@ -49,8 +50,8 @@ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerT
         set(
             StateObserver,
             SafeStateObserver(
-                LoggingStateObserver(MiraiLogger.create("States")),
-                MiraiLogger.create("StateObserver errors")
+                LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States")),
+                MiraiLogger.Factory.create(SafeStateObserver::class, "StateObserver errors")
             )
         )
     }

+ 1 - 1
mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt

@@ -53,7 +53,7 @@ internal sealed class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abstr
         }
     }
 
-    open val networkLogger = MiraiLogger.create("network")
+    open val networkLogger = MiraiLogger.Factory.create(NetworkHandler::class, "network")
 
     sealed class NHEvent {
         object Login : NHEvent()

+ 3 - 3
mirai-core/src/commonTest/kotlin/network/framework/TestNetworkHandlerContext.kt

@@ -24,14 +24,14 @@ import net.mamoe.mirai.utils.MiraiLogger
 
 internal class TestNetworkHandlerContext(
     override val bot: QQAndroidBot = MockBot(),
-    override val logger: MiraiLogger = MiraiLogger.create("Test"),
+    override val logger: MiraiLogger = MiraiLogger.Factory.create(TestNetworkHandlerContext::class, "Test"),
     components: ComponentStorage = ConcurrentComponentStorage().apply {
         set(SsoProcessor, SsoProcessorImpl(SsoProcessorContextImpl(bot)))
         set(
             StateObserver,
             SafeStateObserver(
-                LoggingStateObserver(MiraiLogger.create("States")),
-                MiraiLogger.create("StateObserver errors")
+                LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States")),
+                MiraiLogger.Factory.create(LoggingStateObserver::class, "StateObserver errors")
             )
         )
     }

+ 1 - 1
mirai-core/src/commonTest/kotlin/network/handler/KeepAliveNetworkHandlerSelectorTest.kt

@@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger
 import kotlin.test.*
 import kotlin.time.Duration
 
-internal val selectorLogger = MiraiLogger.create("selector")
+internal val selectorLogger = MiraiLogger.Factory.create(TestSelector::class, "selector")
 
 internal class TestSelector<H : NetworkHandler> :
     AbstractKeepAliveNetworkHandlerSelector<H> {

+ 10 - 3
mirai-core/src/commonTest/kotlin/test/initPlatform.common.kt

@@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.test
 import net.mamoe.mirai.IMirai
 import net.mamoe.mirai.internal.network.framework.SynchronizedStdoutLogger
 import net.mamoe.mirai.utils.MiraiLogger
+import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.Timeout
 import java.util.concurrent.TimeUnit
@@ -26,9 +27,7 @@ abstract class AbstractTest {
     init {
         initPlatform()
 
-        MiraiLogger.setDefaultLoggerCreator {
-            SynchronizedStdoutLogger(it)
-        }
+        restoreLoggerFactory()
 
         System.setProperty("mirai.network.packet.logger", "true")
         System.setProperty("mirai.network.state.observer.logging", "true")
@@ -38,6 +37,14 @@ abstract class AbstractTest {
 
     }
 
+    @AfterEach
+    protected fun restoreLoggerFactory() {
+        @Suppress("DEPRECATION")
+        MiraiLogger.setDefaultLoggerCreator {
+            SynchronizedStdoutLogger(it)
+        }
+    }
+
     companion object {
         init {
             Exception() // create a exception to load relevant classes to estimate invocation time of test cases more accurately.

+ 6 - 5
mirai-core/src/commonTest/kotlin/test/printing.kt

@@ -1,10 +1,10 @@
 /*
- * Copyright 2019-2020 Mamoe Technologies and contributors.
+ * Copyright 2019-2021 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.
+ * 此源代码的使用受 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
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
  */
 
 @file:Suppress("NOTHING_TO_INLINE")
@@ -14,12 +14,13 @@ package test
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.Input
 import kotlinx.io.core.readAvailable
+import net.mamoe.mirai.IMirai
 import net.mamoe.mirai.utils.*
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 
 
-val DebugLogger: MiraiLogger = MiraiLogger.create("Packet Debug")
+val DebugLogger: MiraiLogger = MiraiLogger.Factory.create(IMirai::class, "Packet Debug")
 
 internal inline fun ByteArray.debugPrintThis(name: String): ByteArray {
     DebugLogger.debug(name + "=" + this.toUHexString())