From afa848e204b3c73eb15ba64f3df28024709cb4a4 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 02:11:07 +0100 Subject: [PATCH 01/48] feat: implement error logging system for player connection failures --- .../slne/surf/core/api/common/SurfCoreApi.kt | 13 +++++ .../core/api/common/error/SurfCoreError.kt | 7 +++ .../surf/core/fallback/DatabaseLoaderImpl.kt | 8 +-- .../SurfCoreErrorLoggingRepository.kt | 55 +++++++++++++++++++ .../SurfCoreErrorLoggingServiceImpl.kt | 25 +++++++++ .../fallback/table/SurfCoreErrorLogsTable.kt | 11 ++++ .../surf/core/core/common/SurfCoreApiImpl.kt | 21 +++++++ .../player/SurfCoreErrorLoggingService.kt | 28 ++++++++++ .../core/paper/api/SurfCoreApiPaperImpl.kt | 30 ++++++++++ .../paper/listener/PlayerConnectListener.kt | 18 +++++- .../velocity/api/SurfCoreApiVelocityImpl.kt | 30 ++++++++++ 11 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt create mode 100644 surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt index bde65d5..d405d25 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt @@ -1,5 +1,6 @@ package dev.slne.surf.core.api.common +import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.api.common.event.SurfEvent import dev.slne.surf.core.api.common.player.SurfPlayer import dev.slne.surf.core.api.common.server.SurfServer @@ -28,6 +29,18 @@ interface SurfCoreApi { fun fireEvent(event: SurfEvent) fun subscribe(eventClass: KClass, handler: (SurfEvent) -> Unit) + fun logError(playerUuid: UUID, code: String, message: String) + fun logError(playerUUid: UUID, message: String): String + + suspend fun logErrorAwaiting( + playerUuid: UUID, + code: String, + message: String, + server: String + ): SurfCoreError + + suspend fun logErrorAwaiting(playerUuid: UUID, message: String, server: String): SurfCoreError + fun sendPlayer(player: SurfPlayer, server: SurfServer) /** diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt new file mode 100644 index 0000000..1ee13ae --- /dev/null +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt @@ -0,0 +1,7 @@ +package dev.slne.surf.core.api.common.error + +data class SurfCoreError( + val code: String, + val message: String, + val server: String +) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt index 8b87056..58700fc 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt @@ -2,10 +2,7 @@ package dev.slne.surf.core.fallback import com.google.auto.service.AutoService import dev.slne.surf.core.core.common.database.DatabaseLoader -import dev.slne.surf.core.fallback.table.SurfPlayerIpAddressHistoryTable -import dev.slne.surf.core.fallback.table.SurfPlayerNameHistoryTable -import dev.slne.surf.core.fallback.table.SurfPlayerTable -import dev.slne.surf.core.fallback.table.SurfPlayerTexturesHistoryTable +import dev.slne.surf.core.fallback.table.* import dev.slne.surf.database.DatabaseApi import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.SchemaUtils import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction @@ -23,7 +20,8 @@ class DatabaseLoaderImpl : DatabaseLoader, Services.Fallback { SurfPlayerTable, SurfPlayerNameHistoryTable, SurfPlayerIpAddressHistoryTable, - SurfPlayerTexturesHistoryTable + SurfPlayerTexturesHistoryTable, + SurfCoreErrorLogsTable ) } } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt new file mode 100644 index 0000000..cf8ff38 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -0,0 +1,55 @@ +package dev.slne.surf.core.fallback.repository + +import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction +import dev.slne.surf.surfapi.core.api.util.toObjectList +import it.unimi.dsi.fastutil.objects.ObjectList +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import java.util.* + +val surfCoreErrorLoggingRepository = SurfCoreErrorLoggingRepository() + +class SurfCoreErrorLoggingRepository { + suspend fun logError( + playerUuid: UUID, + code: String, + message: String, + server: String, + ): SurfCoreError = suspendTransaction { + SurfCoreErrorLogsTable.insert { + it[SurfCoreErrorLogsTable.playerUuid] = playerUuid + it[SurfCoreErrorLogsTable.errorCode] = code + it[SurfCoreErrorLogsTable.errorMessage] = message + it[SurfCoreErrorLogsTable.server] = server + } + + SurfCoreError(code, message, server) + } + + suspend fun getErrors(playerUuid: UUID): ObjectList = suspendTransaction { + SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.playerUuid eq playerUuid) + .map { + SurfCoreError( + code = it[SurfCoreErrorLogsTable.errorCode], + message = it[SurfCoreErrorLogsTable.errorMessage], + server = it[SurfCoreErrorLogsTable.server], + ) + }.toList().toObjectList() + } + + suspend fun getError(code: String): SurfCoreError? = suspendTransaction { + SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.errorCode eq code).map { + SurfCoreError( + code = it[SurfCoreErrorLogsTable.errorCode], + message = it[SurfCoreErrorLogsTable.errorMessage], + server = it[SurfCoreErrorLogsTable.server], + ) + }.firstOrNull() + } +} \ No newline at end of file diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt new file mode 100644 index 0000000..a34bff2 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt @@ -0,0 +1,25 @@ +package dev.slne.surf.core.fallback.service + +import com.google.auto.service.AutoService +import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.core.common.player.SurfCoreErrorLoggingService +import dev.slne.surf.core.fallback.repository.surfCoreErrorLoggingRepository +import it.unimi.dsi.fastutil.objects.ObjectList +import net.kyori.adventure.util.Services +import java.util.* + +@AutoService(SurfCoreErrorLoggingService::class) +class SurfCoreErrorLoggingServiceImpl : SurfCoreErrorLoggingService, Services.Fallback { + override suspend fun logError( + playerUuid: UUID, + code: String, + message: String, + server: String + ): SurfCoreError = surfCoreErrorLoggingRepository.logError(playerUuid, code, message, server) + + override suspend fun getErrors(playerUuid: UUID): ObjectList = + surfCoreErrorLoggingRepository.getErrors(playerUuid) + + override suspend fun getError(code: String): SurfCoreError? = + surfCoreErrorLoggingRepository.getError(code) +} \ No newline at end of file diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt new file mode 100644 index 0000000..d1fe4e4 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.core.fallback.table + +import dev.slne.surf.database.columns.nativeUuid +import dev.slne.surf.database.table.AuditableLongIdTable + +object SurfCoreErrorLogsTable : AuditableLongIdTable("surf_player_error_logs") { + val playerUuid = nativeUuid("player_uuid") + val errorCode = varchar("error_code", 7) + val errorMessage = text("error_message") + val server = varchar("server", 255) +} \ No newline at end of file diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt index aeefacf..925b9a4 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt @@ -1,10 +1,12 @@ package dev.slne.surf.core.core.common import dev.slne.surf.core.api.common.SurfCoreApi +import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.api.common.event.SurfEvent import dev.slne.surf.core.api.common.player.SurfPlayer import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.core.common.event.surfEventBus +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.redis.event.SurfPlayerMessageRedisEvent import dev.slne.surf.core.core.common.redis.redisApi @@ -59,4 +61,23 @@ abstract class SurfCoreApiImpl : SurfCoreApi { override fun fireEvent(event: SurfEvent) { surfEventBus.fire(event) } + + override suspend fun logErrorAwaiting( + playerUuid: UUID, + code: String, + message: String, + server: String + ): SurfCoreError = surfCoreErrorLoggingService.logError(playerUuid, code, message, server) + + override suspend fun logErrorAwaiting( + playerUuid: UUID, + message: String, + server: String + ): SurfCoreError = + surfCoreErrorLoggingService.logError( + playerUuid, + surfCoreErrorLoggingService.generateCode(), + message, + server + ) } \ No newline at end of file diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt new file mode 100644 index 0000000..310f8c4 --- /dev/null +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt @@ -0,0 +1,28 @@ +package dev.slne.surf.core.core.common.player + +import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.surfapi.core.api.util.random +import dev.slne.surf.surfapi.core.api.util.requiredService +import it.unimi.dsi.fastutil.objects.ObjectList +import java.util.* + +val surfCoreErrorLoggingService = requiredService() + +interface SurfCoreErrorLoggingService { + suspend fun logError( + playerUuid: UUID, + code: String, + message: String, + server: String + ): SurfCoreError + + suspend fun getErrors(playerUuid: UUID): ObjectList + suspend fun getError(code: String): SurfCoreError? + + fun generateCode(): String { + val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + return (1..7) + .map { chars[random.nextInt(chars.length)] } + .joinToString("") + } +} \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt index b17c771..d9cab69 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt @@ -1,5 +1,6 @@ package dev.slne.surf.core.paper.api +import com.github.shynixn.mccoroutine.folia.launch import com.google.auto.service.AutoService import dev.slne.surf.core.api.common.SurfCoreApi import dev.slne.surf.core.api.common.player.SurfPlayer @@ -7,14 +8,43 @@ import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.api.common.server.type.SurfServerType import dev.slne.surf.core.api.paper.util.bukkitPlayer import dev.slne.surf.core.core.common.SurfCoreApiImpl +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService +import dev.slne.surf.core.paper.plugin import dev.slne.surf.core.paper.surfServerConfig import dev.slne.surf.surfapi.bukkit.api.surfBukkitApi import net.kyori.adventure.util.Services +import java.util.* @AutoService(SurfCoreApi::class) class SurfCoreApiPaperImpl : SurfCoreApiImpl(), Services.Fallback { override fun getCurrentServerName() = surfServerConfig.serverName override fun getCurrentServerCategory() = surfServerConfig.serverCategory + override fun logError(playerUuid: UUID, code: String, message: String) { + plugin.launch { + surfCoreErrorLoggingService.logError( + playerUuid, + code, + message, + SurfServer.current().name + ) + } + } + + override fun logError(playerUUid: UUID, message: String): String { + val code = surfCoreErrorLoggingService.generateCode() + + plugin.launch { + surfCoreErrorLoggingService.logError( + playerUUid, + code, + message, + SurfServer.current().name + ) + } + + return code + } + override fun sendPlayer( player: SurfPlayer, server: SurfServer diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index 2521264..7ca8c8b 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -1,5 +1,6 @@ package dev.slne.surf.core.paper.listener +import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.util.formatMillis import dev.slne.surf.core.paper.permission.PermissionRegistry @@ -20,7 +21,11 @@ object PlayerConnectListener : Listener { val surfPlayer = surfPlayerService.findPlayerByUuid(player.uniqueId) if (surfPlayer == null) { - player.kick(buildDisconnectComponent(), PlayerKickEvent.Cause.UNKNOWN) + val code = surfCoreApi.logError( + player.uniqueId, + "Failed to load player data on PlayerClientLoadedWorldEvent" + ) + player.kick(buildDisconnectComponent(code), PlayerKickEvent.Cause.UNKNOWN) return } @@ -56,11 +61,18 @@ object PlayerConnectListener : Listener { val surfPlayer = surfPlayerService.findPlayerByUuid(event.uniqueId) if (surfPlayer == null) { - event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, buildDisconnectComponent()) + val code = surfCoreApi.logError( + event.uniqueId, + "Failed to load player data on AsyncPlayerPreLoginEvent" + ) + event.disallow( + AsyncPlayerPreLoginEvent.Result.KICK_OTHER, + buildDisconnectComponent(code) + ) } } - private fun buildDisconnectComponent() = buildText { + private fun buildDisconnectComponent(code: String) = buildText { appendNewline(2) primary("CASTCRAFTER") appendNewline() diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt index 651d952..da6fb84 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt @@ -1,20 +1,50 @@ package dev.slne.surf.core.velocity.api +import com.github.shynixn.mccoroutine.velocity.launch import com.google.auto.service.AutoService import dev.slne.surf.core.api.common.SurfCoreApi import dev.slne.surf.core.api.common.player.SurfPlayer import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.api.common.server.type.SurfServerType import dev.slne.surf.core.core.common.SurfCoreApiImpl +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import dev.slne.surf.core.velocity.plugin import dev.slne.surf.core.velocity.surfServerConfig import net.kyori.adventure.util.Services +import java.util.* import kotlin.jvm.optionals.getOrNull @AutoService(SurfCoreApi::class) class SurfCoreApiVelocityImpl : SurfCoreApiImpl(), Services.Fallback { override fun getCurrentServerName() = surfServerConfig.serverName override fun getCurrentServerCategory() = surfServerConfig.serverCategory + + override fun logError(playerUuid: UUID, code: String, message: String) { + plugin.pluginContainer.launch { + surfCoreErrorLoggingService.logError( + playerUuid, + code, + message, + SurfServer.current().name + ) + } + } + + override fun logError(playerUUid: UUID, message: String): String { + val code = surfCoreErrorLoggingService.generateCode() + + plugin.pluginContainer.launch { + surfCoreErrorLoggingService.logError( + playerUUid, + code, + message, + SurfServer.current().name + ) + } + + return code + } + override fun sendPlayer( player: SurfPlayer, server: SurfServer From 51841dffde4ffa10c71d6dbbed80689f66a0cac2 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 02:12:09 +0100 Subject: [PATCH 02/48] feat: update error logging to use dynamic error code in PlayerConnectListener --- .../dev/slne/surf/core/paper/listener/PlayerConnectListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index 7ca8c8b..677046e 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -80,7 +80,7 @@ object PlayerConnectListener : Listener { appendNewline(2) error("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN.") appendNewline() - error("Code: 1921186215185: 1") // surf-core in A1Z26-Cipher + Error Code 1 + error("Code: $code") appendNewline(3) spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") appendNewline() From 38cd40ee60d765c9960db7fb8067090c8aa5cea9 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 10:53:41 +0100 Subject: [PATCH 03/48] feat: add core error command for viewing player errors and error codes --- .../core/api/common/error/SurfCoreError.kt | 5 +- .../SurfCoreErrorLoggingRepository.kt | 7 +- .../fallback/table/SurfCoreErrorLogsTable.kt | 6 +- .../dev/slne/surf/core/paper/PaperMain.kt | 1 + .../core/paper/command/CoreErrorCommand.kt | 99 +++++++++++++++++++ .../paper/listener/PlayerConnectListener.kt | 2 +- .../paper/permission/PermissionRegistry.kt | 1 + 7 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt index 1ee13ae..7711b5b 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt @@ -1,7 +1,10 @@ package dev.slne.surf.core.api.common.error +import java.time.OffsetDateTime + data class SurfCoreError( val code: String, val message: String, - val server: String + val server: String, + val timestamp: OffsetDateTime ) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index cf8ff38..6ccd790 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -11,6 +11,7 @@ import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList +import java.time.OffsetDateTime import java.util.* val surfCoreErrorLoggingRepository = SurfCoreErrorLoggingRepository() @@ -22,14 +23,16 @@ class SurfCoreErrorLoggingRepository { message: String, server: String, ): SurfCoreError = suspendTransaction { + val timestamp = OffsetDateTime.now() SurfCoreErrorLogsTable.insert { it[SurfCoreErrorLogsTable.playerUuid] = playerUuid it[SurfCoreErrorLogsTable.errorCode] = code it[SurfCoreErrorLogsTable.errorMessage] = message it[SurfCoreErrorLogsTable.server] = server + it[SurfCoreErrorLogsTable.timestamp] = timestamp } - SurfCoreError(code, message, server) + SurfCoreError(code, message, server, timestamp) } suspend fun getErrors(playerUuid: UUID): ObjectList = suspendTransaction { @@ -39,6 +42,7 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], + timestamp = it[SurfCoreErrorLogsTable.timestamp] ) }.toList().toObjectList() } @@ -49,6 +53,7 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], + timestamp = it[SurfCoreErrorLogsTable.timestamp] ) }.firstOrNull() } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt index d1fe4e4..a710e8a 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt @@ -1,11 +1,13 @@ package dev.slne.surf.core.fallback.table import dev.slne.surf.database.columns.nativeUuid -import dev.slne.surf.database.table.AuditableLongIdTable +import dev.slne.surf.database.columns.time.offsetDateTime +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTable -object SurfCoreErrorLogsTable : AuditableLongIdTable("surf_player_error_logs") { +object SurfCoreErrorLogsTable : LongIdTable("surf_player_error_logs") { val playerUuid = nativeUuid("player_uuid") val errorCode = varchar("error_code", 7) val errorMessage = text("error_message") val server = varchar("server", 255) + val timestamp = offsetDateTime("timestamp") } \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index 4d50933..229d25a 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -37,6 +37,7 @@ class PaperMain : SuspendingJavaPlugin() { networkServerCommand() networkBroadcastCommand() networkSendCommand() + coreErrorCommand() PlayerConnectListener.register() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt new file mode 100644 index 0000000..c03a075 --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -0,0 +1,99 @@ +package dev.slne.surf.core.paper.command + +import dev.jorel.commandapi.kotlindsl.commandTree +import dev.jorel.commandapi.kotlindsl.getValue +import dev.jorel.commandapi.kotlindsl.literalArgument +import dev.jorel.commandapi.kotlindsl.stringArgument +import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService +import dev.slne.surf.core.paper.permission.PermissionRegistry +import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend +import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import dev.slne.surf.surfapi.core.api.messages.adventure.clickRunsCommand +import dev.slne.surf.surfapi.core.api.messages.adventure.sendText +import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination +import dev.slne.surf.surfapi.core.api.service.PlayerLookupService +import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter + +fun coreErrorCommand() = commandTree("coreerror") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR) + literalArgument("viewPlayer") { + stringArgument("playerName") { + anyExecutorSuspend { executor, args -> + val playerName: String by args + val player = PlayerLookupService.getUuid(playerName) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Spieler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + val error = surfCoreErrorLoggingService.getErrors(player) + + executor.sendText { + appendNewline() + append(pagination.renderComponent(error)) + } + } + } + } + literalArgument("viewCode") { + stringArgument("code") { + anyExecutorSuspend { executor, args -> + val code: String by args + val error = surfCoreErrorLoggingService.getError(code) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Fehler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + executor.sendText { + appendNewline() + darkSpacer("*" + "-".repeat(20) + "*") + appendNewline() + appendNewline() + appendInfoPrefix() + info("Fehlercode: ") + variableValue(error.code) + appendNewline() + appendInfoPrefix() + info("Nachricht: ") + variableValue(error.message) + appendNewline() + appendInfoPrefix() + info("Server: ") + variableValue(error.server) + appendNewline() + appendInfoPrefix() + info("Zeitpunkt: ") + variableValue(error.timestamp.format(dateTimeFormatter)) + appendNewline() + appendNewline() + darkSpacer("*" + "-".repeat(20) + "*") + } + } + } + } +} + +private val pagination = Pagination { + title { primary("Fehler-Übersicht") } + rowRenderer { row, _ -> + listOf( + buildText { + appendInfoPrefix() + variableKey("Fehlercode: ") + variableValue(row.code) + appendSpace() + spacer("(${row.timestamp.format(dateTimeFormatter)})") + clickRunsCommand("/coreerror viewCode ${row.code}") + hoverEvent(buildText { + spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") + }) + } + ) + } +} \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index 677046e..7377543 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -80,7 +80,7 @@ object PlayerConnectListener : Listener { appendNewline(2) error("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN.") appendNewline() - error("Code: $code") + error("Code: #$code") appendNewline(3) spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") appendNewline() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt index 70cf4e5..66d75ab 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt @@ -19,6 +19,7 @@ object PermissionRegistry : PermissionRegistry() { val COMMAND_INFO_SERVER = create("$BASE_COMMAND.info.server") val COMMAND_CORE = create("$BASE_COMMAND.core") + val COMMAND_CORE_ERROR = create("$BASE_COMMAND.coreerror") val JOIN_WHERE_AM_I = create("$BASE.whereamijoin") } \ No newline at end of file From a0504ed09dec33679c6f7496c31f7faacb6f4205 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 11:15:05 +0100 Subject: [PATCH 04/48] feat: enhance error logging and disconnect message handling in authentication flow --- .../slne/surf/core/api/common/SurfCoreApi.kt | 1 + .../surf/core/core/common/SurfCoreApiImpl.kt | 2 ++ .../core/core/common/util/core-common-util.kt | 26 ++++++++++++++++++- .../paper/listener/PlayerConnectListener.kt | 22 +++++----------- .../velocity/auth/AuthenticationListener.kt | 26 ++++++++++++------- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt index d405d25..7482999 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt @@ -40,6 +40,7 @@ interface SurfCoreApi { ): SurfCoreError suspend fun logErrorAwaiting(playerUuid: UUID, message: String, server: String): SurfCoreError + fun generateCode(): String fun sendPlayer(player: SurfPlayer, server: SurfServer) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt index 925b9a4..e8302ec 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt @@ -80,4 +80,6 @@ abstract class SurfCoreApiImpl : SurfCoreApi { message, server ) + + override fun generateCode() = surfCoreErrorLoggingService.generateCode() } \ No newline at end of file diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt index 2f3435c..52525bd 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt @@ -1,5 +1,8 @@ package dev.slne.surf.core.core.common.util +import dev.slne.surf.surfapi.core.api.messages.adventure.appendNewline +import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneId @@ -29,4 +32,25 @@ fun Long.formatTimeMillis(): String = fun OffsetDateTime.formatDateTime( formatter: DateTimeFormatter = dateTimeFormatter -): String = this.format(formatter) \ No newline at end of file +): String = this.format(formatter) + + +fun SurfComponentBuilder.renderDisconnectMessage( + titleReason: String, + reason: SurfComponentBuilder.() -> Unit, + footer: SurfComponentBuilder.() -> Unit = { spacer("Sollte das Problem weiterhin bestehen, wende dich bitte an den Support.") } +) = + buildText { + appendNewline(2) + primary("CASTCRAFTER") + appendNewline() + primary("COMMUNITY SERVER") + appendNewline(2) + error(titleReason) + appendNewline(3) + append(reason) + appendNewline() + append(footer) + appendNewline(2) + primary("discord.gg/castcrafter") + } \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index 7377543..e4483b5 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -3,8 +3,8 @@ package dev.slne.surf.core.paper.listener import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.util.formatMillis +import dev.slne.surf.core.core.common.util.renderDisconnectMessage import dev.slne.surf.core.paper.permission.PermissionRegistry -import dev.slne.surf.surfapi.core.api.messages.adventure.appendNewline import dev.slne.surf.surfapi.core.api.messages.adventure.buildText import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard import dev.slne.surf.surfapi.core.api.messages.adventure.sendText @@ -73,19 +73,11 @@ object PlayerConnectListener : Listener { } private fun buildDisconnectComponent(code: String) = buildText { - appendNewline(2) - primary("CASTCRAFTER") - appendNewline() - primary("COMMUNITY SERVER") - appendNewline(2) - error("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN.") - appendNewline() - error("Code: #$code") - appendNewline(3) - spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") - appendNewline() - spacer("Sollte das Problem weiterhin bestehen, wende dich bitte an den Support.") - appendNewline(2) - primary("discord.gg/castcrafter") + renderDisconnectMessage("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN", { + spacer("Code: ") + error("#$code") + appendNewline() + spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") + }) } } \ No newline at end of file diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt index 7c65cb3..e702fb3 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt @@ -10,6 +10,8 @@ import com.velocitypowered.api.event.connection.PreTransferEvent import com.velocitypowered.api.event.player.CookieReceiveEvent import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent import com.velocitypowered.api.network.HandshakeIntent +import dev.slne.surf.core.api.common.surfCoreApi +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import dev.slne.surf.core.velocity.plugin import dev.slne.surf.core.velocity.velocityCoreConfigManager import dev.slne.surf.surfapi.core.api.messages.CommonComponents @@ -23,15 +25,6 @@ object AuthenticationListener { @Subscribe fun onLogin(event: LoginEvent, continuation: Continuation) { if (event.player.handshakeIntent != HandshakeIntent.TRANSFER) { - if (event.player.hasPermission("surf.core.bypass")) { - continuation.resume() - event.player.sendText { - appendWarningPrefix() - error("Du verbindest dich über eine inoffizielle Methode, hast aber die Berechtigung zum Umgehen.") - } - return - } - event.player.virtualHost.getOrNull()?.hostString?.let { domain -> if (velocityCoreConfigManager.config.blockedDomains.any { it.equals( @@ -40,6 +33,21 @@ object AuthenticationListener { ) }) { continuation.resume() + if (event.player.hasPermission("surf.core.bypass")) { + event.player.sendText { + appendWarningPrefix() + error("Du verbindest dich über eine inoffizielle Methode, hast aber die Berechtigung zum Umgehen.") + } + return + } + + val code = surfCoreErrorLoggingService.generateCode() + val errorCode = surfCoreApi.logError( + event.player.uniqueId, + code, + "Blocked connection from domain: $domain" + ) + event.result = ResultedEvent.ComponentResult.denied(buildText { CommonComponents.renderDisconnectMessage( this, From de1eb9465f25945fc7ce3335e6c7b0fcdba8aeaf Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 11:24:17 +0100 Subject: [PATCH 05/48] feat: refactor disconnect message handling to include error codes in AuthenticationListener and PlayerConnectListener --- .../core/core/common/util/core-common-util.kt | 44 +++++++++++++------ .../paper/listener/PlayerConnectListener.kt | 7 +-- .../velocity/auth/AuthenticationListener.kt | 18 ++++---- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt index 52525bd..67e354f 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt @@ -1,7 +1,7 @@ package dev.slne.surf.core.core.common.util import dev.slne.surf.surfapi.core.api.messages.adventure.appendNewline -import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder import java.time.Instant import java.time.OffsetDateTime @@ -35,22 +35,38 @@ fun OffsetDateTime.formatDateTime( ): String = this.format(formatter) -fun SurfComponentBuilder.renderDisconnectMessage( +fun SurfComponentBuilder.renderErrorCodeDisconnectMessage( + errorCode: String, titleReason: String, reason: SurfComponentBuilder.() -> Unit, footer: SurfComponentBuilder.() -> Unit = { spacer("Sollte das Problem weiterhin bestehen, wende dich bitte an den Support.") } ) = - buildText { - appendNewline(2) - primary("CASTCRAFTER") + renderDisconnectMessage(titleReason, { + spacer("Fehlercode: ") + append { + error("#$errorCode") + clickCopiesToClipboard(errorCode) + } appendNewline() - primary("COMMUNITY SERVER") - appendNewline(2) - error(titleReason) - appendNewline(3) append(reason) - appendNewline() - append(footer) - appendNewline(2) - primary("discord.gg/castcrafter") - } \ No newline at end of file + }, footer) + + +fun SurfComponentBuilder.renderDisconnectMessage( + titleReason: String, + reason: SurfComponentBuilder.() -> Unit, + footer: SurfComponentBuilder.() -> Unit = { spacer("Sollte das Problem weiterhin bestehen, wende dich bitte an den Support.") } +) = append { + appendNewline(2) + primary("CASTCRAFTER") + appendNewline() + primary("COMMUNITY SERVER") + appendNewline(2) + error(titleReason) + appendNewline(3) + append(reason) + appendNewline() + append(footer) + appendNewline(2) + primary("discord.gg/castcrafter") +} diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index e4483b5..cc7fdc3 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -3,7 +3,7 @@ package dev.slne.surf.core.paper.listener import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.util.formatMillis -import dev.slne.surf.core.core.common.util.renderDisconnectMessage +import dev.slne.surf.core.core.common.util.renderErrorCodeDisconnectMessage import dev.slne.surf.core.paper.permission.PermissionRegistry import dev.slne.surf.surfapi.core.api.messages.adventure.buildText import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard @@ -73,10 +73,7 @@ object PlayerConnectListener : Listener { } private fun buildDisconnectComponent(code: String) = buildText { - renderDisconnectMessage("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN", { - spacer("Code: ") - error("#$code") - appendNewline() + renderErrorCodeDisconnectMessage(code, "DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN", { spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") }) } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt index e702fb3..9018eec 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt @@ -12,9 +12,9 @@ import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent import com.velocitypowered.api.network.HandshakeIntent import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService +import dev.slne.surf.core.core.common.util.renderErrorCodeDisconnectMessage import dev.slne.surf.core.velocity.plugin import dev.slne.surf.core.velocity.velocityCoreConfigManager -import dev.slne.surf.surfapi.core.api.messages.CommonComponents import dev.slne.surf.surfapi.core.api.messages.adventure.buildText import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.surfapi.core.api.util.random @@ -49,15 +49,13 @@ object AuthenticationListener { ) event.result = ResultedEvent.ComponentResult.denied(buildText { - CommonComponents.renderDisconnectMessage( - this, - "INOFFIZIELLE DOMAIN", - { - error("Bitte verbinde dich über die offizielle Domain.") - appendNewline() - variableValue("castcrafter.de") - } - ) + renderErrorCodeDisconnectMessage(code, "INOFFIZIELLE DOMAIN", { + error("Bitte verbinde dich über die offizielle Domain.") + appendNewline() + variableValue("castcrafter.de") + }, { + spacer("Wenn du denkst, dass dies ein Fehler ist, kontaktiere den Support mit diesem Code.") + }) }) return } From ded7a4369906b4d4b791de04dd7b6e09876d1e26 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 11:29:08 +0100 Subject: [PATCH 06/48] feat: add error logging for failed authentication attempts in AuthentificationService --- .../slne/surf/core/velocity/auth/AuthentificationService.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt index 9600e31..ec28d64 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt @@ -1,6 +1,7 @@ package dev.slne.surf.core.velocity.auth import com.velocitypowered.api.event.Continuation +import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.core.common.redis.redisApi import dev.slne.surf.surfapi.core.api.messages.adventure.key import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf @@ -20,6 +21,7 @@ class AuthentificationService { val storedHash = authMap.remove(uuid) if (storedHash == null) { + surfCoreApi.logError(uuid, "Failed authentication attempt: no stored hash") println("[connection] Failed to authenticate player $uuid: no stored hash") return false } @@ -27,6 +29,7 @@ class AuthentificationService { val tokenHash = hash(token) if (!storedHash.contentEquals(tokenHash)) { + surfCoreApi.logError(uuid, "Failed authentication attempt: invalid token") println("[connection] Failed to authenticate player $uuid: invalid token") return false } From a4f943d8c5330473f4bb51f91f0157102262fd89 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 11:33:59 +0100 Subject: [PATCH 07/48] feat: rename AuthentificationService to AuthenticationService and update references --- .../dev/slne/surf/core/velocity/VelocityMain.kt | 4 ++-- .../core/velocity/auth/AuthenticationListener.kt | 16 ++++++++-------- ...cationService.kt => AuthenticationService.kt} | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) rename surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/{AuthentificationService.kt => AuthenticationService.kt} (95%) diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt index ae7b099..dfa59de 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt @@ -23,7 +23,7 @@ import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.redis.redisLoader import dev.slne.surf.core.core.common.server.surfServerService import dev.slne.surf.core.velocity.auth.AuthenticationListener -import dev.slne.surf.core.velocity.auth.authentificationService +import dev.slne.surf.core.velocity.auth.authenticationService import dev.slne.surf.core.velocity.config.VelocityCoreConfigManager import dev.slne.surf.core.velocity.listener.ConnectionListener import dev.slne.surf.core.velocity.listener.VelocityServerListener @@ -49,7 +49,7 @@ class VelocityMain @Inject constructor( redisLoader.load() surfPlayerService.init() surfServerService.init() - authentificationService.init() + authenticationService.init() redisLoader.connect(VelocitySurfPlayerRedisListener) val server = SurfServer( diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt index 9018eec..46bdc8b 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt @@ -65,23 +65,23 @@ object AuthenticationListener { return } - authentificationService.continuations[event.player.uniqueId] = continuation - event.player.requestCookie(authentificationService.key) + authenticationService.continuations[event.player.uniqueId] = continuation + event.player.requestCookie(authenticationService.key) } @Subscribe fun onCookieReceive(event: CookieReceiveEvent) { - if (event.originalKey != authentificationService.key) return + if (event.originalKey != authenticationService.key) return event.originalData?.let { - authentificationService.authenticate(event.player.uniqueId, it) + authenticationService.authenticate(event.player.uniqueId, it) } } @Subscribe fun onInitialServer(event: PlayerChooseInitialServerEvent) { val player = event.player - val lastServerName = authentificationService.lastServerMap.remove(player.uniqueId) ?: return + val lastServerName = authenticationService.lastServerMap.remove(player.uniqueId) ?: return plugin.proxy.getServer(lastServerName).getOrNull()?.let { event.setInitialServer(it) @@ -93,12 +93,12 @@ object AuthenticationListener { val player = event.player() val token = generateToken() - authentificationService.preTransfer(player.uniqueId, token) + authenticationService.preTransfer(player.uniqueId, token) - player.storeCookie(authentificationService.key, token) + player.storeCookie(authenticationService.key, token) player.currentServer.getOrNull()?.serverInfo?.name?.let { - authentificationService.lastServerMap[player.uniqueId] = it + authenticationService.lastServerMap[player.uniqueId] = it } } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationService.kt similarity index 95% rename from surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt rename to surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationService.kt index ec28d64..5eaf867 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthentificationService.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationService.kt @@ -9,9 +9,9 @@ import java.security.MessageDigest import java.util.* import kotlin.time.Duration.Companion.seconds -val authentificationService = AuthentificationService() +val authenticationService = AuthenticationService() -class AuthentificationService { +class AuthenticationService { val authMap = redisApi.createSyncMap("surf-core:authentification", 5.seconds) val lastServerMap = redisApi.createSyncMap("surf-core:last-server", 5.seconds) val continuations = mutableObject2ObjectMapOf() From ef2f04fdf403171270351cb2e74f128eb7751a37 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 11:35:57 +0100 Subject: [PATCH 08/48] feat: fix typo in logError method parameter name in SurfCoreApi --- .../main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt index 7482999..bd5419d 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt @@ -30,7 +30,7 @@ interface SurfCoreApi { fun subscribe(eventClass: KClass, handler: (SurfEvent) -> Unit) fun logError(playerUuid: UUID, code: String, message: String) - fun logError(playerUUid: UUID, message: String): String + fun logError(playerUuid: UUID, message: String): String suspend fun logErrorAwaiting( playerUuid: UUID, From 14208a3a84e72c35e74e47e03d93940fda6b01a3 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 13:12:35 +0100 Subject: [PATCH 09/48] feat: add player UUID to error logging and enhance error details in SurfCoreError --- .../kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt | 2 +- .../dev/slne/surf/core/api/common/error/SurfCoreError.kt | 2 ++ .../fallback/repository/SurfCoreErrorLoggingRepository.kt | 4 +++- .../dev/slne/surf/core/paper/command/CoreErrorCommand.kt | 8 ++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt index bd5419d..f2c66b2 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt @@ -41,7 +41,7 @@ interface SurfCoreApi { suspend fun logErrorAwaiting(playerUuid: UUID, message: String, server: String): SurfCoreError fun generateCode(): String - + fun sendPlayer(player: SurfPlayer, server: SurfServer) /** diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt index 7711b5b..0549e1b 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt @@ -1,8 +1,10 @@ package dev.slne.surf.core.api.common.error import java.time.OffsetDateTime +import java.util.* data class SurfCoreError( + val playerUuid: UUID, val code: String, val message: String, val server: String, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 6ccd790..12bcbbd 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -32,13 +32,14 @@ class SurfCoreErrorLoggingRepository { it[SurfCoreErrorLogsTable.timestamp] = timestamp } - SurfCoreError(code, message, server, timestamp) + SurfCoreError(playerUuid, code, message, server, timestamp) } suspend fun getErrors(playerUuid: UUID): ObjectList = suspendTransaction { SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.playerUuid eq playerUuid) .map { SurfCoreError( + playerUuid = it[SurfCoreErrorLogsTable.playerUuid], code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], @@ -50,6 +51,7 @@ class SurfCoreErrorLoggingRepository { suspend fun getError(code: String): SurfCoreError? = suspendTransaction { SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.errorCode eq code).map { SurfCoreError( + playerUuid = it[SurfCoreErrorLogsTable.playerUuid], code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index c03a075..10cd698 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -9,6 +9,7 @@ import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import dev.slne.surf.core.paper.permission.PermissionRegistry import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard import dev.slne.surf.surfapi.core.api.messages.adventure.clickRunsCommand import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination @@ -56,6 +57,13 @@ fun coreErrorCommand() = commandTree("coreerror") { appendNewline() appendNewline() appendInfoPrefix() + info("Spieler: ") + append { + variableValue(error.playerUuid.toString()) + clickCopiesToClipboard(error.playerUuid.toString()) + } + appendNewline() + appendInfoPrefix() info("Fehlercode: ") variableValue(error.code) appendNewline() From eb50f72c01ab895e61ccf08629f46e61281bfd4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:26:26 +0000 Subject: [PATCH 10/48] Initial plan From 505a8981b5995d99e70521b063b06d6acec23a54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:30:12 +0000 Subject: [PATCH 11/48] feat: enhance error logging with stacktrace, location, and deduplication Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../core/api/common/error/SurfCoreError.kt | 6 +- .../SurfCoreErrorLoggingRepository.kt | 86 ++++++++++++++++--- .../SurfCoreErrorLoggingServiceImpl.kt | 13 ++- .../fallback/table/SurfCoreErrorLogsTable.kt | 10 +++ .../core/common/error/GlobalErrorHandler.kt | 71 +++++++++++++++ .../player/SurfCoreErrorLoggingService.kt | 34 +++++++- .../dev/slne/surf/core/paper/PaperMain.kt | 4 + .../core/paper/command/CoreErrorCommand.kt | 22 ++++- .../slne/surf/core/velocity/VelocityMain.kt | 4 + 9 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt index 0549e1b..393be07 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt @@ -8,5 +8,9 @@ data class SurfCoreError( val code: String, val message: String, val server: String, - val timestamp: OffsetDateTime + val timestamp: OffsetDateTime, + val stacktrace: String, + val location: String, + val lastOccurred: OffsetDateTime, + val occurrenceCount: Int ) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 12bcbbd..dfcc1f8 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -2,10 +2,13 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.update import dev.slne.surf.surfapi.core.api.util.toObjectList import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.flow.firstOrNull @@ -22,17 +25,72 @@ class SurfCoreErrorLoggingRepository { code: String, message: String, server: String, + stacktrace: String, + location: String, ): SurfCoreError = suspendTransaction { val timestamp = OffsetDateTime.now() - SurfCoreErrorLogsTable.insert { - it[SurfCoreErrorLogsTable.playerUuid] = playerUuid - it[SurfCoreErrorLogsTable.errorCode] = code - it[SurfCoreErrorLogsTable.errorMessage] = message - it[SurfCoreErrorLogsTable.server] = server - it[SurfCoreErrorLogsTable.timestamp] = timestamp - } + + // Check if a similar error already exists (same message, location, and server) + val existingError = SurfCoreErrorLogsTable.selectAll() + .where( + (SurfCoreErrorLogsTable.errorMessage eq message) and + (SurfCoreErrorLogsTable.location eq location) and + (SurfCoreErrorLogsTable.server eq server) + ) + .map { + SurfCoreError( + playerUuid = it[SurfCoreErrorLogsTable.playerUuid], + code = it[SurfCoreErrorLogsTable.errorCode], + message = it[SurfCoreErrorLogsTable.errorMessage], + server = it[SurfCoreErrorLogsTable.server], + timestamp = it[SurfCoreErrorLogsTable.timestamp], + stacktrace = it[SurfCoreErrorLogsTable.stacktrace], + location = it[SurfCoreErrorLogsTable.location], + lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], + occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] + ) + } + .firstOrNull() + + if (existingError != null) { + // Update the existing error with new lastOccurred and increment count + SurfCoreErrorLogsTable.update( + where = { SurfCoreErrorLogsTable.errorCode eq existingError.code } + ) { + it[SurfCoreErrorLogsTable.lastOccurred] = timestamp + it[SurfCoreErrorLogsTable.occurrenceCount] = existingError.occurrenceCount + 1 + } + + return@suspendTransaction existingError.copy( + lastOccurred = timestamp, + occurrenceCount = existingError.occurrenceCount + 1 + ) + } else { + // Insert new error + SurfCoreErrorLogsTable.insert { + it[SurfCoreErrorLogsTable.playerUuid] = playerUuid + it[SurfCoreErrorLogsTable.errorCode] = code + it[SurfCoreErrorLogsTable.errorMessage] = message + it[SurfCoreErrorLogsTable.server] = server + it[SurfCoreErrorLogsTable.timestamp] = timestamp + it[SurfCoreErrorLogsTable.stacktrace] = stacktrace + it[SurfCoreErrorLogsTable.location] = location + it[SurfCoreErrorLogsTable.lastOccurred] = timestamp + it[SurfCoreErrorLogsTable.occurrenceCount] = 1 + } - SurfCoreError(playerUuid, code, message, server, timestamp) + return@suspendTransaction SurfCoreError( + playerUuid, + code, + message, + server, + timestamp, + stacktrace, + location, + timestamp, + 1 + ) + } } suspend fun getErrors(playerUuid: UUID): ObjectList = suspendTransaction { @@ -43,7 +101,11 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp] + timestamp = it[SurfCoreErrorLogsTable.timestamp], + stacktrace = it[SurfCoreErrorLogsTable.stacktrace], + location = it[SurfCoreErrorLogsTable.location], + lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], + occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] ) }.toList().toObjectList() } @@ -55,7 +117,11 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp] + timestamp = it[SurfCoreErrorLogsTable.timestamp], + stacktrace = it[SurfCoreErrorLogsTable.stacktrace], + location = it[SurfCoreErrorLogsTable.location], + lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], + occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] ) }.firstOrNull() } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt index a34bff2..ccaa7bb 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt @@ -14,8 +14,17 @@ class SurfCoreErrorLoggingServiceImpl : SurfCoreErrorLoggingService, Services.Fa playerUuid: UUID, code: String, message: String, - server: String - ): SurfCoreError = surfCoreErrorLoggingRepository.logError(playerUuid, code, message, server) + server: String, + stacktrace: String, + location: String + ): SurfCoreError = surfCoreErrorLoggingRepository.logError( + playerUuid, + code, + message, + server, + stacktrace, + location + ) override suspend fun getErrors(playerUuid: UUID): ObjectList = surfCoreErrorLoggingRepository.getErrors(playerUuid) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt index a710e8a..46e9930 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt @@ -10,4 +10,14 @@ object SurfCoreErrorLogsTable : LongIdTable("surf_player_error_logs") { val errorMessage = text("error_message") val server = varchar("server", 255) val timestamp = offsetDateTime("timestamp") + val stacktrace = text("stacktrace") + val location = varchar("location", 500) + val lastOccurred = offsetDateTime("last_occurred") + val occurrenceCount = integer("occurrence_count").default(1) + + init { + // Create a unique index on the combination of message, location, and server + // This helps identify duplicate errors + index(isUnique = false, errorMessage, location, server) + } } \ No newline at end of file diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt new file mode 100644 index 0000000..ff2520f --- /dev/null +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -0,0 +1,71 @@ +package dev.slne.surf.core.core.common.error + +import dev.slne.surf.core.core.common.config.surfServerConfig +import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.* +import java.util.logging.Level +import java.util.logging.Logger + +/** + * Global error handler for uncaught exceptions. + * Logs errors to the database and prevents duplicate logging. + */ +object GlobalErrorHandler { + private val logger = Logger.getLogger(GlobalErrorHandler::class.java.name) + private val errorScope = CoroutineScope(Dispatchers.IO) + + /** + * Installs the global uncaught exception handler for all threads. + */ + fun install() { + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + handleError(thread.name, throwable, UUID(0, 0)) // System UUID for non-player errors + } + logger.info("Global error handler installed") + } + + /** + * Creates a coroutine exception handler that logs errors to the database. + */ + fun createCoroutineExceptionHandler(playerUuid: UUID = UUID(0, 0)): CoroutineExceptionHandler { + return CoroutineExceptionHandler { context, throwable -> + handleError(context.toString(), throwable, playerUuid) + } + } + + /** + * Handles an error by logging it to the database. + */ + private fun handleError(source: String, throwable: Throwable, playerUuid: UUID) { + try { + logger.log(Level.SEVERE, "Uncaught exception from $source", throwable) + + // Log to database asynchronously + errorScope.launch { + try { + surfCoreErrorLoggingService.logError( + playerUuid = playerUuid, + throwable = throwable, + server = surfServerConfig.serverName + ) + } catch (e: Exception) { + logger.log(Level.SEVERE, "Failed to log error to database", e) + } + } + } catch (e: Exception) { + // Fallback if error handling itself fails + logger.log(Level.SEVERE, "Error handler failed", e) + } + } + + /** + * Logs an error manually with a specific player UUID. + */ + fun logError(throwable: Throwable, playerUuid: UUID = UUID(0, 0)) { + handleError("Manual", throwable, playerUuid) + } +} diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt index 310f8c4..0ab8466 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt @@ -13,8 +13,23 @@ interface SurfCoreErrorLoggingService { playerUuid: UUID, code: String, message: String, - server: String + server: String, + stacktrace: String, + location: String ): SurfCoreError + + suspend fun logError( + playerUuid: UUID, + throwable: Throwable, + server: String + ): SurfCoreError { + val code = generateCode() + val message = throwable.message ?: throwable::class.java.simpleName + val stacktrace = throwable.stackTraceToString() + val location = extractLocation(throwable) + + return logError(playerUuid, code, message, server, stacktrace, location) + } suspend fun getErrors(playerUuid: UUID): ObjectList suspend fun getError(code: String): SurfCoreError? @@ -25,4 +40,21 @@ interface SurfCoreErrorLoggingService { .map { chars[random.nextInt(chars.length)] } .joinToString("") } + + fun extractLocation(throwable: Throwable): String { + val stackTrace = throwable.stackTrace + if (stackTrace.isEmpty()) { + return "Unknown" + } + + // Find the first stack trace element that's not from Java/Kotlin internals + val relevantElement = stackTrace.firstOrNull { element -> + !element.className.startsWith("java.") && + !element.className.startsWith("kotlin.") && + !element.className.startsWith("sun.") && + !element.className.startsWith("jdk.") + } ?: stackTrace.first() + + return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" + } } \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index 229d25a..4457770 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -6,6 +6,7 @@ import dev.slne.surf.core.api.common.event.SurfServerStoppingEvent import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.api.common.server.state.SurfServerState import dev.slne.surf.core.core.common.database.databaseLoader +import dev.slne.surf.core.core.common.error.GlobalErrorHandler import dev.slne.surf.core.core.common.event.surfEventBus import dev.slne.surf.core.core.common.redis.redisLoader import dev.slne.surf.core.core.common.server.surfServerService @@ -21,6 +22,9 @@ val plugin get() = JavaPlugin.getPlugin(PaperMain::class.java) class PaperMain : SuspendingJavaPlugin() { override fun onLoad() { + // Install global error handler early + GlobalErrorHandler.install() + surfEventBus.registerListener(SurfServerEventListener) } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 10cd698..48b4771 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -76,9 +76,29 @@ fun coreErrorCommand() = commandTree("coreerror") { variableValue(error.server) appendNewline() appendInfoPrefix() - info("Zeitpunkt: ") + info("Ort: ") + variableValue(error.location) + appendNewline() + appendInfoPrefix() + info("Erstmals aufgetreten: ") variableValue(error.timestamp.format(dateTimeFormatter)) appendNewline() + appendInfoPrefix() + info("Zuletzt aufgetreten: ") + variableValue(error.lastOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Anzahl: ") + variableValue(error.occurrenceCount.toString()) + appendNewline() + appendInfoPrefix() + info("Stacktrace: ") + appendNewline() + spacer(error.stacktrace.take(500)) + if (error.stacktrace.length > 500) { + spacer("... (gekürzt)") + } + appendNewline() appendNewline() darkSpacer("*" + "-".repeat(20) + "*") } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt index dfa59de..0d314b8 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt @@ -18,6 +18,7 @@ import dev.slne.surf.core.api.common.server.state.SurfServerState import dev.slne.surf.core.api.common.server.type.SurfServerType import dev.slne.surf.core.core.common.config.SurfServerConfigHolder import dev.slne.surf.core.core.common.database.databaseLoader +import dev.slne.surf.core.core.common.error.GlobalErrorHandler import dev.slne.surf.core.core.common.event.surfEventBus import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.redis.redisLoader @@ -43,6 +44,9 @@ class VelocityMain @Inject constructor( ) { init { suspendingPluginContainer.initialize(this) + + // Install global error handler early + GlobalErrorHandler.install() instance = this surfServerConfigHolder = SurfServerConfigHolder(dataPath) From 68beaf78045af91eb2484eb1397c08679fb09d6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:33:53 +0000 Subject: [PATCH 12/48] feat: implement separate system error logging infrastructure Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../core/api/common/error/SurfCoreError.kt | 6 +- .../surf/core/api/common/error/SystemError.kt | 18 +++ .../surf/core/fallback/DatabaseLoaderImpl.kt | 3 +- .../SurfCoreErrorLoggingRepository.kt | 86 ++--------- .../repository/SystemErrorRepository.kt | 138 ++++++++++++++++++ .../SurfCoreErrorLoggingServiceImpl.kt | 13 +- .../service/SystemErrorServiceImpl.kt | 24 +++ .../fallback/table/SurfCoreErrorLogsTable.kt | 10 -- .../core/fallback/table/SystemErrorTable.kt | 24 +++ .../core/common/error/GlobalErrorHandler.kt | 19 +-- .../core/common/error/SystemErrorService.kt | 70 +++++++++ .../player/SurfCoreErrorLoggingService.kt | 34 +---- .../dev/slne/surf/core/paper/PaperMain.kt | 1 + .../core/paper/command/CoreErrorCommand.kt | 22 +-- .../core/paper/command/SystemErrorCommand.kt | 123 ++++++++++++++++ 15 files changed, 423 insertions(+), 168 deletions(-) create mode 100644 surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt create mode 100644 surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt create mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt index 393be07..0549e1b 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt @@ -8,9 +8,5 @@ data class SurfCoreError( val code: String, val message: String, val server: String, - val timestamp: OffsetDateTime, - val stacktrace: String, - val location: String, - val lastOccurred: OffsetDateTime, - val occurrenceCount: Int + val timestamp: OffsetDateTime ) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt new file mode 100644 index 0000000..792080e --- /dev/null +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt @@ -0,0 +1,18 @@ +package dev.slne.surf.core.api.common.error + +import java.time.OffsetDateTime + +/** + * Represents a system error that occurred during execution. + * This is separate from player-specific errors (SurfCoreError). + */ +data class SystemError( + val id: Long, + val errorMessage: String, + val stacktrace: String, + val location: String, + val server: String, + val firstOccurred: OffsetDateTime, + val lastOccurred: OffsetDateTime, + val occurrenceCount: Int +) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt index 58700fc..ec56a5b 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt @@ -21,7 +21,8 @@ class DatabaseLoaderImpl : DatabaseLoader, Services.Fallback { SurfPlayerNameHistoryTable, SurfPlayerIpAddressHistoryTable, SurfPlayerTexturesHistoryTable, - SurfCoreErrorLogsTable + SurfCoreErrorLogsTable, + SystemErrorTable ) } } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index dfcc1f8..12bcbbd 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -2,13 +2,10 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.update import dev.slne.surf.surfapi.core.api.util.toObjectList import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.flow.firstOrNull @@ -25,72 +22,17 @@ class SurfCoreErrorLoggingRepository { code: String, message: String, server: String, - stacktrace: String, - location: String, ): SurfCoreError = suspendTransaction { val timestamp = OffsetDateTime.now() - - // Check if a similar error already exists (same message, location, and server) - val existingError = SurfCoreErrorLogsTable.selectAll() - .where( - (SurfCoreErrorLogsTable.errorMessage eq message) and - (SurfCoreErrorLogsTable.location eq location) and - (SurfCoreErrorLogsTable.server eq server) - ) - .map { - SurfCoreError( - playerUuid = it[SurfCoreErrorLogsTable.playerUuid], - code = it[SurfCoreErrorLogsTable.errorCode], - message = it[SurfCoreErrorLogsTable.errorMessage], - server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp], - stacktrace = it[SurfCoreErrorLogsTable.stacktrace], - location = it[SurfCoreErrorLogsTable.location], - lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], - occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] - ) - } - .firstOrNull() - - if (existingError != null) { - // Update the existing error with new lastOccurred and increment count - SurfCoreErrorLogsTable.update( - where = { SurfCoreErrorLogsTable.errorCode eq existingError.code } - ) { - it[SurfCoreErrorLogsTable.lastOccurred] = timestamp - it[SurfCoreErrorLogsTable.occurrenceCount] = existingError.occurrenceCount + 1 - } - - return@suspendTransaction existingError.copy( - lastOccurred = timestamp, - occurrenceCount = existingError.occurrenceCount + 1 - ) - } else { - // Insert new error - SurfCoreErrorLogsTable.insert { - it[SurfCoreErrorLogsTable.playerUuid] = playerUuid - it[SurfCoreErrorLogsTable.errorCode] = code - it[SurfCoreErrorLogsTable.errorMessage] = message - it[SurfCoreErrorLogsTable.server] = server - it[SurfCoreErrorLogsTable.timestamp] = timestamp - it[SurfCoreErrorLogsTable.stacktrace] = stacktrace - it[SurfCoreErrorLogsTable.location] = location - it[SurfCoreErrorLogsTable.lastOccurred] = timestamp - it[SurfCoreErrorLogsTable.occurrenceCount] = 1 - } - - return@suspendTransaction SurfCoreError( - playerUuid, - code, - message, - server, - timestamp, - stacktrace, - location, - timestamp, - 1 - ) + SurfCoreErrorLogsTable.insert { + it[SurfCoreErrorLogsTable.playerUuid] = playerUuid + it[SurfCoreErrorLogsTable.errorCode] = code + it[SurfCoreErrorLogsTable.errorMessage] = message + it[SurfCoreErrorLogsTable.server] = server + it[SurfCoreErrorLogsTable.timestamp] = timestamp } + + SurfCoreError(playerUuid, code, message, server, timestamp) } suspend fun getErrors(playerUuid: UUID): ObjectList = suspendTransaction { @@ -101,11 +43,7 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp], - stacktrace = it[SurfCoreErrorLogsTable.stacktrace], - location = it[SurfCoreErrorLogsTable.location], - lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], - occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] + timestamp = it[SurfCoreErrorLogsTable.timestamp] ) }.toList().toObjectList() } @@ -117,11 +55,7 @@ class SurfCoreErrorLoggingRepository { code = it[SurfCoreErrorLogsTable.errorCode], message = it[SurfCoreErrorLogsTable.errorMessage], server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp], - stacktrace = it[SurfCoreErrorLogsTable.stacktrace], - location = it[SurfCoreErrorLogsTable.location], - lastOccurred = it[SurfCoreErrorLogsTable.lastOccurred], - occurrenceCount = it[SurfCoreErrorLogsTable.occurrenceCount] + timestamp = it[SurfCoreErrorLogsTable.timestamp] ) }.firstOrNull() } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt new file mode 100644 index 0000000..82e0bb4 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt @@ -0,0 +1,138 @@ +package dev.slne.surf.core.fallback.repository + +import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.core.fallback.table.SystemErrorTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.update +import dev.slne.surf.surfapi.core.api.util.toObjectList +import it.unimi.dsi.fastutil.objects.ObjectList +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import java.time.OffsetDateTime + +val systemErrorRepository = SystemErrorRepository() + +/** + * Repository for managing system-wide errors. + * Handles error logging with automatic deduplication. + */ +class SystemErrorRepository { + /** + * Logs a system error. If a similar error already exists (same message, location, and server), + * it updates the lastOccurred timestamp and increments the occurrence count. + */ + suspend fun logError( + message: String, + stacktrace: String, + location: String, + server: String + ): SystemError = suspendTransaction { + val now = OffsetDateTime.now() + + // Check if a similar error already exists + val existingError = SystemErrorTable.selectAll() + .where( + (SystemErrorTable.errorMessage eq message) and + (SystemErrorTable.location eq location) and + (SystemErrorTable.server eq server) + ) + .map { row -> + SystemError( + id = row[SystemErrorTable.id].value, + errorMessage = row[SystemErrorTable.errorMessage], + stacktrace = row[SystemErrorTable.stacktrace], + location = row[SystemErrorTable.location], + server = row[SystemErrorTable.server], + firstOccurred = row[SystemErrorTable.firstOccurred], + lastOccurred = row[SystemErrorTable.lastOccurred], + occurrenceCount = row[SystemErrorTable.occurrenceCount] + ) + } + .firstOrNull() + + if (existingError != null) { + // Update the existing error with new lastOccurred and increment count + SystemErrorTable.update( + where = { SystemErrorTable.id eq existingError.id } + ) { + it[SystemErrorTable.lastOccurred] = now + it[SystemErrorTable.occurrenceCount] = existingError.occurrenceCount + 1 + it[SystemErrorTable.stacktrace] = stacktrace // Update stacktrace in case it's slightly different + } + + return@suspendTransaction existingError.copy( + lastOccurred = now, + occurrenceCount = existingError.occurrenceCount + 1, + stacktrace = stacktrace + ) + } else { + // Insert new error + val insertResult = SystemErrorTable.insert { + it[SystemErrorTable.errorMessage] = message + it[SystemErrorTable.stacktrace] = stacktrace + it[SystemErrorTable.location] = location + it[SystemErrorTable.server] = server + it[SystemErrorTable.firstOccurred] = now + it[SystemErrorTable.lastOccurred] = now + it[SystemErrorTable.occurrenceCount] = 1 + } + + val id = insertResult[SystemErrorTable.id] + + return@suspendTransaction SystemError( + id = id.value, + errorMessage = message, + stacktrace = stacktrace, + location = location, + server = server, + firstOccurred = now, + lastOccurred = now, + occurrenceCount = 1 + ) + } + } + + /** + * Gets all system errors. + */ + suspend fun getAllErrors(): ObjectList = suspendTransaction { + SystemErrorTable.selectAll() + .map { row -> + SystemError( + id = row[SystemErrorTable.id].value, + errorMessage = row[SystemErrorTable.errorMessage], + stacktrace = row[SystemErrorTable.stacktrace], + location = row[SystemErrorTable.location], + server = row[SystemErrorTable.server], + firstOccurred = row[SystemErrorTable.firstOccurred], + lastOccurred = row[SystemErrorTable.lastOccurred], + occurrenceCount = row[SystemErrorTable.occurrenceCount] + ) + }.toList().toObjectList() + } + + /** + * Gets a specific error by ID. + */ + suspend fun getError(id: Long): SystemError? = suspendTransaction { + SystemErrorTable.selectAll() + .where(SystemErrorTable.id eq id) + .map { row -> + SystemError( + id = row[SystemErrorTable.id].value, + errorMessage = row[SystemErrorTable.errorMessage], + stacktrace = row[SystemErrorTable.stacktrace], + location = row[SystemErrorTable.location], + server = row[SystemErrorTable.server], + firstOccurred = row[SystemErrorTable.firstOccurred], + lastOccurred = row[SystemErrorTable.lastOccurred], + occurrenceCount = row[SystemErrorTable.occurrenceCount] + ) + }.firstOrNull() + } +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt index ccaa7bb..a34bff2 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt @@ -14,17 +14,8 @@ class SurfCoreErrorLoggingServiceImpl : SurfCoreErrorLoggingService, Services.Fa playerUuid: UUID, code: String, message: String, - server: String, - stacktrace: String, - location: String - ): SurfCoreError = surfCoreErrorLoggingRepository.logError( - playerUuid, - code, - message, - server, - stacktrace, - location - ) + server: String + ): SurfCoreError = surfCoreErrorLoggingRepository.logError(playerUuid, code, message, server) override suspend fun getErrors(playerUuid: UUID): ObjectList = surfCoreErrorLoggingRepository.getErrors(playerUuid) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt new file mode 100644 index 0000000..d393183 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt @@ -0,0 +1,24 @@ +package dev.slne.surf.core.fallback.service + +import com.google.auto.service.AutoService +import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.core.core.common.error.SystemErrorService +import dev.slne.surf.core.fallback.repository.systemErrorRepository +import it.unimi.dsi.fastutil.objects.ObjectList +import net.kyori.adventure.util.Services + +@AutoService(SystemErrorService::class) +class SystemErrorServiceImpl : SystemErrorService, Services.Fallback { + override suspend fun logError( + message: String, + stacktrace: String, + location: String, + server: String + ): SystemError = systemErrorRepository.logError(message, stacktrace, location, server) + + override suspend fun getAllErrors(): ObjectList = + systemErrorRepository.getAllErrors() + + override suspend fun getError(id: Long): SystemError? = + systemErrorRepository.getError(id) +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt index 46e9930..a710e8a 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt @@ -10,14 +10,4 @@ object SurfCoreErrorLogsTable : LongIdTable("surf_player_error_logs") { val errorMessage = text("error_message") val server = varchar("server", 255) val timestamp = offsetDateTime("timestamp") - val stacktrace = text("stacktrace") - val location = varchar("location", 500) - val lastOccurred = offsetDateTime("last_occurred") - val occurrenceCount = integer("occurrence_count").default(1) - - init { - // Create a unique index on the combination of message, location, and server - // This helps identify duplicate errors - index(isUnique = false, errorMessage, location, server) - } } \ No newline at end of file diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt new file mode 100644 index 0000000..86e8ec9 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt @@ -0,0 +1,24 @@ +package dev.slne.surf.core.fallback.table + +import dev.slne.surf.database.columns.time.offsetDateTime +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTable + +/** + * Table for storing system-wide errors (not player-specific). + * Tracks errors from threads and coroutines with deduplication support. + */ +object SystemErrorTable : LongIdTable("system_errors") { + val errorMessage = text("error_message") + val stacktrace = text("stacktrace") + val location = varchar("location", 500) + val server = varchar("server", 255) + val firstOccurred = offsetDateTime("first_occurred") + val lastOccurred = offsetDateTime("last_occurred") + val occurrenceCount = integer("occurrence_count").default(1) + + init { + // Create a unique index on the combination of message, location, and server + // This helps identify and deduplicate similar errors + index(isUnique = true, errorMessage, location, server) + } +} diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index ff2520f..2806995 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -1,12 +1,10 @@ package dev.slne.surf.core.core.common.error import dev.slne.surf.core.core.common.config.surfServerConfig -import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.util.* import java.util.logging.Level import java.util.logging.Logger @@ -23,7 +21,7 @@ object GlobalErrorHandler { */ fun install() { Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> - handleError(thread.name, throwable, UUID(0, 0)) // System UUID for non-player errors + handleError(thread.name, throwable) } logger.info("Global error handler installed") } @@ -31,24 +29,23 @@ object GlobalErrorHandler { /** * Creates a coroutine exception handler that logs errors to the database. */ - fun createCoroutineExceptionHandler(playerUuid: UUID = UUID(0, 0)): CoroutineExceptionHandler { + fun createCoroutineExceptionHandler(): CoroutineExceptionHandler { return CoroutineExceptionHandler { context, throwable -> - handleError(context.toString(), throwable, playerUuid) + handleError(context.toString(), throwable) } } /** * Handles an error by logging it to the database. */ - private fun handleError(source: String, throwable: Throwable, playerUuid: UUID) { + private fun handleError(source: String, throwable: Throwable) { try { logger.log(Level.SEVERE, "Uncaught exception from $source", throwable) // Log to database asynchronously errorScope.launch { try { - surfCoreErrorLoggingService.logError( - playerUuid = playerUuid, + systemErrorService.logError( throwable = throwable, server = surfServerConfig.serverName ) @@ -63,9 +60,9 @@ object GlobalErrorHandler { } /** - * Logs an error manually with a specific player UUID. + * Logs an error manually. */ - fun logError(throwable: Throwable, playerUuid: UUID = UUID(0, 0)) { - handleError("Manual", throwable, playerUuid) + fun logError(throwable: Throwable) { + handleError("Manual", throwable) } } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt new file mode 100644 index 0000000..7321391 --- /dev/null +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt @@ -0,0 +1,70 @@ +package dev.slne.surf.core.core.common.error + +import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.surfapi.core.api.util.requiredService +import it.unimi.dsi.fastutil.objects.ObjectList + +val systemErrorService = requiredService() + +/** + * Service for managing system-wide errors. + * This is separate from player-specific error logging. + */ +interface SystemErrorService { + /** + * Logs a system error from a throwable. + * Automatically extracts stacktrace and location information. + */ + suspend fun logError( + throwable: Throwable, + server: String + ): SystemError { + val message = throwable.message ?: throwable::class.java.simpleName + val stacktrace = throwable.stackTraceToString() + val location = extractLocation(throwable) + + return logError(message, stacktrace, location, server) + } + + /** + * Logs a system error with explicit parameters. + */ + suspend fun logError( + message: String, + stacktrace: String, + location: String, + server: String + ): SystemError + + /** + * Gets all system errors. + */ + suspend fun getAllErrors(): ObjectList + + /** + * Gets a specific error by ID. + */ + suspend fun getError(id: Long): SystemError? + + /** + * Extracts the relevant location from a throwable's stack trace. + */ + fun extractLocation(throwable: Throwable): String { + val stackTrace = throwable.stackTrace + if (stackTrace.isEmpty()) { + return "Unknown" + } + + // Find the first stack trace element that's not from Java/Kotlin internals + val relevantElement = stackTrace.firstOrNull { element -> + !element.className.startsWith("java.") && + !element.className.startsWith("kotlin.") && + !element.className.startsWith("sun.") && + !element.className.startsWith("jdk.") && + !element.className.startsWith("org.jetbrains.exposed.") && + !element.className.startsWith("kotlinx.coroutines.") + } ?: stackTrace.first() + + return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" + } +} diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt index 0ab8466..310f8c4 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt @@ -13,23 +13,8 @@ interface SurfCoreErrorLoggingService { playerUuid: UUID, code: String, message: String, - server: String, - stacktrace: String, - location: String - ): SurfCoreError - - suspend fun logError( - playerUuid: UUID, - throwable: Throwable, server: String - ): SurfCoreError { - val code = generateCode() - val message = throwable.message ?: throwable::class.java.simpleName - val stacktrace = throwable.stackTraceToString() - val location = extractLocation(throwable) - - return logError(playerUuid, code, message, server, stacktrace, location) - } + ): SurfCoreError suspend fun getErrors(playerUuid: UUID): ObjectList suspend fun getError(code: String): SurfCoreError? @@ -40,21 +25,4 @@ interface SurfCoreErrorLoggingService { .map { chars[random.nextInt(chars.length)] } .joinToString("") } - - fun extractLocation(throwable: Throwable): String { - val stackTrace = throwable.stackTrace - if (stackTrace.isEmpty()) { - return "Unknown" - } - - // Find the first stack trace element that's not from Java/Kotlin internals - val relevantElement = stackTrace.firstOrNull { element -> - !element.className.startsWith("java.") && - !element.className.startsWith("kotlin.") && - !element.className.startsWith("sun.") && - !element.className.startsWith("jdk.") - } ?: stackTrace.first() - - return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" - } } \ No newline at end of file diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index 4457770..3043c49 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -42,6 +42,7 @@ class PaperMain : SuspendingJavaPlugin() { networkBroadcastCommand() networkSendCommand() coreErrorCommand() + systemErrorCommand() PlayerConnectListener.register() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 48b4771..10cd698 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -76,29 +76,9 @@ fun coreErrorCommand() = commandTree("coreerror") { variableValue(error.server) appendNewline() appendInfoPrefix() - info("Ort: ") - variableValue(error.location) - appendNewline() - appendInfoPrefix() - info("Erstmals aufgetreten: ") + info("Zeitpunkt: ") variableValue(error.timestamp.format(dateTimeFormatter)) appendNewline() - appendInfoPrefix() - info("Zuletzt aufgetreten: ") - variableValue(error.lastOccurred.format(dateTimeFormatter)) - appendNewline() - appendInfoPrefix() - info("Anzahl: ") - variableValue(error.occurrenceCount.toString()) - appendNewline() - appendInfoPrefix() - info("Stacktrace: ") - appendNewline() - spacer(error.stacktrace.take(500)) - if (error.stacktrace.length > 500) { - spacer("... (gekürzt)") - } - appendNewline() appendNewline() darkSpacer("*" + "-".repeat(20) + "*") } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt new file mode 100644 index 0000000..449830a --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt @@ -0,0 +1,123 @@ +package dev.slne.surf.core.paper.command + +import dev.jorel.commandapi.kotlindsl.commandTree +import dev.jorel.commandapi.kotlindsl.getValue +import dev.jorel.commandapi.kotlindsl.literalArgument +import dev.jorel.commandapi.kotlindsl.longArgument +import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.paper.permission.PermissionRegistry +import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend +import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard +import dev.slne.surf.surfapi.core.api.messages.adventure.clickRunsCommand +import dev.slne.surf.surfapi.core.api.messages.adventure.sendText +import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination +import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter + +fun systemErrorCommand() = commandTree("systemerror") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR) + literalArgument("list") { + anyExecutorSuspend { executor, _ -> + val errors = systemErrorService.getAllErrors() + + executor.sendText { + appendNewline() + append(pagination.renderComponent(errors)) + } + } + } + literalArgument("view") { + longArgument("id") { + anyExecutorSuspend { executor, args -> + val id: Long by args + val error = systemErrorService.getError(id) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Fehler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + executor.sendText { + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") + appendNewline() + appendNewline() + appendInfoPrefix() + info("Fehler-ID: ") + variableValue(error.id.toString()) + appendNewline() + appendInfoPrefix() + info("Server: ") + variableValue(error.server) + appendNewline() + appendInfoPrefix() + info("Ort: ") + append { + variableValue(error.location) + clickCopiesToClipboard(error.location) + } + appendNewline() + appendInfoPrefix() + info("Erstmals aufgetreten: ") + variableValue(error.firstOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Zuletzt aufgetreten: ") + variableValue(error.lastOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Anzahl: ") + variableValue(error.occurrenceCount.toString()) + appendNewline() + appendInfoPrefix() + info("Nachricht: ") + appendNewline() + spacer(error.errorMessage.take(200)) + if (error.errorMessage.length > 200) { + spacer("... (gekürzt)") + } + appendNewline() + appendInfoPrefix() + info("Stacktrace: ") + appendNewline() + spacer(error.stacktrace.take(1000)) + if (error.stacktrace.length > 1000) { + appendNewline() + spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") + } + appendNewline() + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") + } + } + } + } +} + +private val pagination = Pagination { + title { primary("System-Fehler Übersicht") } + rowRenderer { row, _ -> + listOf( + buildText { + appendInfoPrefix() + variableKey("ID: ") + variableValue(row.id.toString()) + appendSpace() + spacer("(${row.occurrenceCount}x)") + appendSpace() + spacer("- ${row.location.take(50)}") + if (row.location.length > 50) { + spacer("...") + } + clickRunsCommand("/systemerror view ${row.id}") + hoverEvent(buildText { + spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") + appendNewline() + spacer("Zuletzt: ${row.lastOccurred.format(dateTimeFormatter)}") + }) + } + ) + } +} From 5ed926483844078235b3cd9e810016bc11072033 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:35:59 +0000 Subject: [PATCH 13/48] fix: address code review feedback - lifecycle management and code cleanup Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- SYSTEM_ERROR_LOGGING.md | 199 ++++++++++++++++++ .../core/common/error/GlobalErrorHandler.kt | 21 +- .../core/common/error/SystemErrorService.kt | 46 ++-- .../dev/slne/surf/core/paper/PaperMain.kt | 2 + .../core/paper/command/TestErrorCommand.kt | 82 ++++++++ .../slne/surf/core/velocity/VelocityMain.kt | 1 + 6 files changed, 323 insertions(+), 28 deletions(-) create mode 100644 SYSTEM_ERROR_LOGGING.md create mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt diff --git a/SYSTEM_ERROR_LOGGING.md b/SYSTEM_ERROR_LOGGING.md new file mode 100644 index 0000000..c9d6ae2 --- /dev/null +++ b/SYSTEM_ERROR_LOGGING.md @@ -0,0 +1,199 @@ +# System Error Logging Implementation + +## Overview + +This implementation provides a comprehensive system-wide error logging infrastructure that captures and tracks errors from threads and coroutines. It is completely separate from the existing player-specific error logging system (`SurfCoreError`). + +## Key Features + +1. **Automatic Error Capture** + - Global uncaught exception handler for all threads + - Coroutine exception handlers for async operations + - Automatic stacktrace and location extraction + +2. **Error Deduplication** + - Identical errors (same message, location, and server) are deduplicated + - Tracks first and last occurrence timestamps + - Maintains occurrence count for each unique error + +3. **Comprehensive Error Data** + - Error message + - Full stacktrace + - Location (class, method, and line number) + - Server name + - First and last occurrence timestamps + - Total occurrence count + +## Architecture + +### Data Model + +**SystemError** (`surf-core-api-common`) +- `id: Long` - Unique error identifier +- `errorMessage: String` - Error message +- `stacktrace: String` - Full stacktrace +- `location: String` - Where the error occurred (format: `ClassName.methodName:lineNumber`) +- `server: String` - Server where the error occurred +- `firstOccurred: OffsetDateTime` - Timestamp of first occurrence +- `lastOccurred: OffsetDateTime` - Timestamp of most recent occurrence +- `occurrenceCount: Int` - Number of times this error has occurred + +### Database Layer + +**SystemErrorTable** (`surf-core-backend`) +- Table name: `system_errors` +- Unique index on (errorMessage, location, server) for deduplication +- Uses Exposed ORM with R2DBC for async database operations + +**SystemErrorRepository** (`surf-core-backend`) +- `logError()` - Logs an error with automatic deduplication +- `getAllErrors()` - Retrieves all system errors +- `getError(id)` - Retrieves a specific error by ID + +### Service Layer + +**SystemErrorService** (`surf-core-core-common`) +- Interface for error logging operations +- `logError(throwable, server)` - Logs an error from a Throwable +- `logError(message, stacktrace, location, server)` - Logs an error with explicit parameters +- `getAllErrors()` - Gets all errors +- `getError(id)` - Gets a specific error +- `extractLocation(throwable)` - Extracts relevant location from stacktrace + +**SystemErrorServiceImpl** (`surf-core-backend`) +- Implementation using AutoService for dependency injection + +### Error Handler + +**GlobalErrorHandler** (`surf-core-core-common`) +- Singleton object managing global error handling +- `install()` - Installs the global uncaught exception handler +- `createCoroutineExceptionHandler()` - Creates a CoroutineExceptionHandler +- `logError(throwable)` - Manually logs an error +- Logs errors asynchronously to avoid blocking execution + +## Installation & Setup + +The error handler is automatically installed during plugin initialization: + +### Paper (Bukkit/Spigot/Folia) +```kotlin +// In PaperMain.kt +override fun onLoad() { + GlobalErrorHandler.install() + // ... other initialization +} +``` + +### Velocity +```kotlin +// In VelocityMain.kt +init { + GlobalErrorHandler.install() + // ... other initialization +} +``` + +## Usage + +### Automatic Error Capture + +Errors are automatically captured from: + +1. **Uncaught Thread Exceptions** + ```kotlin + Thread { + throw RuntimeException("This will be automatically logged") + }.start() + ``` + +2. **Coroutine Exceptions** (when using the provided handler) + ```kotlin + launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { + throw RuntimeException("This will be automatically logged") + } + ``` + +### Manual Error Logging + +```kotlin +try { + // Some risky operation + riskyOperation() +} catch (e: Exception) { + GlobalErrorHandler.logError(e) +} +``` + +### Viewing Errors + +Two commands are available for viewing system errors: + +1. **List All Errors** + ``` + /systemerror list + ``` + - Shows paginated list of all system errors + - Displays: ID, occurrence count, and location + - Click to view details + +2. **View Error Details** + ``` + /systemerror view + ``` + - Shows complete error details + - Includes: message, stacktrace, timestamps, occurrence count + +## Testing + +A test command is provided to verify the error logging system: + +``` +/testerror thread - Triggers a thread exception +/testerror coroutine - Triggers a coroutine exception +/testerror manual - Manually logs an error +/testerror duplicate - Triggers duplicate errors to test deduplication +``` + +After triggering errors, use `/systemerror list` to view them. + +## Deduplication Logic + +The system identifies duplicate errors using: +- Error message (exact match) +- Location (class.method:line) +- Server name + +When a duplicate is detected: +- The `lastOccurred` timestamp is updated +- The `occurrenceCount` is incremented +- The stacktrace is updated (in case of minor variations) +- A new row is NOT created + +## Performance Considerations + +1. **Asynchronous Logging** - All database operations are performed asynchronously using coroutines, preventing blocking of the main thread +2. **Efficient Deduplication** - Uses database index for fast duplicate detection +3. **Controlled Stacktrace Size** - Stacktraces are stored as text but truncated in command output + +## Separation from Player Errors + +The system error logging is completely independent from player error logging: + +| Feature | System Errors | Player Errors | +|---------|--------------|---------------| +| Purpose | Track system/application errors | Track player-specific issues | +| Data Class | `SystemError` | `SurfCoreError` | +| Table | `system_errors` | `surf_player_error_logs` | +| Service | `SystemErrorService` | `SurfCoreErrorLoggingService` | +| Command | `/systemerror` | `/coreerror` | +| Deduplication | Automatic | Manual with error codes | + +## Future Enhancements + +Possible improvements: +- Email/webhook notifications for critical errors +- Error rate limiting to prevent log spam +- Automatic error grouping/categorization +- Integration with external monitoring services +- Error resolution workflow (mark as fixed/ignored) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index 2806995..8fbc3c8 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -1,10 +1,7 @@ package dev.slne.surf.core.core.common.error import dev.slne.surf.core.core.common.config.surfServerConfig -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.util.logging.Level import java.util.logging.Logger @@ -14,7 +11,7 @@ import java.util.logging.Logger */ object GlobalErrorHandler { private val logger = Logger.getLogger(GlobalErrorHandler::class.java.name) - private val errorScope = CoroutineScope(Dispatchers.IO) + private val errorScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) /** * Installs the global uncaught exception handler for all threads. @@ -65,4 +62,18 @@ object GlobalErrorHandler { fun logError(throwable: Throwable) { handleError("Manual", throwable) } + + /** + * Shuts down the error logging scope gracefully, waiting for pending operations. + */ + fun shutdown() { + runBlocking { + try { + errorScope.coroutineContext.job.cancelAndJoin() + logger.info("Global error handler shutdown complete") + } catch (e: Exception) { + logger.log(Level.WARNING, "Error during error handler shutdown", e) + } + } + } } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt index 7321391..1db5901 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt @@ -6,6 +6,28 @@ import it.unimi.dsi.fastutil.objects.ObjectList val systemErrorService = requiredService() +/** + * Extracts the relevant location from a throwable's stack trace. + */ +fun extractErrorLocation(throwable: Throwable): String { + val stackTrace = throwable.stackTrace + if (stackTrace.isEmpty()) { + return "Unknown" + } + + // Find the first stack trace element that's not from Java/Kotlin internals + val relevantElement = stackTrace.firstOrNull { element -> + !element.className.startsWith("java.") && + !element.className.startsWith("kotlin.") && + !element.className.startsWith("sun.") && + !element.className.startsWith("jdk.") && + !element.className.startsWith("org.jetbrains.exposed.") && + !element.className.startsWith("kotlinx.coroutines.") + } ?: stackTrace.first() + + return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" +} + /** * Service for managing system-wide errors. * This is separate from player-specific error logging. @@ -21,7 +43,7 @@ interface SystemErrorService { ): SystemError { val message = throwable.message ?: throwable::class.java.simpleName val stacktrace = throwable.stackTraceToString() - val location = extractLocation(throwable) + val location = extractErrorLocation(throwable) return logError(message, stacktrace, location, server) } @@ -45,26 +67,4 @@ interface SystemErrorService { * Gets a specific error by ID. */ suspend fun getError(id: Long): SystemError? - - /** - * Extracts the relevant location from a throwable's stack trace. - */ - fun extractLocation(throwable: Throwable): String { - val stackTrace = throwable.stackTrace - if (stackTrace.isEmpty()) { - return "Unknown" - } - - // Find the first stack trace element that's not from Java/Kotlin internals - val relevantElement = stackTrace.firstOrNull { element -> - !element.className.startsWith("java.") && - !element.className.startsWith("kotlin.") && - !element.className.startsWith("sun.") && - !element.className.startsWith("jdk.") && - !element.className.startsWith("org.jetbrains.exposed.") && - !element.className.startsWith("kotlinx.coroutines.") - } ?: stackTrace.first() - - return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" - } } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index 3043c49..e222470 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -43,6 +43,7 @@ class PaperMain : SuspendingJavaPlugin() { networkSendCommand() coreErrorCommand() systemErrorCommand() + testErrorCommand() // Test command for error logging PlayerConnectListener.register() @@ -58,6 +59,7 @@ class PaperMain : SuspendingJavaPlugin() { surfServerService.changeState(SurfServer.current(), SurfServerState.STOPPING) surfServerService.removeServer(SurfServer.current()) + GlobalErrorHandler.shutdown() redisLoader.disconnect() databaseLoader.disconnect() } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt new file mode 100644 index 0000000..718631f --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt @@ -0,0 +1,82 @@ +package dev.slne.surf.core.paper.command + +import dev.jorel.commandapi.kotlindsl.commandTree +import dev.jorel.commandapi.kotlindsl.literalArgument +import dev.slne.surf.core.core.common.error.GlobalErrorHandler +import dev.slne.surf.core.paper.permission.PermissionRegistry +import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend +import dev.slne.surf.surfapi.core.api.messages.adventure.sendText +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/** + * Test command to demonstrate the error logging system. + * This should be removed or disabled in production. + */ +fun testErrorCommand() = commandTree("testerror") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR) + + literalArgument("thread") { + anyExecutorSuspend { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering thread exception...") + } + + // Trigger an exception in a new thread + Thread { + throw RuntimeException("Test thread exception from /testerror thread") + }.start() + } + } + + literalArgument("coroutine") { + anyExecutorSuspend { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering coroutine exception...") + } + + // Trigger an exception in a coroutine + runBlocking { + launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { + throw RuntimeException("Test coroutine exception from /testerror coroutine") + } + } + } + } + + literalArgument("manual") { + anyExecutorSuspend { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Logging manual error...") + } + + // Manually log an error + try { + @Suppress("DIVISION_BY_ZERO") + 1 / 0 + } catch (e: Exception) { + GlobalErrorHandler.logError(e) + } + } + } + + literalArgument("duplicate") { + anyExecutorSuspend { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering duplicate errors...") + } + + // Trigger the same error multiple times to test deduplication + repeat(3) { + Thread { + Thread.sleep(100L * it) + throw RuntimeException("Duplicate test error - this should only be logged once") + }.start() + } + } + } +} diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt index 0d314b8..6f9d354 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt @@ -105,6 +105,7 @@ class VelocityMain @Inject constructor( }) } + GlobalErrorHandler.shutdown() redisLoader.disconnect() databaseLoader.disconnect() } From bc984a33ed98388d833ce05ded2d04f5a1c2a51f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:36:47 +0000 Subject: [PATCH 14/48] docs: add comprehensive implementation summary Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- IMPLEMENTATION_SUMMARY.md | 218 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 IMPLEMENTATION_SUMMARY.md diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..b2f4b16 --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,218 @@ +# Implementation Summary: System Error Logging + +## What Was Implemented + +A comprehensive system-wide error logging infrastructure that captures, tracks, and deduplicates errors from threads and coroutines across the entire application. + +## Requirements Met + +✅ **Custom error handler on every thread**: Implemented via `Thread.setDefaultUncaughtExceptionHandler()` in `GlobalErrorHandler.install()` + +✅ **Custom error handler in MCCoroutine/coroutine scopes**: Implemented via `GlobalErrorHandler.createCoroutineExceptionHandler()` returning a `CoroutineExceptionHandler` + +✅ **lastOccurred value with deduplication**: Errors with identical message, location, and server are not logged multiple times. Instead, `lastOccurred` timestamp is updated and `occurrenceCount` is incremented. + +✅ **Comprehensive error data**: Each error includes: +- Where it occurred (`location` field: `ClassName.methodName:lineNumber`) +- When it occurred (`firstOccurred` and `lastOccurred` timestamps) +- Full stacktrace (`stacktrace` field) +- Server name +- Occurrence count + +✅ **Separate table, repository, and service structure**: Complete separation from player error logging + +## Architecture + +### Components Created + +1. **Data Model** (`SystemError`) + - Location: `surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt` + - Fields: id, errorMessage, stacktrace, location, server, firstOccurred, lastOccurred, occurrenceCount + +2. **Database Table** (`SystemErrorTable`) + - Location: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt` + - Table name: `system_errors` + - Unique index on (message, location, server) for deduplication + +3. **Repository** (`SystemErrorRepository`) + - Location: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt` + - Methods: logError, getAllErrors, getError + - Automatic deduplication logic + +4. **Service** (`SystemErrorService` / `SystemErrorServiceImpl`) + - Interface: `surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt` + - Implementation: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt` + - Uses AutoService for dependency injection + +5. **Global Error Handler** (`GlobalErrorHandler`) + - Location: `surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt` + - Manages thread and coroutine exception handling + - Asynchronous error logging with proper lifecycle management + +6. **Commands** + - `SystemErrorCommand`: View system errors (`/systemerror list`, `/systemerror view `) + - `TestErrorCommand`: Test error logging (`/testerror thread|coroutine|manual|duplicate`) + +## Key Features + +### Deduplication Algorithm +``` +IF exists error with (same message + same location + same server): + UPDATE lastOccurred = now + INCREMENT occurrenceCount + UPDATE stacktrace +ELSE: + INSERT new error with occurrenceCount = 1 +``` + +### Error Capture Flow +``` +Exception occurs → GlobalErrorHandler intercepts + ↓ +Extract: message, stacktrace, location (from stacktrace) + ↓ +Async: systemErrorService.logError() + ↓ +Repository: Check for duplicate → Update or Insert + ↓ +Database: system_errors table +``` + +### Location Extraction +Stacktrace is analyzed to find the first non-internal frame: +- Skips: `java.*`, `kotlin.*`, `sun.*`, `jdk.*`, `org.jetbrains.exposed.*`, `kotlinx.coroutines.*` +- Returns: `ClassName.methodName:lineNumber` + +## Integration Points + +### Paper Plugin +```kotlin +// PaperMain.kt +override fun onLoad() { + GlobalErrorHandler.install() // Install global handler +} + +override fun onDisable() { + GlobalErrorHandler.shutdown() // Wait for pending logs +} +``` + +### Velocity Plugin +```kotlin +// VelocityMain.kt +init { + GlobalErrorHandler.install() // Install global handler +} + +onProxyShutdown() { + GlobalErrorHandler.shutdown() // Wait for pending logs +} +``` + +## Usage Examples + +### Automatic Thread Exception Capture +```kotlin +Thread { + throw RuntimeException("Error") // Automatically logged +}.start() +``` + +### Automatic Coroutine Exception Capture +```kotlin +launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { + throw RuntimeException("Error") // Automatically logged +} +``` + +### Manual Error Logging +```kotlin +try { + riskyOperation() +} catch (e: Exception) { + GlobalErrorHandler.logError(e) +} +``` + +### Viewing Errors +``` +/systemerror list # List all errors +/systemerror view 123 # View error #123 details +``` + +### Testing +``` +/testerror thread # Test thread exception +/testerror coroutine # Test coroutine exception +/testerror manual # Test manual logging +/testerror duplicate # Test deduplication +``` + +## Performance Characteristics + +- **Asynchronous Logging**: All database operations are non-blocking +- **Supervised Coroutines**: Uses `SupervisorJob` to prevent cascade failures +- **Graceful Shutdown**: Waits for pending operations before shutdown +- **Efficient Deduplication**: Database index on (message, location, server) + +## Separation from Player Errors + +| Aspect | System Errors | Player Errors | +|--------|--------------|---------------| +| Purpose | System/application errors | Player-specific issues | +| Data Class | `SystemError` | `SurfCoreError` | +| Table | `system_errors` | `surf_player_error_logs` | +| Service | `SystemErrorService` | `SurfCoreErrorLoggingService` | +| Command | `/systemerror` | `/coreerror` | +| Handler | `GlobalErrorHandler` | Manual with error codes | + +## Files Modified/Created + +### Created (9 files) +1. `surf-core-api/surf-core-api-common/.../error/SystemError.kt` +2. `surf-core-backend/.../table/SystemErrorTable.kt` +3. `surf-core-backend/.../repository/SystemErrorRepository.kt` +4. `surf-core-backend/.../service/SystemErrorServiceImpl.kt` +5. `surf-core-core/surf-core-core-common/.../error/SystemErrorService.kt` +6. `surf-core-core/surf-core-core-common/.../error/GlobalErrorHandler.kt` +7. `surf-core-paper/.../command/SystemErrorCommand.kt` +8. `surf-core-paper/.../command/TestErrorCommand.kt` +9. `SYSTEM_ERROR_LOGGING.md` (Documentation) + +### Modified (4 files) +1. `surf-core-backend/.../DatabaseLoaderImpl.kt` (Register new table) +2. `surf-core-paper/.../PaperMain.kt` (Install handler, register commands) +3. `surf-core-velocity/.../VelocityMain.kt` (Install handler) +4. This summary document + +## Testing Recommendations + +1. **Basic Error Capture** + - Run `/testerror thread` → Verify error appears in `/systemerror list` + - Run `/testerror coroutine` → Verify error appears in `/systemerror list` + - Run `/testerror manual` → Verify error appears in `/systemerror list` + +2. **Deduplication** + - Run `/testerror duplicate` (triggers same error 3x) + - Run `/systemerror list` → Should see 1 error with count=3 + - Run `/systemerror view ` → Verify occurrenceCount shows 3 + +3. **Data Integrity** + - View error details → Verify stacktrace is present + - View error details → Verify location format is correct + - View error details → Verify timestamps are accurate + +4. **Performance** + - Trigger multiple errors rapidly → Verify no blocking + - Shutdown server → Verify graceful shutdown with no errors + +## Production Considerations + +- ⚠️ **Remove or disable** `/testerror` command in production +- Consider adding error rate limiting to prevent log spam +- Monitor database table size and implement archival strategy +- Consider adding alerting for critical errors + +## Documentation + +Full documentation available in `SYSTEM_ERROR_LOGGING.md` From 582d5d8ce73d25b30f280a4da01a0d93471450a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:49:29 +0000 Subject: [PATCH 15/48] feat: add MCCoroutine exception event listeners for proper coroutine error handling Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- SYSTEM_ERROR_LOGGING.md | 38 ++++++++++-- .../dev/slne/surf/core/paper/PaperMain.kt | 2 + .../listener/MCCoroutineExceptionListener.kt | 60 +++++++++++++++++++ .../slne/surf/core/velocity/VelocityMain.kt | 2 + .../listener/MCCoroutineExceptionListener.kt | 59 ++++++++++++++++++ 5 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt create mode 100644 surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt diff --git a/SYSTEM_ERROR_LOGGING.md b/SYSTEM_ERROR_LOGGING.md index c9d6ae2..98ac179 100644 --- a/SYSTEM_ERROR_LOGGING.md +++ b/SYSTEM_ERROR_LOGGING.md @@ -67,11 +67,18 @@ This implementation provides a comprehensive system-wide error logging infrastru **GlobalErrorHandler** (`surf-core-core-common`) - Singleton object managing global error handling -- `install()` - Installs the global uncaught exception handler +- `install()` - Installs the global uncaught exception handler for non-coroutine threads - `createCoroutineExceptionHandler()` - Creates a CoroutineExceptionHandler - `logError(throwable)` - Manually logs an error - Logs errors asynchronously to avoid blocking execution +**MCCoroutineExceptionListener** (`surf-core-paper` / `surf-core-velocity`) +- Listens to `MCCoroutineExceptionEvent` from MCCoroutine +- Handles exceptions from coroutines managed by MCCoroutine +- Automatically logs exceptions to the system error database +- Cancels the event to prevent duplicate logging +- Skips `CancellationException` as per MCCoroutine best practices + ## Installation & Setup The error handler is automatically installed during plugin initialization: @@ -80,7 +87,13 @@ The error handler is automatically installed during plugin initialization: ```kotlin // In PaperMain.kt override fun onLoad() { - GlobalErrorHandler.install() + GlobalErrorHandler.install() // Handles non-coroutine thread exceptions + // ... other initialization +} + +override fun onEnable() { + // Register MCCoroutine exception listener + MCCoroutineExceptionListener.register() // Handles MCCoroutine exceptions // ... other initialization } ``` @@ -89,7 +102,14 @@ override fun onLoad() { ```kotlin // In VelocityMain.kt init { - GlobalErrorHandler.install() + GlobalErrorHandler.install() // Handles non-coroutine thread exceptions + // ... other initialization +} + +@Subscribe +fun onProxyInitialize(event: ProxyInitializeEvent) { + // Register MCCoroutine exception listener + eventManager.register(this, MCCoroutineExceptionListener) // Handles MCCoroutine exceptions // ... other initialization } ``` @@ -100,14 +120,22 @@ init { Errors are automatically captured from: -1. **Uncaught Thread Exceptions** +1. **Uncaught Thread Exceptions** (via GlobalErrorHandler) ```kotlin Thread { throw RuntimeException("This will be automatically logged") }.start() ``` -2. **Coroutine Exceptions** (when using the provided handler) +2. **MCCoroutine Exceptions** (via MCCoroutineExceptionListener) + ```kotlin + // In a command or listener using MCCoroutine + suspend fun onCommand() { + throw RuntimeException("This will be automatically logged") + } + ``` + +3. **Custom Coroutine Exceptions** (when using the provided handler) ```kotlin launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { throw RuntimeException("This will be automatically logged") diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index e222470..f4b18d4 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -12,6 +12,7 @@ import dev.slne.surf.core.core.common.redis.redisLoader import dev.slne.surf.core.core.common.server.surfServerService import dev.slne.surf.core.paper.command.* import dev.slne.surf.core.paper.event.SurfServerEventListener +import dev.slne.surf.core.paper.listener.MCCoroutineExceptionListener import dev.slne.surf.core.paper.listener.PlayerConnectListener import dev.slne.surf.surfapi.bukkit.api.event.register import kotlinx.coroutines.runBlocking @@ -46,6 +47,7 @@ class PaperMain : SuspendingJavaPlugin() { testErrorCommand() // Test command for error logging PlayerConnectListener.register() + MCCoroutineExceptionListener.register() surfServerService.addServer(SurfServer.current().copy(maxPlayers = Bukkit.getMaxPlayers())) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt new file mode 100644 index 0000000..14604d2 --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -0,0 +1,60 @@ +package dev.slne.surf.core.paper.listener + +import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent +import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.core.common.config.surfServerConfig +import dev.slne.surf.core.paper.plugin +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import java.util.logging.Level +import java.util.logging.Logger +import kotlin.coroutines.cancellation.CancellationException + +/** + * Listens to MCCoroutine exception events and logs them to the system error database. + * This handles exceptions from coroutines managed by MCCoroutine. + */ +object MCCoroutineExceptionListener : Listener { + private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) + + @EventHandler + fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { + // Only handle exceptions from our plugin + if (event.plugin != plugin) { + return + } + + // Skip CancellationException as per MCCoroutine documentation + if (event.exception is CancellationException) { + return + } + + // Cancel the event to prevent MCCoroutine's default logging + // since we're handling it ourselves + event.isCancelled = true + + // Log the exception + logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) + + // Log to database asynchronously with try-catch to prevent infinite stacking + try { + runBlocking { + launch { + try { + systemErrorService.logError( + throwable = event.exception, + server = surfServerConfig.serverName + ) + } catch (e: Exception) { + // Log but don't rethrow to prevent infinite exception loop + logger.log(Level.SEVERE, "Failed to log MCCoroutine exception to database", e) + } + } + } + } catch (e: Exception) { + logger.log(Level.SEVERE, "Error while handling MCCoroutine exception", e) + } + } +} diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt index 6f9d354..db7294d 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt @@ -27,6 +27,7 @@ import dev.slne.surf.core.velocity.auth.AuthenticationListener import dev.slne.surf.core.velocity.auth.authenticationService import dev.slne.surf.core.velocity.config.VelocityCoreConfigManager import dev.slne.surf.core.velocity.listener.ConnectionListener +import dev.slne.surf.core.velocity.listener.MCCoroutineExceptionListener import dev.slne.surf.core.velocity.listener.VelocityServerListener import dev.slne.surf.core.velocity.redis.listener.VelocitySurfPlayerRedisListener import dev.slne.surf.surfapi.core.api.messages.adventure.buildText @@ -82,6 +83,7 @@ class VelocityMain @Inject constructor( eventManager.register(this, ConnectionListener) eventManager.register(this, AuthenticationListener) eventManager.register(this, VelocityServerListener) + eventManager.register(this, MCCoroutineExceptionListener) surfServerService.changeState(SurfServer.current(), SurfServerState.RUNNING) } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt new file mode 100644 index 0000000..56103a1 --- /dev/null +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -0,0 +1,59 @@ +package dev.slne.surf.core.velocity.listener + +import com.github.shynixn.mccoroutine.velocity.MCCoroutineExceptionEvent +import com.velocitypowered.api.event.Subscribe +import dev.slne.surf.core.core.common.config.surfServerConfig +import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.velocity.plugin +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.util.logging.Level +import java.util.logging.Logger +import kotlin.coroutines.cancellation.CancellationException + +/** + * Listens to MCCoroutine exception events and logs them to the system error database. + * This handles exceptions from coroutines managed by MCCoroutine. + */ +object MCCoroutineExceptionListener { + private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) + + @Subscribe + fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { + // Only handle exceptions from our plugin + if (event.plugin != plugin.pluginContainer) { + return + } + + // Skip CancellationException as per MCCoroutine documentation + if (event.exception is CancellationException) { + return + } + + // Cancel the event to prevent MCCoroutine's default logging + // since we're handling it ourselves + event.isCancelled = true + + // Log the exception + logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) + + // Log to database asynchronously with try-catch to prevent infinite stacking + try { + runBlocking { + launch { + try { + systemErrorService.logError( + throwable = event.exception, + server = surfServerConfig.serverName + ) + } catch (e: Exception) { + // Log but don't rethrow to prevent infinite exception loop + logger.log(Level.SEVERE, "Failed to log MCCoroutine exception to database", e) + } + } + } + } catch (e: Exception) { + logger.log(Level.SEVERE, "Error while handling MCCoroutine exception", e) + } + } +} From ee7d519f996db4d9d13df297efba063ddb6fad26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:58:37 +0000 Subject: [PATCH 16/48] docs: remove IMPLEMENTATION_SUMMARY.md and SYSTEM_ERROR_LOGGING.md Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- IMPLEMENTATION_SUMMARY.md | 218 ------------------------------------ SYSTEM_ERROR_LOGGING.md | 227 -------------------------------------- 2 files changed, 445 deletions(-) delete mode 100644 IMPLEMENTATION_SUMMARY.md delete mode 100644 SYSTEM_ERROR_LOGGING.md diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index b2f4b16..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,218 +0,0 @@ -# Implementation Summary: System Error Logging - -## What Was Implemented - -A comprehensive system-wide error logging infrastructure that captures, tracks, and deduplicates errors from threads and coroutines across the entire application. - -## Requirements Met - -✅ **Custom error handler on every thread**: Implemented via `Thread.setDefaultUncaughtExceptionHandler()` in `GlobalErrorHandler.install()` - -✅ **Custom error handler in MCCoroutine/coroutine scopes**: Implemented via `GlobalErrorHandler.createCoroutineExceptionHandler()` returning a `CoroutineExceptionHandler` - -✅ **lastOccurred value with deduplication**: Errors with identical message, location, and server are not logged multiple times. Instead, `lastOccurred` timestamp is updated and `occurrenceCount` is incremented. - -✅ **Comprehensive error data**: Each error includes: -- Where it occurred (`location` field: `ClassName.methodName:lineNumber`) -- When it occurred (`firstOccurred` and `lastOccurred` timestamps) -- Full stacktrace (`stacktrace` field) -- Server name -- Occurrence count - -✅ **Separate table, repository, and service structure**: Complete separation from player error logging - -## Architecture - -### Components Created - -1. **Data Model** (`SystemError`) - - Location: `surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt` - - Fields: id, errorMessage, stacktrace, location, server, firstOccurred, lastOccurred, occurrenceCount - -2. **Database Table** (`SystemErrorTable`) - - Location: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt` - - Table name: `system_errors` - - Unique index on (message, location, server) for deduplication - -3. **Repository** (`SystemErrorRepository`) - - Location: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt` - - Methods: logError, getAllErrors, getError - - Automatic deduplication logic - -4. **Service** (`SystemErrorService` / `SystemErrorServiceImpl`) - - Interface: `surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt` - - Implementation: `surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt` - - Uses AutoService for dependency injection - -5. **Global Error Handler** (`GlobalErrorHandler`) - - Location: `surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt` - - Manages thread and coroutine exception handling - - Asynchronous error logging with proper lifecycle management - -6. **Commands** - - `SystemErrorCommand`: View system errors (`/systemerror list`, `/systemerror view `) - - `TestErrorCommand`: Test error logging (`/testerror thread|coroutine|manual|duplicate`) - -## Key Features - -### Deduplication Algorithm -``` -IF exists error with (same message + same location + same server): - UPDATE lastOccurred = now - INCREMENT occurrenceCount - UPDATE stacktrace -ELSE: - INSERT new error with occurrenceCount = 1 -``` - -### Error Capture Flow -``` -Exception occurs → GlobalErrorHandler intercepts - ↓ -Extract: message, stacktrace, location (from stacktrace) - ↓ -Async: systemErrorService.logError() - ↓ -Repository: Check for duplicate → Update or Insert - ↓ -Database: system_errors table -``` - -### Location Extraction -Stacktrace is analyzed to find the first non-internal frame: -- Skips: `java.*`, `kotlin.*`, `sun.*`, `jdk.*`, `org.jetbrains.exposed.*`, `kotlinx.coroutines.*` -- Returns: `ClassName.methodName:lineNumber` - -## Integration Points - -### Paper Plugin -```kotlin -// PaperMain.kt -override fun onLoad() { - GlobalErrorHandler.install() // Install global handler -} - -override fun onDisable() { - GlobalErrorHandler.shutdown() // Wait for pending logs -} -``` - -### Velocity Plugin -```kotlin -// VelocityMain.kt -init { - GlobalErrorHandler.install() // Install global handler -} - -onProxyShutdown() { - GlobalErrorHandler.shutdown() // Wait for pending logs -} -``` - -## Usage Examples - -### Automatic Thread Exception Capture -```kotlin -Thread { - throw RuntimeException("Error") // Automatically logged -}.start() -``` - -### Automatic Coroutine Exception Capture -```kotlin -launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { - throw RuntimeException("Error") // Automatically logged -} -``` - -### Manual Error Logging -```kotlin -try { - riskyOperation() -} catch (e: Exception) { - GlobalErrorHandler.logError(e) -} -``` - -### Viewing Errors -``` -/systemerror list # List all errors -/systemerror view 123 # View error #123 details -``` - -### Testing -``` -/testerror thread # Test thread exception -/testerror coroutine # Test coroutine exception -/testerror manual # Test manual logging -/testerror duplicate # Test deduplication -``` - -## Performance Characteristics - -- **Asynchronous Logging**: All database operations are non-blocking -- **Supervised Coroutines**: Uses `SupervisorJob` to prevent cascade failures -- **Graceful Shutdown**: Waits for pending operations before shutdown -- **Efficient Deduplication**: Database index on (message, location, server) - -## Separation from Player Errors - -| Aspect | System Errors | Player Errors | -|--------|--------------|---------------| -| Purpose | System/application errors | Player-specific issues | -| Data Class | `SystemError` | `SurfCoreError` | -| Table | `system_errors` | `surf_player_error_logs` | -| Service | `SystemErrorService` | `SurfCoreErrorLoggingService` | -| Command | `/systemerror` | `/coreerror` | -| Handler | `GlobalErrorHandler` | Manual with error codes | - -## Files Modified/Created - -### Created (9 files) -1. `surf-core-api/surf-core-api-common/.../error/SystemError.kt` -2. `surf-core-backend/.../table/SystemErrorTable.kt` -3. `surf-core-backend/.../repository/SystemErrorRepository.kt` -4. `surf-core-backend/.../service/SystemErrorServiceImpl.kt` -5. `surf-core-core/surf-core-core-common/.../error/SystemErrorService.kt` -6. `surf-core-core/surf-core-core-common/.../error/GlobalErrorHandler.kt` -7. `surf-core-paper/.../command/SystemErrorCommand.kt` -8. `surf-core-paper/.../command/TestErrorCommand.kt` -9. `SYSTEM_ERROR_LOGGING.md` (Documentation) - -### Modified (4 files) -1. `surf-core-backend/.../DatabaseLoaderImpl.kt` (Register new table) -2. `surf-core-paper/.../PaperMain.kt` (Install handler, register commands) -3. `surf-core-velocity/.../VelocityMain.kt` (Install handler) -4. This summary document - -## Testing Recommendations - -1. **Basic Error Capture** - - Run `/testerror thread` → Verify error appears in `/systemerror list` - - Run `/testerror coroutine` → Verify error appears in `/systemerror list` - - Run `/testerror manual` → Verify error appears in `/systemerror list` - -2. **Deduplication** - - Run `/testerror duplicate` (triggers same error 3x) - - Run `/systemerror list` → Should see 1 error with count=3 - - Run `/systemerror view ` → Verify occurrenceCount shows 3 - -3. **Data Integrity** - - View error details → Verify stacktrace is present - - View error details → Verify location format is correct - - View error details → Verify timestamps are accurate - -4. **Performance** - - Trigger multiple errors rapidly → Verify no blocking - - Shutdown server → Verify graceful shutdown with no errors - -## Production Considerations - -- ⚠️ **Remove or disable** `/testerror` command in production -- Consider adding error rate limiting to prevent log spam -- Monitor database table size and implement archival strategy -- Consider adding alerting for critical errors - -## Documentation - -Full documentation available in `SYSTEM_ERROR_LOGGING.md` diff --git a/SYSTEM_ERROR_LOGGING.md b/SYSTEM_ERROR_LOGGING.md deleted file mode 100644 index 98ac179..0000000 --- a/SYSTEM_ERROR_LOGGING.md +++ /dev/null @@ -1,227 +0,0 @@ -# System Error Logging Implementation - -## Overview - -This implementation provides a comprehensive system-wide error logging infrastructure that captures and tracks errors from threads and coroutines. It is completely separate from the existing player-specific error logging system (`SurfCoreError`). - -## Key Features - -1. **Automatic Error Capture** - - Global uncaught exception handler for all threads - - Coroutine exception handlers for async operations - - Automatic stacktrace and location extraction - -2. **Error Deduplication** - - Identical errors (same message, location, and server) are deduplicated - - Tracks first and last occurrence timestamps - - Maintains occurrence count for each unique error - -3. **Comprehensive Error Data** - - Error message - - Full stacktrace - - Location (class, method, and line number) - - Server name - - First and last occurrence timestamps - - Total occurrence count - -## Architecture - -### Data Model - -**SystemError** (`surf-core-api-common`) -- `id: Long` - Unique error identifier -- `errorMessage: String` - Error message -- `stacktrace: String` - Full stacktrace -- `location: String` - Where the error occurred (format: `ClassName.methodName:lineNumber`) -- `server: String` - Server where the error occurred -- `firstOccurred: OffsetDateTime` - Timestamp of first occurrence -- `lastOccurred: OffsetDateTime` - Timestamp of most recent occurrence -- `occurrenceCount: Int` - Number of times this error has occurred - -### Database Layer - -**SystemErrorTable** (`surf-core-backend`) -- Table name: `system_errors` -- Unique index on (errorMessage, location, server) for deduplication -- Uses Exposed ORM with R2DBC for async database operations - -**SystemErrorRepository** (`surf-core-backend`) -- `logError()` - Logs an error with automatic deduplication -- `getAllErrors()` - Retrieves all system errors -- `getError(id)` - Retrieves a specific error by ID - -### Service Layer - -**SystemErrorService** (`surf-core-core-common`) -- Interface for error logging operations -- `logError(throwable, server)` - Logs an error from a Throwable -- `logError(message, stacktrace, location, server)` - Logs an error with explicit parameters -- `getAllErrors()` - Gets all errors -- `getError(id)` - Gets a specific error -- `extractLocation(throwable)` - Extracts relevant location from stacktrace - -**SystemErrorServiceImpl** (`surf-core-backend`) -- Implementation using AutoService for dependency injection - -### Error Handler - -**GlobalErrorHandler** (`surf-core-core-common`) -- Singleton object managing global error handling -- `install()` - Installs the global uncaught exception handler for non-coroutine threads -- `createCoroutineExceptionHandler()` - Creates a CoroutineExceptionHandler -- `logError(throwable)` - Manually logs an error -- Logs errors asynchronously to avoid blocking execution - -**MCCoroutineExceptionListener** (`surf-core-paper` / `surf-core-velocity`) -- Listens to `MCCoroutineExceptionEvent` from MCCoroutine -- Handles exceptions from coroutines managed by MCCoroutine -- Automatically logs exceptions to the system error database -- Cancels the event to prevent duplicate logging -- Skips `CancellationException` as per MCCoroutine best practices - -## Installation & Setup - -The error handler is automatically installed during plugin initialization: - -### Paper (Bukkit/Spigot/Folia) -```kotlin -// In PaperMain.kt -override fun onLoad() { - GlobalErrorHandler.install() // Handles non-coroutine thread exceptions - // ... other initialization -} - -override fun onEnable() { - // Register MCCoroutine exception listener - MCCoroutineExceptionListener.register() // Handles MCCoroutine exceptions - // ... other initialization -} -``` - -### Velocity -```kotlin -// In VelocityMain.kt -init { - GlobalErrorHandler.install() // Handles non-coroutine thread exceptions - // ... other initialization -} - -@Subscribe -fun onProxyInitialize(event: ProxyInitializeEvent) { - // Register MCCoroutine exception listener - eventManager.register(this, MCCoroutineExceptionListener) // Handles MCCoroutine exceptions - // ... other initialization -} -``` - -## Usage - -### Automatic Error Capture - -Errors are automatically captured from: - -1. **Uncaught Thread Exceptions** (via GlobalErrorHandler) - ```kotlin - Thread { - throw RuntimeException("This will be automatically logged") - }.start() - ``` - -2. **MCCoroutine Exceptions** (via MCCoroutineExceptionListener) - ```kotlin - // In a command or listener using MCCoroutine - suspend fun onCommand() { - throw RuntimeException("This will be automatically logged") - } - ``` - -3. **Custom Coroutine Exceptions** (when using the provided handler) - ```kotlin - launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { - throw RuntimeException("This will be automatically logged") - } - ``` - -### Manual Error Logging - -```kotlin -try { - // Some risky operation - riskyOperation() -} catch (e: Exception) { - GlobalErrorHandler.logError(e) -} -``` - -### Viewing Errors - -Two commands are available for viewing system errors: - -1. **List All Errors** - ``` - /systemerror list - ``` - - Shows paginated list of all system errors - - Displays: ID, occurrence count, and location - - Click to view details - -2. **View Error Details** - ``` - /systemerror view - ``` - - Shows complete error details - - Includes: message, stacktrace, timestamps, occurrence count - -## Testing - -A test command is provided to verify the error logging system: - -``` -/testerror thread - Triggers a thread exception -/testerror coroutine - Triggers a coroutine exception -/testerror manual - Manually logs an error -/testerror duplicate - Triggers duplicate errors to test deduplication -``` - -After triggering errors, use `/systemerror list` to view them. - -## Deduplication Logic - -The system identifies duplicate errors using: -- Error message (exact match) -- Location (class.method:line) -- Server name - -When a duplicate is detected: -- The `lastOccurred` timestamp is updated -- The `occurrenceCount` is incremented -- The stacktrace is updated (in case of minor variations) -- A new row is NOT created - -## Performance Considerations - -1. **Asynchronous Logging** - All database operations are performed asynchronously using coroutines, preventing blocking of the main thread -2. **Efficient Deduplication** - Uses database index for fast duplicate detection -3. **Controlled Stacktrace Size** - Stacktraces are stored as text but truncated in command output - -## Separation from Player Errors - -The system error logging is completely independent from player error logging: - -| Feature | System Errors | Player Errors | -|---------|--------------|---------------| -| Purpose | Track system/application errors | Track player-specific issues | -| Data Class | `SystemError` | `SurfCoreError` | -| Table | `system_errors` | `surf_player_error_logs` | -| Service | `SystemErrorService` | `SurfCoreErrorLoggingService` | -| Command | `/systemerror` | `/coreerror` | -| Deduplication | Automatic | Manual with error codes | - -## Future Enhancements - -Possible improvements: -- Email/webhook notifications for critical errors -- Error rate limiting to prevent log spam -- Automatic error grouping/categorization -- Integration with external monitoring services -- Error resolution workflow (mark as fixed/ignored) From a14d9bcc4ba226348bb17dbf4b7a7524f70eefd4 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 14:59:04 +0100 Subject: [PATCH 17/48] feat: implement error logging structure with improved server name handling --- .../core/common/error/GlobalErrorHandler.kt | 37 ++++--------------- .../dev/slne/surf/core/paper/PaperMain.kt | 5 +-- .../core/paper/command/TestErrorCommand.kt | 31 +++++++--------- .../listener/MCCoroutineExceptionListener.kt | 32 +++++----------- .../listener/MCCoroutineExceptionListener.kt | 34 ++++++----------- 5 files changed, 44 insertions(+), 95 deletions(-) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index 8fbc3c8..3861a8f 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -1,71 +1,50 @@ package dev.slne.surf.core.core.common.error -import dev.slne.surf.core.core.common.config.surfServerConfig +import dev.slne.surf.core.api.common.server.SurfServer import kotlinx.coroutines.* import java.util.logging.Level import java.util.logging.Logger -/** - * Global error handler for uncaught exceptions. - * Logs errors to the database and prevents duplicate logging. - */ object GlobalErrorHandler { private val logger = Logger.getLogger(GlobalErrorHandler::class.java.name) private val errorScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - /** - * Installs the global uncaught exception handler for all threads. - */ + fun install() { Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> handleError(thread.name, throwable) } logger.info("Global error handler installed") } - - /** - * Creates a coroutine exception handler that logs errors to the database. - */ + fun createCoroutineExceptionHandler(): CoroutineExceptionHandler { return CoroutineExceptionHandler { context, throwable -> handleError(context.toString(), throwable) } } - - /** - * Handles an error by logging it to the database. - */ + private fun handleError(source: String, throwable: Throwable) { try { logger.log(Level.SEVERE, "Uncaught exception from $source", throwable) - - // Log to database asynchronously + errorScope.launch { try { systemErrorService.logError( throwable = throwable, - server = surfServerConfig.serverName + server = SurfServer.current().name ) } catch (e: Exception) { logger.log(Level.SEVERE, "Failed to log error to database", e) } } } catch (e: Exception) { - // Fallback if error handling itself fails logger.log(Level.SEVERE, "Error handler failed", e) } } - - /** - * Logs an error manually. - */ + fun logError(throwable: Throwable) { handleError("Manual", throwable) } - - /** - * Shuts down the error logging scope gracefully, waiting for pending operations. - */ + fun shutdown() { runBlocking { try { diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index f4b18d4..c42a184 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -23,9 +23,8 @@ val plugin get() = JavaPlugin.getPlugin(PaperMain::class.java) class PaperMain : SuspendingJavaPlugin() { override fun onLoad() { - // Install global error handler early GlobalErrorHandler.install() - + surfEventBus.registerListener(SurfServerEventListener) } @@ -44,7 +43,7 @@ class PaperMain : SuspendingJavaPlugin() { networkSendCommand() coreErrorCommand() systemErrorCommand() - testErrorCommand() // Test command for error logging + testErrorCommand() PlayerConnectListener.register() MCCoroutineExceptionListener.register() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt index 718631f..482fd3d 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt @@ -1,5 +1,6 @@ package dev.slne.surf.core.paper.command +import dev.jorel.commandapi.kotlindsl.anyExecutor import dev.jorel.commandapi.kotlindsl.commandTree import dev.jorel.commandapi.kotlindsl.literalArgument import dev.slne.surf.core.core.common.error.GlobalErrorHandler @@ -9,35 +10,30 @@ import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -/** - * Test command to demonstrate the error logging system. - * This should be removed or disabled in production. - */ + fun testErrorCommand() = commandTree("testerror") { withPermission(PermissionRegistry.COMMAND_CORE_ERROR) - + literalArgument("thread") { - anyExecutorSuspend { executor, _ -> + anyExecutor { executor, _ -> executor.sendText { appendSuccessPrefix() success("Triggering thread exception...") } - - // Trigger an exception in a new thread + Thread { throw RuntimeException("Test thread exception from /testerror thread") }.start() } } - + literalArgument("coroutine") { - anyExecutorSuspend { executor, _ -> + anyExecutor { executor, _ -> executor.sendText { appendSuccessPrefix() success("Triggering coroutine exception...") } - - // Trigger an exception in a coroutine + runBlocking { launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { throw RuntimeException("Test coroutine exception from /testerror coroutine") @@ -45,15 +41,14 @@ fun testErrorCommand() = commandTree("testerror") { } } } - + literalArgument("manual") { - anyExecutorSuspend { executor, _ -> + anyExecutor { executor, _ -> executor.sendText { appendSuccessPrefix() success("Logging manual error...") } - - // Manually log an error + try { @Suppress("DIVISION_BY_ZERO") 1 / 0 @@ -62,14 +57,14 @@ fun testErrorCommand() = commandTree("testerror") { } } } - + literalArgument("duplicate") { anyExecutorSuspend { executor, _ -> executor.sendText { appendSuccessPrefix() success("Triggering duplicate errors...") } - + // Trigger the same error multiple times to test deduplication repeat(3) { Thread { diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 14604d2..5a8e7df 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -2,8 +2,7 @@ package dev.slne.surf.core.paper.listener import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent import dev.slne.surf.core.core.common.error.systemErrorService -import dev.slne.surf.core.core.common.config.surfServerConfig -import dev.slne.surf.core.paper.plugin +import dev.slne.surf.core.paper.surfServerConfig import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.bukkit.event.EventHandler @@ -12,33 +11,19 @@ import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException -/** - * Listens to MCCoroutine exception events and logs them to the system error database. - * This handles exceptions from coroutines managed by MCCoroutine. - */ object MCCoroutineExceptionListener : Listener { private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) - + @EventHandler fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { - // Only handle exceptions from our plugin - if (event.plugin != plugin) { - return - } - - // Skip CancellationException as per MCCoroutine documentation if (event.exception is CancellationException) { return } - - // Cancel the event to prevent MCCoroutine's default logging - // since we're handling it ourselves + event.isCancelled = true - - // Log the exception + logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) - - // Log to database asynchronously with try-catch to prevent infinite stacking + try { runBlocking { launch { @@ -48,8 +33,11 @@ object MCCoroutineExceptionListener : Listener { server = surfServerConfig.serverName ) } catch (e: Exception) { - // Log but don't rethrow to prevent infinite exception loop - logger.log(Level.SEVERE, "Failed to log MCCoroutine exception to database", e) + logger.log( + Level.SEVERE, + "Failed to log MCCoroutine exception to database", + e + ) } } } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 56103a1..65b3a3b 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -1,43 +1,28 @@ package dev.slne.surf.core.velocity.listener import com.github.shynixn.mccoroutine.velocity.MCCoroutineExceptionEvent +import com.velocitypowered.api.event.ResultedEvent import com.velocitypowered.api.event.Subscribe -import dev.slne.surf.core.core.common.config.surfServerConfig import dev.slne.surf.core.core.common.error.systemErrorService -import dev.slne.surf.core.velocity.plugin +import dev.slne.surf.core.velocity.surfServerConfig import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException -/** - * Listens to MCCoroutine exception events and logs them to the system error database. - * This handles exceptions from coroutines managed by MCCoroutine. - */ object MCCoroutineExceptionListener { private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) - + @Subscribe fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { - // Only handle exceptions from our plugin - if (event.plugin != plugin.pluginContainer) { - return - } - - // Skip CancellationException as per MCCoroutine documentation if (event.exception is CancellationException) { return } - - // Cancel the event to prevent MCCoroutine's default logging - // since we're handling it ourselves - event.isCancelled = true - - // Log the exception + event.result = ResultedEvent.GenericResult.denied() + logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) - - // Log to database asynchronously with try-catch to prevent infinite stacking + try { runBlocking { launch { @@ -47,8 +32,11 @@ object MCCoroutineExceptionListener { server = surfServerConfig.serverName ) } catch (e: Exception) { - // Log but don't rethrow to prevent infinite exception loop - logger.log(Level.SEVERE, "Failed to log MCCoroutine exception to database", e) + logger.log( + Level.SEVERE, + "Failed to log MCCoroutine exception to database", + e + ) } } } From d22f196fc294d94654f1f8be42a7dd9d41152d62 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 15:06:35 +0100 Subject: [PATCH 18/48] feat: add launch command to trigger exceptions for testing error handling --- .../surf/core/paper/command/TestErrorCommand.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt index 482fd3d..ee219e5 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt @@ -1,10 +1,12 @@ package dev.slne.surf.core.paper.command +import com.github.shynixn.mccoroutine.folia.launch import dev.jorel.commandapi.kotlindsl.anyExecutor import dev.jorel.commandapi.kotlindsl.commandTree import dev.jorel.commandapi.kotlindsl.literalArgument import dev.slne.surf.core.core.common.error.GlobalErrorHandler import dev.slne.surf.core.paper.permission.PermissionRegistry +import dev.slne.surf.core.paper.plugin import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import kotlinx.coroutines.launch @@ -42,6 +44,19 @@ fun testErrorCommand() = commandTree("testerror") { } } + literalArgument("launch") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering exception in launch...") + } + + plugin.launch { + throw RuntimeException("Test exception from plugin.launch in /testerror launch") + } + } + } + literalArgument("manual") { anyExecutor { executor, _ -> executor.sendText { From 043b6243c6f6edd1ed550a7aa35092432a3e34cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:07:29 +0000 Subject: [PATCH 19/48] fix: correct /testerror coroutine to use suspend executor and add plugin check to listener Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../surf/core/paper/command/TestErrorCommand.kt | 11 +++-------- .../listener/MCCoroutineExceptionListener.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt index ee219e5..0806ee3 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt @@ -9,8 +9,6 @@ import dev.slne.surf.core.paper.permission.PermissionRegistry import dev.slne.surf.core.paper.plugin import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend import dev.slne.surf.surfapi.core.api.messages.adventure.sendText -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking fun testErrorCommand() = commandTree("testerror") { @@ -30,17 +28,14 @@ fun testErrorCommand() = commandTree("testerror") { } literalArgument("coroutine") { - anyExecutor { executor, _ -> + anyExecutorSuspend { executor, _ -> executor.sendText { appendSuccessPrefix() success("Triggering coroutine exception...") } - runBlocking { - launch(GlobalErrorHandler.createCoroutineExceptionHandler()) { - throw RuntimeException("Test coroutine exception from /testerror coroutine") - } - } + // This will be caught by MCCoroutineExceptionListener + throw RuntimeException("Test coroutine exception from /testerror coroutine") } } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 5a8e7df..1960607 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -2,6 +2,7 @@ package dev.slne.surf.core.paper.listener import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.paper.plugin import dev.slne.surf.core.paper.surfServerConfig import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -11,19 +12,32 @@ import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException +/** + * Listens to MCCoroutine exception events and logs them to the system error database. + * This handles exceptions from coroutines managed by MCCoroutine. + */ object MCCoroutineExceptionListener : Listener { private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) @EventHandler fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { + // Only handle exceptions from our plugin + if (event.plugin != plugin) { + return + } + + // Skip CancellationException as per MCCoroutine documentation if (event.exception is CancellationException) { return } + // Cancel the event to prevent MCCoroutine's default logging event.isCancelled = true + // Log the exception logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) + // Log to database asynchronously with try-catch to prevent infinite stacking try { runBlocking { launch { @@ -33,6 +47,7 @@ object MCCoroutineExceptionListener : Listener { server = surfServerConfig.serverName ) } catch (e: Exception) { + // Log but don't rethrow to prevent infinite exception loop logger.log( Level.SEVERE, "Failed to log MCCoroutine exception to database", From 2fa1c5a53440b135409fc9240b090775e2e5498e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:12:50 +0000 Subject: [PATCH 20/48] feat: log error ID to console when errors are captured Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt | 3 ++- .../surf/core/paper/listener/MCCoroutineExceptionListener.kt | 3 ++- .../core/velocity/listener/MCCoroutineExceptionListener.kt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index 3861a8f..74d01cb 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -28,10 +28,11 @@ object GlobalErrorHandler { errorScope.launch { try { - systemErrorService.logError( + val systemError = systemErrorService.logError( throwable = throwable, server = SurfServer.current().name ) + logger.info("This error has been logged with ID: ${systemError.id}") } catch (e: Exception) { logger.log(Level.SEVERE, "Failed to log error to database", e) } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 1960607..805b8c4 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -42,10 +42,11 @@ object MCCoroutineExceptionListener : Listener { runBlocking { launch { try { - systemErrorService.logError( + val systemError = systemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) + logger.info("This error has been logged with ID: ${systemError.id}") } catch (e: Exception) { // Log but don't rethrow to prevent infinite exception loop logger.log( diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 65b3a3b..40df2e7 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -27,10 +27,11 @@ object MCCoroutineExceptionListener { runBlocking { launch { try { - systemErrorService.logError( + val systemError = systemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) + logger.info("This error has been logged with ID: ${systemError.id}") } catch (e: Exception) { logger.log( Level.SEVERE, From dfdf5a610cace50a6c7186a4ed4167a1def959ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:20:48 +0000 Subject: [PATCH 21/48] fix: use upsert for atomic deduplication across restarts Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../repository/SystemErrorRepository.kt | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt index 82e0bb4..5633ef3 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt @@ -4,10 +4,9 @@ import dev.slne.surf.core.api.common.error.SystemError import dev.slne.surf.core.fallback.table.SystemErrorTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.update +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert import dev.slne.surf.surfapi.core.api.util.toObjectList import it.unimi.dsi.fastutil.objects.ObjectList import kotlinx.coroutines.flow.firstOrNull @@ -19,12 +18,13 @@ val systemErrorRepository = SystemErrorRepository() /** * Repository for managing system-wide errors. - * Handles error logging with automatic deduplication. + * Handles error logging with automatic deduplication using upsert. */ class SystemErrorRepository { /** * Logs a system error. If a similar error already exists (same message, location, and server), * it updates the lastOccurred timestamp and increments the occurrence count. + * Uses upsert to handle deduplication atomically at the database level. */ suspend fun logError( message: String, @@ -34,7 +34,7 @@ class SystemErrorRepository { ): SystemError = suspendTransaction { val now = OffsetDateTime.now() - // Check if a similar error already exists + // First, check if the error exists to get the current occurrence count val existingError = SystemErrorTable.selectAll() .where( (SystemErrorTable.errorMessage eq message) and @@ -55,46 +55,39 @@ class SystemErrorRepository { } .firstOrNull() - if (existingError != null) { - // Update the existing error with new lastOccurred and increment count - SystemErrorTable.update( - where = { SystemErrorTable.id eq existingError.id } - ) { - it[SystemErrorTable.lastOccurred] = now - it[SystemErrorTable.occurrenceCount] = existingError.occurrenceCount + 1 - it[SystemErrorTable.stacktrace] = stacktrace // Update stacktrace in case it's slightly different - } - - return@suspendTransaction existingError.copy( - lastOccurred = now, - occurrenceCount = existingError.occurrenceCount + 1, - stacktrace = stacktrace + // Use upsert to handle insert or update atomically + SystemErrorTable.upsert { + it[SystemErrorTable.errorMessage] = message + it[SystemErrorTable.stacktrace] = stacktrace + it[SystemErrorTable.location] = location + it[SystemErrorTable.server] = server + it[SystemErrorTable.firstOccurred] = existingError?.firstOccurred ?: now + it[SystemErrorTable.lastOccurred] = now + it[SystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 + } + + // Fetch the final result + val resultError = SystemErrorTable.selectAll() + .where( + (SystemErrorTable.errorMessage eq message) and + (SystemErrorTable.location eq location) and + (SystemErrorTable.server eq server) ) - } else { - // Insert new error - val insertResult = SystemErrorTable.insert { - it[SystemErrorTable.errorMessage] = message - it[SystemErrorTable.stacktrace] = stacktrace - it[SystemErrorTable.location] = location - it[SystemErrorTable.server] = server - it[SystemErrorTable.firstOccurred] = now - it[SystemErrorTable.lastOccurred] = now - it[SystemErrorTable.occurrenceCount] = 1 + .map { row -> + SystemError( + id = row[SystemErrorTable.id].value, + errorMessage = row[SystemErrorTable.errorMessage], + stacktrace = row[SystemErrorTable.stacktrace], + location = row[SystemErrorTable.location], + server = row[SystemErrorTable.server], + firstOccurred = row[SystemErrorTable.firstOccurred], + lastOccurred = row[SystemErrorTable.lastOccurred], + occurrenceCount = row[SystemErrorTable.occurrenceCount] + ) } - - val id = insertResult[SystemErrorTable.id] - - return@suspendTransaction SystemError( - id = id.value, - errorMessage = message, - stacktrace = stacktrace, - location = location, - server = server, - firstOccurred = now, - lastOccurred = now, - occurrenceCount = 1 - ) - } + .firstOrNull() + + return@suspendTransaction resultError ?: throw IllegalStateException("Failed to retrieve error after upsert") } /** From d8516b6cd9ec8063476512219fa33fbab9b3daaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:42:35 +0000 Subject: [PATCH 22/48] refactor: remove documentation comments from code Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../surf/core/api/common/error/SystemError.kt | 4 ---- .../repository/SystemErrorRepository.kt | 18 ---------------- .../core/fallback/table/SystemErrorTable.kt | 6 ------ .../core/common/error/SystemErrorService.kt | 21 ------------------- .../listener/MCCoroutineExceptionListener.kt | 10 --------- 5 files changed, 59 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt index 792080e..a443508 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt @@ -2,10 +2,6 @@ package dev.slne.surf.core.api.common.error import java.time.OffsetDateTime -/** - * Represents a system error that occurred during execution. - * This is separate from player-specific errors (SurfCoreError). - */ data class SystemError( val id: Long, val errorMessage: String, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt index 5633ef3..1fa3393 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt @@ -16,16 +16,7 @@ import java.time.OffsetDateTime val systemErrorRepository = SystemErrorRepository() -/** - * Repository for managing system-wide errors. - * Handles error logging with automatic deduplication using upsert. - */ class SystemErrorRepository { - /** - * Logs a system error. If a similar error already exists (same message, location, and server), - * it updates the lastOccurred timestamp and increments the occurrence count. - * Uses upsert to handle deduplication atomically at the database level. - */ suspend fun logError( message: String, stacktrace: String, @@ -34,7 +25,6 @@ class SystemErrorRepository { ): SystemError = suspendTransaction { val now = OffsetDateTime.now() - // First, check if the error exists to get the current occurrence count val existingError = SystemErrorTable.selectAll() .where( (SystemErrorTable.errorMessage eq message) and @@ -55,7 +45,6 @@ class SystemErrorRepository { } .firstOrNull() - // Use upsert to handle insert or update atomically SystemErrorTable.upsert { it[SystemErrorTable.errorMessage] = message it[SystemErrorTable.stacktrace] = stacktrace @@ -66,7 +55,6 @@ class SystemErrorRepository { it[SystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 } - // Fetch the final result val resultError = SystemErrorTable.selectAll() .where( (SystemErrorTable.errorMessage eq message) and @@ -90,9 +78,6 @@ class SystemErrorRepository { return@suspendTransaction resultError ?: throw IllegalStateException("Failed to retrieve error after upsert") } - /** - * Gets all system errors. - */ suspend fun getAllErrors(): ObjectList = suspendTransaction { SystemErrorTable.selectAll() .map { row -> @@ -109,9 +94,6 @@ class SystemErrorRepository { }.toList().toObjectList() } - /** - * Gets a specific error by ID. - */ suspend fun getError(id: Long): SystemError? = suspendTransaction { SystemErrorTable.selectAll() .where(SystemErrorTable.id eq id) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt index 86e8ec9..a7d0277 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt @@ -3,10 +3,6 @@ package dev.slne.surf.core.fallback.table import dev.slne.surf.database.columns.time.offsetDateTime import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTable -/** - * Table for storing system-wide errors (not player-specific). - * Tracks errors from threads and coroutines with deduplication support. - */ object SystemErrorTable : LongIdTable("system_errors") { val errorMessage = text("error_message") val stacktrace = text("stacktrace") @@ -17,8 +13,6 @@ object SystemErrorTable : LongIdTable("system_errors") { val occurrenceCount = integer("occurrence_count").default(1) init { - // Create a unique index on the combination of message, location, and server - // This helps identify and deduplicate similar errors index(isUnique = true, errorMessage, location, server) } } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt index 1db5901..fa4d7e4 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt @@ -6,16 +6,12 @@ import it.unimi.dsi.fastutil.objects.ObjectList val systemErrorService = requiredService() -/** - * Extracts the relevant location from a throwable's stack trace. - */ fun extractErrorLocation(throwable: Throwable): String { val stackTrace = throwable.stackTrace if (stackTrace.isEmpty()) { return "Unknown" } - // Find the first stack trace element that's not from Java/Kotlin internals val relevantElement = stackTrace.firstOrNull { element -> !element.className.startsWith("java.") && !element.className.startsWith("kotlin.") && @@ -28,15 +24,7 @@ fun extractErrorLocation(throwable: Throwable): String { return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" } -/** - * Service for managing system-wide errors. - * This is separate from player-specific error logging. - */ interface SystemErrorService { - /** - * Logs a system error from a throwable. - * Automatically extracts stacktrace and location information. - */ suspend fun logError( throwable: Throwable, server: String @@ -48,9 +36,6 @@ interface SystemErrorService { return logError(message, stacktrace, location, server) } - /** - * Logs a system error with explicit parameters. - */ suspend fun logError( message: String, stacktrace: String, @@ -58,13 +43,7 @@ interface SystemErrorService { server: String ): SystemError - /** - * Gets all system errors. - */ suspend fun getAllErrors(): ObjectList - /** - * Gets a specific error by ID. - */ suspend fun getError(id: Long): SystemError? } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 805b8c4..27b4115 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -12,32 +12,23 @@ import java.util.logging.Level import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException -/** - * Listens to MCCoroutine exception events and logs them to the system error database. - * This handles exceptions from coroutines managed by MCCoroutine. - */ object MCCoroutineExceptionListener : Listener { private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) @EventHandler fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { - // Only handle exceptions from our plugin if (event.plugin != plugin) { return } - // Skip CancellationException as per MCCoroutine documentation if (event.exception is CancellationException) { return } - // Cancel the event to prevent MCCoroutine's default logging event.isCancelled = true - // Log the exception logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) - // Log to database asynchronously with try-catch to prevent infinite stacking try { runBlocking { launch { @@ -48,7 +39,6 @@ object MCCoroutineExceptionListener : Listener { ) logger.info("This error has been logged with ID: ${systemError.id}") } catch (e: Exception) { - // Log but don't rethrow to prevent infinite exception loop logger.log( Level.SEVERE, "Failed to log MCCoroutine exception to database", From c8267fe2e02c5e7e668f9853cf8eff3cfe97e31c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:47:16 +0000 Subject: [PATCH 23/48] refactor: rename SystemError to SurfCoreSystemError throughout codebase Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- ...{SystemError.kt => SurfCoreSystemError.kt} | 2 +- .../surf/core/fallback/DatabaseLoaderImpl.kt | 2 +- .../SurfCoreSystemErrorRepository.kt | 113 ++++++++++++++++++ .../repository/SystemErrorRepository.kt | 113 ------------------ .../service/SurfCoreSystemErrorServiceImpl.kt | 24 ++++ .../service/SystemErrorServiceImpl.kt | 24 ---- ...orTable.kt => SurfCoreSystemErrorTable.kt} | 2 +- .../core/common/error/GlobalErrorHandler.kt | 4 +- ...rvice.kt => SurfCoreSystemErrorService.kt} | 14 +-- .../dev/slne/surf/core/paper/PaperMain.kt | 2 +- ...mmand.kt => SurfCoreSystemErrorCommand.kt} | 8 +- .../listener/MCCoroutineExceptionListener.kt | 6 +- .../listener/MCCoroutineExceptionListener.kt | 6 +- 13 files changed, 160 insertions(+), 160 deletions(-) rename surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/{SystemError.kt => SurfCoreSystemError.kt} (90%) create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt delete mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt delete mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt rename surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/{SystemErrorTable.kt => SurfCoreSystemErrorTable.kt} (89%) rename surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/{SystemErrorService.kt => SurfCoreSystemErrorService.kt} (78%) rename surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/{SystemErrorCommand.kt => SurfCoreSystemErrorCommand.kt} (94%) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt similarity index 90% rename from surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt rename to surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index a443508..9e4d3f6 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -2,7 +2,7 @@ package dev.slne.surf.core.api.common.error import java.time.OffsetDateTime -data class SystemError( +data class SurfCoreSystemError( val id: Long, val errorMessage: String, val stacktrace: String, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt index ec56a5b..26b277c 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt @@ -22,7 +22,7 @@ class DatabaseLoaderImpl : DatabaseLoader, Services.Fallback { SurfPlayerIpAddressHistoryTable, SurfPlayerTexturesHistoryTable, SurfCoreErrorLogsTable, - SystemErrorTable + SurfCoreSystemErrorTable ) } } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt new file mode 100644 index 0000000..2a78984 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -0,0 +1,113 @@ +package dev.slne.surf.core.fallback.repository + +import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert +import dev.slne.surf.surfapi.core.api.util.toObjectList +import it.unimi.dsi.fastutil.objects.ObjectList +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import java.time.OffsetDateTime + +val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() + +class SurfCoreSystemErrorRepository { + suspend fun logError( + message: String, + stacktrace: String, + location: String, + server: String + ): SurfCoreSystemError = suspendTransaction { + val now = OffsetDateTime.now() + + val existingError = SurfCoreSystemErrorTable.selectAll() + .where( + (SurfCoreSystemErrorTable.errorMessage eq message) and + (SurfCoreSystemErrorTable.location eq location) and + (SurfCoreSystemErrorTable.server eq server) + ) + .map { row -> + SurfCoreSystemError( + id = row[SurfCoreSystemErrorTable.id].value, + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + } + .firstOrNull() + + SurfCoreSystemErrorTable.upsert { + it[SurfCoreSystemErrorTable.errorMessage] = message + it[SurfCoreSystemErrorTable.stacktrace] = stacktrace + it[SurfCoreSystemErrorTable.location] = location + it[SurfCoreSystemErrorTable.server] = server + it[SurfCoreSystemErrorTable.firstOccurred] = existingError?.firstOccurred ?: now + it[SurfCoreSystemErrorTable.lastOccurred] = now + it[SurfCoreSystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 + } + + val resultError = SurfCoreSystemErrorTable.selectAll() + .where( + (SurfCoreSystemErrorTable.errorMessage eq message) and + (SurfCoreSystemErrorTable.location eq location) and + (SurfCoreSystemErrorTable.server eq server) + ) + .map { row -> + SurfCoreSystemError( + id = row[SurfCoreSystemErrorTable.id].value, + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + } + .firstOrNull() + + return@suspendTransaction resultError ?: throw IllegalStateException("Failed to retrieve error after upsert") + } + + suspend fun getAllErrors(): ObjectList = suspendTransaction { + SurfCoreSystemErrorTable.selectAll() + .map { row -> + SurfCoreSystemError( + id = row[SurfCoreSystemErrorTable.id].value, + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + }.toList().toObjectList() + } + + suspend fun getError(id: Long): SurfCoreSystemError? = suspendTransaction { + SurfCoreSystemErrorTable.selectAll() + .where(SurfCoreSystemErrorTable.id eq id) + .map { row -> + SurfCoreSystemError( + id = row[SurfCoreSystemErrorTable.id].value, + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + }.firstOrNull() + } +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt deleted file mode 100644 index 1fa3393..0000000 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SystemErrorRepository.kt +++ /dev/null @@ -1,113 +0,0 @@ -package dev.slne.surf.core.fallback.repository - -import dev.slne.surf.core.api.common.error.SystemError -import dev.slne.surf.core.fallback.table.SystemErrorTable -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert -import dev.slne.surf.surfapi.core.api.util.toObjectList -import it.unimi.dsi.fastutil.objects.ObjectList -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList -import java.time.OffsetDateTime - -val systemErrorRepository = SystemErrorRepository() - -class SystemErrorRepository { - suspend fun logError( - message: String, - stacktrace: String, - location: String, - server: String - ): SystemError = suspendTransaction { - val now = OffsetDateTime.now() - - val existingError = SystemErrorTable.selectAll() - .where( - (SystemErrorTable.errorMessage eq message) and - (SystemErrorTable.location eq location) and - (SystemErrorTable.server eq server) - ) - .map { row -> - SystemError( - id = row[SystemErrorTable.id].value, - errorMessage = row[SystemErrorTable.errorMessage], - stacktrace = row[SystemErrorTable.stacktrace], - location = row[SystemErrorTable.location], - server = row[SystemErrorTable.server], - firstOccurred = row[SystemErrorTable.firstOccurred], - lastOccurred = row[SystemErrorTable.lastOccurred], - occurrenceCount = row[SystemErrorTable.occurrenceCount] - ) - } - .firstOrNull() - - SystemErrorTable.upsert { - it[SystemErrorTable.errorMessage] = message - it[SystemErrorTable.stacktrace] = stacktrace - it[SystemErrorTable.location] = location - it[SystemErrorTable.server] = server - it[SystemErrorTable.firstOccurred] = existingError?.firstOccurred ?: now - it[SystemErrorTable.lastOccurred] = now - it[SystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 - } - - val resultError = SystemErrorTable.selectAll() - .where( - (SystemErrorTable.errorMessage eq message) and - (SystemErrorTable.location eq location) and - (SystemErrorTable.server eq server) - ) - .map { row -> - SystemError( - id = row[SystemErrorTable.id].value, - errorMessage = row[SystemErrorTable.errorMessage], - stacktrace = row[SystemErrorTable.stacktrace], - location = row[SystemErrorTable.location], - server = row[SystemErrorTable.server], - firstOccurred = row[SystemErrorTable.firstOccurred], - lastOccurred = row[SystemErrorTable.lastOccurred], - occurrenceCount = row[SystemErrorTable.occurrenceCount] - ) - } - .firstOrNull() - - return@suspendTransaction resultError ?: throw IllegalStateException("Failed to retrieve error after upsert") - } - - suspend fun getAllErrors(): ObjectList = suspendTransaction { - SystemErrorTable.selectAll() - .map { row -> - SystemError( - id = row[SystemErrorTable.id].value, - errorMessage = row[SystemErrorTable.errorMessage], - stacktrace = row[SystemErrorTable.stacktrace], - location = row[SystemErrorTable.location], - server = row[SystemErrorTable.server], - firstOccurred = row[SystemErrorTable.firstOccurred], - lastOccurred = row[SystemErrorTable.lastOccurred], - occurrenceCount = row[SystemErrorTable.occurrenceCount] - ) - }.toList().toObjectList() - } - - suspend fun getError(id: Long): SystemError? = suspendTransaction { - SystemErrorTable.selectAll() - .where(SystemErrorTable.id eq id) - .map { row -> - SystemError( - id = row[SystemErrorTable.id].value, - errorMessage = row[SystemErrorTable.errorMessage], - stacktrace = row[SystemErrorTable.stacktrace], - location = row[SystemErrorTable.location], - server = row[SystemErrorTable.server], - firstOccurred = row[SystemErrorTable.firstOccurred], - lastOccurred = row[SystemErrorTable.lastOccurred], - occurrenceCount = row[SystemErrorTable.occurrenceCount] - ) - }.firstOrNull() - } -} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt new file mode 100644 index 0000000..669eb47 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -0,0 +1,24 @@ +package dev.slne.surf.core.fallback.service + +import com.google.auto.service.AutoService +import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.core.common.error.SurfCoreSystemErrorService +import dev.slne.surf.core.fallback.repository.surfCoreSystemErrorRepository +import it.unimi.dsi.fastutil.objects.ObjectList +import net.kyori.adventure.util.Services + +@AutoService(SystemErrorService::class) +class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fallback { + override suspend fun logError( + message: String, + stacktrace: String, + location: String, + server: String + ): SurfCoreSystemError = surfCoreSystemErrorRepository.logError(message, stacktrace, location, server) + + override suspend fun getAllErrors(): ObjectList = + surfCoreSystemErrorRepository.getAllErrors() + + override suspend fun getError(id: Long): SurfCoreSystemError? = + surfCoreSystemErrorRepository.getError(id) +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt deleted file mode 100644 index d393183..0000000 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SystemErrorServiceImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.slne.surf.core.fallback.service - -import com.google.auto.service.AutoService -import dev.slne.surf.core.api.common.error.SystemError -import dev.slne.surf.core.core.common.error.SystemErrorService -import dev.slne.surf.core.fallback.repository.systemErrorRepository -import it.unimi.dsi.fastutil.objects.ObjectList -import net.kyori.adventure.util.Services - -@AutoService(SystemErrorService::class) -class SystemErrorServiceImpl : SystemErrorService, Services.Fallback { - override suspend fun logError( - message: String, - stacktrace: String, - location: String, - server: String - ): SystemError = systemErrorRepository.logError(message, stacktrace, location, server) - - override suspend fun getAllErrors(): ObjectList = - systemErrorRepository.getAllErrors() - - override suspend fun getError(id: Long): SystemError? = - systemErrorRepository.getError(id) -} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt similarity index 89% rename from surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt rename to surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt index a7d0277..d3d8097 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SystemErrorTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt @@ -3,7 +3,7 @@ package dev.slne.surf.core.fallback.table import dev.slne.surf.database.columns.time.offsetDateTime import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTable -object SystemErrorTable : LongIdTable("system_errors") { +object SurfCoreSystemErrorTable : LongIdTable("surf_core_system_errors") { val errorMessage = text("error_message") val stacktrace = text("stacktrace") val location = varchar("location", 500) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index 74d01cb..dbfb768 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -28,11 +28,11 @@ object GlobalErrorHandler { errorScope.launch { try { - val systemError = systemErrorService.logError( + val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = throwable, server = SurfServer.current().name ) - logger.info("This error has been logged with ID: ${systemError.id}") + logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") } catch (e: Exception) { logger.log(Level.SEVERE, "Failed to log error to database", e) } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt similarity index 78% rename from surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt rename to surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt index fa4d7e4..08d3ca5 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt @@ -1,10 +1,10 @@ package dev.slne.surf.core.core.common.error -import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.surfapi.core.api.util.requiredService import it.unimi.dsi.fastutil.objects.ObjectList -val systemErrorService = requiredService() +val surfCoreSystemErrorService = requiredService() fun extractErrorLocation(throwable: Throwable): String { val stackTrace = throwable.stackTrace @@ -24,11 +24,11 @@ fun extractErrorLocation(throwable: Throwable): String { return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" } -interface SystemErrorService { +interface SurfCoreSystemErrorService { suspend fun logError( throwable: Throwable, server: String - ): SystemError { + ): SurfCoreSystemError { val message = throwable.message ?: throwable::class.java.simpleName val stacktrace = throwable.stackTraceToString() val location = extractErrorLocation(throwable) @@ -41,9 +41,9 @@ interface SystemErrorService { stacktrace: String, location: String, server: String - ): SystemError + ): SurfCoreSystemError - suspend fun getAllErrors(): ObjectList + suspend fun getAllErrors(): ObjectList - suspend fun getError(id: Long): SystemError? + suspend fun getError(id: Long): SurfCoreSystemError? } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index c42a184..ff9fb52 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -42,7 +42,7 @@ class PaperMain : SuspendingJavaPlugin() { networkBroadcastCommand() networkSendCommand() coreErrorCommand() - systemErrorCommand() + surfCoreSystemErrorCommand() testErrorCommand() PlayerConnectListener.register() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt similarity index 94% rename from surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt rename to surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt index 449830a..092ce6b 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SystemErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt @@ -5,7 +5,7 @@ import dev.jorel.commandapi.kotlindsl.getValue import dev.jorel.commandapi.kotlindsl.literalArgument import dev.jorel.commandapi.kotlindsl.longArgument import dev.slne.surf.core.api.common.error.SystemError -import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.paper.permission.PermissionRegistry import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend import dev.slne.surf.surfapi.core.api.messages.adventure.buildText @@ -15,11 +15,11 @@ import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter -fun systemErrorCommand() = commandTree("systemerror") { +fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { withPermission(PermissionRegistry.COMMAND_CORE_ERROR) literalArgument("list") { anyExecutorSuspend { executor, _ -> - val errors = systemErrorService.getAllErrors() + val errors = surfCoreSystemErrorService.getAllErrors() executor.sendText { appendNewline() @@ -31,7 +31,7 @@ fun systemErrorCommand() = commandTree("systemerror") { longArgument("id") { anyExecutorSuspend { executor, args -> val id: Long by args - val error = systemErrorService.getError(id) ?: run { + val error = surfCoreSystemErrorService.getError(id) ?: run { executor.sendText { appendErrorPrefix() error("Der Fehler wurde nicht gefunden.") diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 27b4115..79c1f6d 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -1,7 +1,7 @@ package dev.slne.surf.core.paper.listener import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent -import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.paper.plugin import dev.slne.surf.core.paper.surfServerConfig import kotlinx.coroutines.launch @@ -33,11 +33,11 @@ object MCCoroutineExceptionListener : Listener { runBlocking { launch { try { - val systemError = systemErrorService.logError( + val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) - logger.info("This error has been logged with ID: ${systemError.id}") + logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") } catch (e: Exception) { logger.log( Level.SEVERE, diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 40df2e7..2871fe3 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -3,7 +3,7 @@ package dev.slne.surf.core.velocity.listener import com.github.shynixn.mccoroutine.velocity.MCCoroutineExceptionEvent import com.velocitypowered.api.event.ResultedEvent import com.velocitypowered.api.event.Subscribe -import dev.slne.surf.core.core.common.error.systemErrorService +import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.velocity.surfServerConfig import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -27,11 +27,11 @@ object MCCoroutineExceptionListener { runBlocking { launch { try { - val systemError = systemErrorService.logError( + val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) - logger.info("This error has been logged with ID: ${systemError.id}") + logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") } catch (e: Exception) { logger.log( Level.SEVERE, From 7f218cb340c85f9f902149f3d66d6ac3f6e681a5 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 15:52:42 +0100 Subject: [PATCH 24/48] refactor: rename SystemError to SurfCoreSystemError in command and service implementations --- .../core/fallback/service/SurfCoreSystemErrorServiceImpl.kt | 5 +++-- .../surf/core/paper/command/SurfCoreSystemErrorCommand.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt index 669eb47..1abe555 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -7,14 +7,15 @@ import dev.slne.surf.core.fallback.repository.surfCoreSystemErrorRepository import it.unimi.dsi.fastutil.objects.ObjectList import net.kyori.adventure.util.Services -@AutoService(SystemErrorService::class) +@AutoService(SurfCoreSystemErrorService::class) class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fallback { override suspend fun logError( message: String, stacktrace: String, location: String, server: String - ): SurfCoreSystemError = surfCoreSystemErrorRepository.logError(message, stacktrace, location, server) + ): SurfCoreSystemError = + surfCoreSystemErrorRepository.logError(message, stacktrace, location, server) override suspend fun getAllErrors(): ObjectList = surfCoreSystemErrorRepository.getAllErrors() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt index 092ce6b..4fe5d2e 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt @@ -4,7 +4,7 @@ import dev.jorel.commandapi.kotlindsl.commandTree import dev.jorel.commandapi.kotlindsl.getValue import dev.jorel.commandapi.kotlindsl.literalArgument import dev.jorel.commandapi.kotlindsl.longArgument -import dev.slne.surf.core.api.common.error.SystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.paper.permission.PermissionRegistry import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend @@ -96,7 +96,7 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { } } -private val pagination = Pagination { +private val pagination = Pagination { title { primary("System-Fehler Übersicht") } rowRenderer { row, _ -> listOf( From cd3cdb78aecc3c6f193f550082136176a9ee0c39 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 16:09:20 +0100 Subject: [PATCH 25/48] refactor: remove unnecessary plugin check from MCCoroutineExceptionListener --- .../surf/core/paper/listener/MCCoroutineExceptionListener.kt | 5 ----- .../core/velocity/listener/MCCoroutineExceptionListener.kt | 4 ---- 2 files changed, 9 deletions(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 79c1f6d..21047f6 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -2,7 +2,6 @@ package dev.slne.surf.core.paper.listener import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService -import dev.slne.surf.core.paper.plugin import dev.slne.surf.core.paper.surfServerConfig import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -17,10 +16,6 @@ object MCCoroutineExceptionListener : Listener { @EventHandler fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { - if (event.plugin != plugin) { - return - } - if (event.exception is CancellationException) { return } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 2871fe3..e5c605f 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -9,16 +9,12 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.logging.Level import java.util.logging.Logger -import kotlin.coroutines.cancellation.CancellationException object MCCoroutineExceptionListener { private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) @Subscribe fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { - if (event.exception is CancellationException) { - return - } event.result = ResultedEvent.GenericResult.denied() logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) From a1fa50b1453882ad202b86644e9f8258968ad3f7 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 16:33:41 +0100 Subject: [PATCH 26/48] feat: update SurfCoreSystemError to use UUID and enhance error handling logic --- .../api/common/error/SurfCoreSystemError.kt | 3 +- .../SurfCoreSystemErrorRepository.kt | 31 +++++++++++-------- .../table/SurfCoreSystemErrorTable.kt | 6 ++-- .../listener/MCCoroutineExceptionListener.kt | 1 - 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index 9e4d3f6..df9c503 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -1,9 +1,10 @@ package dev.slne.surf.core.api.common.error import java.time.OffsetDateTime +import java.util.* data class SurfCoreSystemError( - val id: Long, + val uuid: UUID, val errorMessage: String, val stacktrace: String, val location: String, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 2a78984..1c25a33 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -4,6 +4,7 @@ import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert @@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import java.time.OffsetDateTime +import java.util.* val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() @@ -24,16 +26,17 @@ class SurfCoreSystemErrorRepository { server: String ): SurfCoreSystemError = suspendTransaction { val now = OffsetDateTime.now() - + val existingError = SurfCoreSystemErrorTable.selectAll() .where( (SurfCoreSystemErrorTable.errorMessage eq message) and - (SurfCoreSystemErrorTable.location eq location) and - (SurfCoreSystemErrorTable.server eq server) + (SurfCoreSystemErrorTable.location eq location) and + (SurfCoreSystemErrorTable.server eq server) and + (SurfCoreSystemErrorTable.lastOccurred lessEq now.minusDays(1)) ) .map { row -> SurfCoreSystemError( - id = row[SurfCoreSystemErrorTable.id].value, + uuid = row[SurfCoreSystemErrorTable.uuid], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -44,8 +47,9 @@ class SurfCoreSystemErrorRepository { ) } .firstOrNull() - + SurfCoreSystemErrorTable.upsert { + it[SurfCoreSystemErrorTable.uuid] = existingError?.uuid ?: UUID.randomUUID() it[SurfCoreSystemErrorTable.errorMessage] = message it[SurfCoreSystemErrorTable.stacktrace] = stacktrace it[SurfCoreSystemErrorTable.location] = location @@ -54,16 +58,16 @@ class SurfCoreSystemErrorRepository { it[SurfCoreSystemErrorTable.lastOccurred] = now it[SurfCoreSystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 } - + val resultError = SurfCoreSystemErrorTable.selectAll() .where( (SurfCoreSystemErrorTable.errorMessage eq message) and - (SurfCoreSystemErrorTable.location eq location) and - (SurfCoreSystemErrorTable.server eq server) + (SurfCoreSystemErrorTable.location eq location) and + (SurfCoreSystemErrorTable.server eq server) ) .map { row -> SurfCoreSystemError( - id = row[SurfCoreSystemErrorTable.id].value, + uuid = row[SurfCoreSystemErrorTable.uuid], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -74,15 +78,16 @@ class SurfCoreSystemErrorRepository { ) } .firstOrNull() - - return@suspendTransaction resultError ?: throw IllegalStateException("Failed to retrieve error after upsert") + + return@suspendTransaction resultError + ?: error("Failed to retrieve error after upsert") } suspend fun getAllErrors(): ObjectList = suspendTransaction { SurfCoreSystemErrorTable.selectAll() .map { row -> SurfCoreSystemError( - id = row[SurfCoreSystemErrorTable.id].value, + uuid = row[SurfCoreSystemErrorTable.uuid], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -99,7 +104,7 @@ class SurfCoreSystemErrorRepository { .where(SurfCoreSystemErrorTable.id eq id) .map { row -> SurfCoreSystemError( - id = row[SurfCoreSystemErrorTable.id].value, + uuid = row[SurfCoreSystemErrorTable.uuid], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt index d3d8097..79739bd 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt @@ -1,9 +1,11 @@ package dev.slne.surf.core.fallback.table +import dev.slne.surf.database.columns.nativeUuid import dev.slne.surf.database.columns.time.offsetDateTime import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTable object SurfCoreSystemErrorTable : LongIdTable("surf_core_system_errors") { + val uuid = nativeUuid("error_uuid").uniqueIndex() val errorMessage = text("error_message") val stacktrace = text("stacktrace") val location = varchar("location", 500) @@ -11,8 +13,4 @@ object SurfCoreSystemErrorTable : LongIdTable("surf_core_system_errors") { val firstOccurred = offsetDateTime("first_occurred") val lastOccurred = offsetDateTime("last_occurred") val occurrenceCount = integer("occurrence_count").default(1) - - init { - index(isUnique = true, errorMessage, location, server) - } } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index e5c605f..0b3d784 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -18,7 +18,6 @@ object MCCoroutineExceptionListener { event.result = ResultedEvent.GenericResult.denied() logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) - try { runBlocking { launch { From e495314a24ef9ca9c4e3cf26bd1ddfff86ce8eb8 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:02:22 +0100 Subject: [PATCH 27/48] feat: enhance error logging with UUID and improve exception handling --- .../api/common/error/SurfCoreSystemError.kt | 4 +- .../SurfCoreSystemErrorRepository.kt | 4 +- .../service/SurfCoreSystemErrorServiceImpl.kt | 5 ++- .../core/common/error/GlobalErrorHandler.kt | 38 ++++++++--------- .../error/SurfCoreSystemErrorService.kt | 23 ++++++----- .../command/SurfCoreSystemErrorCommand.kt | 41 +++++++++++-------- .../core/paper/command/TestErrorCommand.kt | 22 +++++++--- .../listener/MCCoroutineExceptionListener.kt | 26 ++++++------ 8 files changed, 90 insertions(+), 73 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index df9c503..0c5bc75 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -12,4 +12,6 @@ data class SurfCoreSystemError( val firstOccurred: OffsetDateTime, val lastOccurred: OffsetDateTime, val occurrenceCount: Int -) +) { + fun getLocationClassName() = location.substringBeforeLast('.') +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 1c25a33..895d817 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -99,9 +99,9 @@ class SurfCoreSystemErrorRepository { }.toList().toObjectList() } - suspend fun getError(id: Long): SurfCoreSystemError? = suspendTransaction { + suspend fun getError(uuid: UUID): SurfCoreSystemError? = suspendTransaction { SurfCoreSystemErrorTable.selectAll() - .where(SurfCoreSystemErrorTable.id eq id) + .where(SurfCoreSystemErrorTable.uuid eq uuid) .map { row -> SurfCoreSystemError( uuid = row[SurfCoreSystemErrorTable.uuid], diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt index 1abe555..e6c5346 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -6,6 +6,7 @@ import dev.slne.surf.core.core.common.error.SurfCoreSystemErrorService import dev.slne.surf.core.fallback.repository.surfCoreSystemErrorRepository import it.unimi.dsi.fastutil.objects.ObjectList import net.kyori.adventure.util.Services +import java.util.* @AutoService(SurfCoreSystemErrorService::class) class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fallback { @@ -20,6 +21,6 @@ class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fall override suspend fun getAllErrors(): ObjectList = surfCoreSystemErrorRepository.getAllErrors() - override suspend fun getError(id: Long): SurfCoreSystemError? = - surfCoreSystemErrorRepository.getError(id) + override suspend fun getError(uuid: UUID): SurfCoreSystemError? = + surfCoreSystemErrorRepository.getError(uuid) } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index dbfb768..e8fa257 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -1,44 +1,38 @@ package dev.slne.surf.core.core.common.error import dev.slne.surf.core.api.common.server.SurfServer +import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.* -import java.util.logging.Level -import java.util.logging.Logger object GlobalErrorHandler { - private val logger = Logger.getLogger(GlobalErrorHandler::class.java.name) private val errorScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val logger = logger() fun install() { Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> handleError(thread.name, throwable) } - logger.info("Global error handler installed") - } - - fun createCoroutineExceptionHandler(): CoroutineExceptionHandler { - return CoroutineExceptionHandler { context, throwable -> - handleError(context.toString(), throwable) - } + logger.atInfo().log("Global error handler installed") } private fun handleError(source: String, throwable: Throwable) { - try { - logger.log(Level.SEVERE, "Uncaught exception from $source", throwable) + runCatching { + logger.atSevere().log("Uncaught exception from $source", throwable) errorScope.launch { - try { + runCatching { val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = throwable, server = SurfServer.current().name ) - logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") - } catch (e: Exception) { - logger.log(Level.SEVERE, "Failed to log error to database", e) + logger.atInfo() + .log("This error has been logged with ID: ${surfCoreSystemError.uuid}") + }.onFailure { + logger.atSevere().log("Failed to log error to database") } } - } catch (e: Exception) { - logger.log(Level.SEVERE, "Error handler failed", e) + }.onFailure { + logger.atSevere().log("Error handler failed") } } @@ -48,11 +42,11 @@ object GlobalErrorHandler { fun shutdown() { runBlocking { - try { + runCatching { errorScope.coroutineContext.job.cancelAndJoin() - logger.info("Global error handler shutdown complete") - } catch (e: Exception) { - logger.log(Level.WARNING, "Error during error handler shutdown", e) + logger.atInfo().log("Global error handler shutdown complete") + }.onFailure { + logger.atWarning().log("Error during error handler shutdown") } } } diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt index 08d3ca5..4ce5030 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt @@ -3,6 +3,7 @@ package dev.slne.surf.core.core.common.error import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.surfapi.core.api.util.requiredService import it.unimi.dsi.fastutil.objects.ObjectList +import java.util.* val surfCoreSystemErrorService = requiredService() @@ -11,16 +12,16 @@ fun extractErrorLocation(throwable: Throwable): String { if (stackTrace.isEmpty()) { return "Unknown" } - + val relevantElement = stackTrace.firstOrNull { element -> !element.className.startsWith("java.") && - !element.className.startsWith("kotlin.") && - !element.className.startsWith("sun.") && - !element.className.startsWith("jdk.") && - !element.className.startsWith("org.jetbrains.exposed.") && - !element.className.startsWith("kotlinx.coroutines.") + !element.className.startsWith("kotlin.") && + !element.className.startsWith("sun.") && + !element.className.startsWith("jdk.") && + !element.className.startsWith("org.jetbrains.exposed.") && + !element.className.startsWith("kotlinx.coroutines.") } ?: stackTrace.first() - + return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" } @@ -32,10 +33,10 @@ interface SurfCoreSystemErrorService { val message = throwable.message ?: throwable::class.java.simpleName val stacktrace = throwable.stackTraceToString() val location = extractErrorLocation(throwable) - + return logError(message, stacktrace, location, server) } - + suspend fun logError( message: String, stacktrace: String, @@ -44,6 +45,6 @@ interface SurfCoreSystemErrorService { ): SurfCoreSystemError suspend fun getAllErrors(): ObjectList - - suspend fun getError(id: Long): SurfCoreSystemError? + + suspend fun getError(uuid: UUID): SurfCoreSystemError? } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt index 4fe5d2e..b9c7b3b 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt @@ -3,7 +3,7 @@ package dev.slne.surf.core.paper.command import dev.jorel.commandapi.kotlindsl.commandTree import dev.jorel.commandapi.kotlindsl.getValue import dev.jorel.commandapi.kotlindsl.literalArgument -import dev.jorel.commandapi.kotlindsl.longArgument +import dev.jorel.commandapi.kotlindsl.uuidArgument import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.paper.permission.PermissionRegistry @@ -14,6 +14,8 @@ import dev.slne.surf.surfapi.core.api.messages.adventure.clickRunsCommand import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter +import net.kyori.adventure.text.format.TextDecoration +import java.util.* fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { withPermission(PermissionRegistry.COMMAND_CORE_ERROR) @@ -28,10 +30,10 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { } } literalArgument("view") { - longArgument("id") { + uuidArgument("uuid") { anyExecutorSuspend { executor, args -> - val id: Long by args - val error = surfCoreSystemErrorService.getError(id) ?: run { + val uuid: UUID by args + val error = surfCoreSystemErrorService.getError(uuid) ?: run { executor.sendText { appendErrorPrefix() error("Der Fehler wurde nicht gefunden.") @@ -43,10 +45,12 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { appendNewline() darkSpacer("*" + "-".repeat(40) + "*") appendNewline() + primary("Systemfehler Details", TextDecoration.BOLD) + appendNewline() appendNewline() appendInfoPrefix() - info("Fehler-ID: ") - variableValue(error.id.toString()) + info("Fehler-Uuid: ") + variableValue(error.uuid.toString()) appendNewline() appendInfoPrefix() info("Server: ") @@ -55,7 +59,12 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { appendInfoPrefix() info("Ort: ") append { - variableValue(error.location) + variableValue(error.getLocationClassName()) + hoverEvent(buildText { + variableValue(error.location) + appendNewline() + spacer("Klicke, um den vollständigen Ort zu kopieren.") + }) clickCopiesToClipboard(error.location) } appendNewline() @@ -74,16 +83,16 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { appendInfoPrefix() info("Nachricht: ") appendNewline() - spacer(error.errorMessage.take(200)) - if (error.errorMessage.length > 200) { + spacer(error.errorMessage.take(50)) + if (error.errorMessage.length > 50) { spacer("... (gekürzt)") } appendNewline() appendInfoPrefix() info("Stacktrace: ") appendNewline() - spacer(error.stacktrace.take(1000)) - if (error.stacktrace.length > 1000) { + spacer(error.stacktrace.take(75)) + if (error.stacktrace.length > 75) { appendNewline() spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") } @@ -102,16 +111,16 @@ private val pagination = Pagination { listOf( buildText { appendInfoPrefix() - variableKey("ID: ") - variableValue(row.id.toString()) + variableKey("Fehler-Uuid: ") + variableValue(row.uuid.toString()) appendSpace() spacer("(${row.occurrenceCount}x)") appendSpace() - spacer("- ${row.location.take(50)}") - if (row.location.length > 50) { + spacer("- ${row.getLocationClassName().take(50)}") + if (row.getLocationClassName().length > 50) { spacer("...") } - clickRunsCommand("/systemerror view ${row.id}") + clickRunsCommand("/systemerror view ${row.uuid}") hoverEvent(buildText { spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") appendNewline() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt index 0806ee3..f70b80c 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt @@ -1,5 +1,6 @@ package dev.slne.surf.core.paper.command +import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher import com.github.shynixn.mccoroutine.folia.launch import dev.jorel.commandapi.kotlindsl.anyExecutor import dev.jorel.commandapi.kotlindsl.commandTree @@ -22,7 +23,7 @@ fun testErrorCommand() = commandTree("testerror") { } Thread { - throw RuntimeException("Test thread exception from /testerror thread") + error("Test thread exception from /testerror thread") }.start() } } @@ -34,8 +35,7 @@ fun testErrorCommand() = commandTree("testerror") { success("Triggering coroutine exception...") } - // This will be caught by MCCoroutineExceptionListener - throw RuntimeException("Test coroutine exception from /testerror coroutine") + error("Test coroutine exception from /testerror coroutine") } } @@ -47,7 +47,7 @@ fun testErrorCommand() = commandTree("testerror") { } plugin.launch { - throw RuntimeException("Test exception from plugin.launch in /testerror launch") + error("Test exception from plugin.launch in /testerror launch") } } } @@ -68,6 +68,19 @@ fun testErrorCommand() = commandTree("testerror") { } } + literalArgument("customContext") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering exception with custom context...") + + plugin.launch(plugin.globalRegionDispatcher) { + error("Test exception with custom context from /testerror customContext") + } + } + } + } + literalArgument("duplicate") { anyExecutorSuspend { executor, _ -> executor.sendText { @@ -75,7 +88,6 @@ fun testErrorCommand() = commandTree("testerror") { success("Triggering duplicate errors...") } - // Trigger the same error multiple times to test deduplication repeat(3) { Thread { Thread.sleep(100L * it) diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 0b3d784..884d8e4 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -5,39 +5,37 @@ import com.velocitypowered.api.event.ResultedEvent import com.velocitypowered.api.event.Subscribe import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.velocity.surfServerConfig +import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import java.util.logging.Level -import java.util.logging.Logger object MCCoroutineExceptionListener { - private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) + private val logger = logger() @Subscribe fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { event.result = ResultedEvent.GenericResult.denied() - logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) - try { + logger.atSevere().log("MCCoroutine exception occurred", event.exception) + runCatching { runBlocking { launch { - try { + runCatching { val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) - logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") - } catch (e: Exception) { - logger.log( - Level.SEVERE, - "Failed to log MCCoroutine exception to database", - e + logger.atInfo() + .log("This error has been logged with Uuid: ${surfCoreSystemError.uuid}") + }.onFailure { + logger.atSevere().log( + "Failed to log MCCoroutine exception to database" ) } } } - } catch (e: Exception) { - logger.log(Level.SEVERE, "Error while handling MCCoroutine exception", e) + }.onFailure { + logger.atSevere().log("Error while handling MCCoroutine exception") } } } From a89726e9ced5f0bf9486d09046da24c949d08e5d Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:03:40 +0100 Subject: [PATCH 28/48] feat: improve error logging in MCCoroutineExceptionListener with structured logging --- .../listener/MCCoroutineExceptionListener.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 21047f6..8eadd7a 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -3,16 +3,15 @@ package dev.slne.surf.core.paper.listener import com.github.shynixn.mccoroutine.folia.MCCoroutineExceptionEvent import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.paper.surfServerConfig +import dev.slne.surf.surfapi.core.api.util.logger import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.bukkit.event.EventHandler import org.bukkit.event.Listener -import java.util.logging.Level -import java.util.logging.Logger import kotlin.coroutines.cancellation.CancellationException object MCCoroutineExceptionListener : Listener { - private val logger = Logger.getLogger(MCCoroutineExceptionListener::class.java.name) + private val logger = logger() @EventHandler fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { @@ -22,28 +21,28 @@ object MCCoroutineExceptionListener : Listener { event.isCancelled = true - logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) + logger.atSevere().log("MCCoroutine exception occurred", event.exception) - try { + + runCatching { runBlocking { launch { - try { + runCatching { val surfCoreSystemError = surfCoreSystemErrorService.logError( throwable = event.exception, server = surfServerConfig.serverName ) - logger.info("This error has been logged with ID: ${surfCoreSystemError.id}") - } catch (e: Exception) { - logger.log( - Level.SEVERE, - "Failed to log MCCoroutine exception to database", - e + logger.atInfo() + .log("This error has been logged with Uuid: ${surfCoreSystemError.uuid}") + }.onFailure { + logger.atSevere().log( + "Failed to log MCCoroutine exception to database" ) } } } - } catch (e: Exception) { - logger.log(Level.SEVERE, "Error while handling MCCoroutine exception", e) + }.onFailure { + logger.atSevere().log("Error while handling MCCoroutine exception") } } } From 2c18455eee63f43ce5ccfd68ba4548c2dc45e0a4 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:16:31 +0100 Subject: [PATCH 29/48] feat: enhance error logging in MCCoroutineExceptionListener to include exception message and stack trace --- .../core/paper/listener/MCCoroutineExceptionListener.kt | 5 ++++- .../core/velocity/listener/MCCoroutineExceptionListener.kt | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt index 8eadd7a..421c0c3 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -21,7 +21,10 @@ object MCCoroutineExceptionListener : Listener { event.isCancelled = true - logger.atSevere().log("MCCoroutine exception occurred", event.exception) + logger.atSevere() + .log( + "MCCoroutine exception occurred: ${event.exception.message}\n${event.exception.stackTraceToString()}" + ) runCatching { diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt index 884d8e4..955208e 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -16,7 +16,11 @@ object MCCoroutineExceptionListener { fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { event.result = ResultedEvent.GenericResult.denied() - logger.atSevere().log("MCCoroutine exception occurred", event.exception) + logger.atSevere() + .log( + "MCCoroutine exception occurred: ${event.exception.message}\n${event.exception.stackTraceToString()}" + ) + runCatching { runBlocking { launch { From 3f44235f6f53f4bc9d3b45411ccc44f8a636e85b Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:18:13 +0100 Subject: [PATCH 30/48] feat: enhance error logging in SurfCoreSystemErrorCommand with hover events and clipboard functionality --- .../command/SurfCoreSystemErrorCommand.kt | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt index b9c7b3b..3a0026b 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt @@ -77,24 +77,38 @@ fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { variableValue(error.lastOccurred.format(dateTimeFormatter)) appendNewline() appendInfoPrefix() - info("Anzahl: ") + info("Anzahl in den letzten 24h: ") variableValue(error.occurrenceCount.toString()) appendNewline() appendInfoPrefix() info("Nachricht: ") appendNewline() - spacer(error.errorMessage.take(50)) - if (error.errorMessage.length > 50) { - spacer("... (gekürzt)") + append { + spacer(error.errorMessage.take(50)) + if (error.errorMessage.length > 50) { + spacer("... (gekürzt)") + } + hoverEvent(buildText { + spacer(error.errorMessage) + }) + clickCopiesToClipboard(error.errorMessage) } + appendNewline() appendInfoPrefix() info("Stacktrace: ") appendNewline() - spacer(error.stacktrace.take(75)) - if (error.stacktrace.length > 75) { - appendNewline() - spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") + append { + spacer(error.stacktrace.take(75)) + if (error.stacktrace.length > 75) { + appendNewline() + spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") + } + + hoverEvent(buildText { + spacer(error.stacktrace) + }) + clickCopiesToClipboard(error.stacktrace) } appendNewline() appendNewline() From cf3a1a37f9b0e53d32297c224aedbec05c0e46ab Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:23:36 +0100 Subject: [PATCH 31/48] feat: refine getLocationClassName method to improve class name extraction --- .../slne/surf/core/api/common/error/SurfCoreSystemError.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index 0c5bc75..fc21e0d 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -13,5 +13,10 @@ data class SurfCoreSystemError( val lastOccurred: OffsetDateTime, val occurrenceCount: Int ) { - fun getLocationClassName() = location.substringBeforeLast('.') + fun getLocationClassName(): String = + location + .substringBefore(':') + .substringAfterLast('.') + .substringBefore('$') + } From bf86af2ef08b7301d6c2b4abbcbfdd6067a420d1 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:36:41 +0100 Subject: [PATCH 32/48] feat: enhance core error command with player and system error handling --- .../api/common/error/SurfCoreSystemError.kt | 2 + .../dev/slne/surf/core/paper/PaperMain.kt | 2 - .../core/paper/command/CoreErrorCommand.kt | 332 +++++++++++++++--- .../command/SurfCoreSystemErrorCommand.kt | 146 -------- .../core/paper/command/TestErrorCommand.kt | 99 ------ .../paper/permission/PermissionRegistry.kt | 2 + 6 files changed, 281 insertions(+), 302 deletions(-) delete mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt delete mode 100644 surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index fc21e0d..dad4296 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -16,7 +16,9 @@ data class SurfCoreSystemError( fun getLocationClassName(): String = location .substringBefore(':') + .substringBeforeLast('.') .substringAfterLast('.') .substringBefore('$') + } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index ff9fb52..1addc62 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt @@ -42,8 +42,6 @@ class PaperMain : SuspendingJavaPlugin() { networkBroadcastCommand() networkSendCommand() coreErrorCommand() - surfCoreSystemErrorCommand() - testErrorCommand() PlayerConnectListener.register() MCCoroutineExceptionListener.register() diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 10cd698..67e9efa 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -1,12 +1,15 @@ package dev.slne.surf.core.paper.command -import dev.jorel.commandapi.kotlindsl.commandTree -import dev.jorel.commandapi.kotlindsl.getValue -import dev.jorel.commandapi.kotlindsl.literalArgument -import dev.jorel.commandapi.kotlindsl.stringArgument +import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher +import com.github.shynixn.mccoroutine.folia.launch +import dev.jorel.commandapi.kotlindsl.* import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.core.common.error.GlobalErrorHandler +import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService import dev.slne.surf.core.paper.permission.PermissionRegistry +import dev.slne.surf.core.paper.plugin import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend import dev.slne.surf.surfapi.core.api.messages.adventure.buildText import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard @@ -15,79 +18,271 @@ import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination import dev.slne.surf.surfapi.core.api.service.PlayerLookupService import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter +import net.kyori.adventure.text.format.TextDecoration +import java.util.* fun coreErrorCommand() = commandTree("coreerror") { withPermission(PermissionRegistry.COMMAND_CORE_ERROR) - literalArgument("viewPlayer") { - stringArgument("playerName") { - anyExecutorSuspend { executor, args -> - val playerName: String by args - val player = PlayerLookupService.getUuid(playerName) ?: run { + + literalArgument("player") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR_PLAYER) + literalArgument("viewPlayer") { + stringArgument("playerName") { + anyExecutorSuspend { executor, args -> + val playerName: String by args + val player = PlayerLookupService.getUuid(playerName) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Spieler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + val error = surfCoreErrorLoggingService.getErrors(player) + executor.sendText { - appendErrorPrefix() - error("Der Spieler wurde nicht gefunden.") + appendNewline() + append(playerErrorPagination.renderComponent(error)) } - return@anyExecutorSuspend } + } + } + literalArgument("viewCode") { + stringArgument("code") { + anyExecutorSuspend { executor, args -> + val code: String by args + val error = surfCoreErrorLoggingService.getError(code) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Fehler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } - val error = surfCoreErrorLoggingService.getErrors(player) - - executor.sendText { - appendNewline() - append(pagination.renderComponent(error)) + executor.sendText { + appendNewline() + darkSpacer("*" + "-".repeat(20) + "*") + appendNewline() + appendNewline() + appendInfoPrefix() + info("Spieler: ") + append { + variableValue(error.playerUuid.toString()) + clickCopiesToClipboard(error.playerUuid.toString()) + } + appendNewline() + appendInfoPrefix() + info("Fehlercode: ") + variableValue(error.code) + appendNewline() + appendInfoPrefix() + info("Nachricht: ") + variableValue(error.message) + appendNewline() + appendInfoPrefix() + info("Server: ") + variableValue(error.server) + appendNewline() + appendInfoPrefix() + info("Zeitpunkt: ") + variableValue(error.timestamp.format(dateTimeFormatter)) + appendNewline() + appendNewline() + darkSpacer("*" + "-".repeat(20) + "*") + } } } } } - literalArgument("viewCode") { - stringArgument("code") { - anyExecutorSuspend { executor, args -> - val code: String by args - val error = surfCoreErrorLoggingService.getError(code) ?: run { + + literalArgument("system") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR_SYSTEM) + literalArgument("test") { + literalArgument("thread") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering thread exception...") + } + + Thread { + error("Test thread exception from /testerror thread") + }.start() + } + } + + literalArgument("coroutine") { + anyExecutorSuspend { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering coroutine exception...") + } + + error("Test coroutine exception from /testerror coroutine") + } + } + + literalArgument("launch") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering exception in launch...") + } + + plugin.launch { + error("Test exception from plugin.launch in /testerror launch") + } + } + } + + literalArgument("manual") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Logging manual error...") + } + + try { + @Suppress("DIVISION_BY_ZERO") + 1 / 0 + } catch (e: Exception) { + GlobalErrorHandler.logError(e) + } + } + } + + literalArgument("customContext") { + anyExecutor { executor, _ -> + executor.sendText { + appendSuccessPrefix() + success("Triggering exception with custom context...") + + plugin.launch(plugin.globalRegionDispatcher) { + error("Test exception with custom context from /testerror customContext") + } + } + } + } + + literalArgument("duplicate") { + anyExecutorSuspend { executor, _ -> executor.sendText { - appendErrorPrefix() - error("Der Fehler wurde nicht gefunden.") + appendSuccessPrefix() + success("Triggering duplicate errors...") + } + + repeat(3) { + Thread { + Thread.sleep(100L * it) + throw RuntimeException("Duplicate test error - this should only be logged once") + }.start() } - return@anyExecutorSuspend } + } + } + + literalArgument("list") { + anyExecutorSuspend { executor, _ -> + val errors = surfCoreSystemErrorService.getAllErrors() executor.sendText { appendNewline() - darkSpacer("*" + "-".repeat(20) + "*") - appendNewline() - appendNewline() - appendInfoPrefix() - info("Spieler: ") - append { - variableValue(error.playerUuid.toString()) - clickCopiesToClipboard(error.playerUuid.toString()) + append(systemErrorPagination.renderComponent(errors)) + } + } + } + literalArgument("view") { + uuidArgument("uuid") { + anyExecutorSuspend { executor, args -> + val uuid: UUID by args + val error = surfCoreSystemErrorService.getError(uuid) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Fehler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + executor.sendText { + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") + appendNewline() + primary("Systemfehler Details", TextDecoration.BOLD) + appendNewline() + appendNewline() + appendInfoPrefix() + info("Fehler-Uuid: ") + variableValue(error.uuid.toString()) + appendNewline() + appendInfoPrefix() + info("Server: ") + variableValue(error.server) + appendNewline() + appendInfoPrefix() + info("Ort: ") + append { + variableValue(error.getLocationClassName()) + hoverEvent(buildText { + variableValue(error.location) + appendNewline() + spacer("Klicke, um den vollständigen Ort zu kopieren.") + }) + clickCopiesToClipboard(error.location) + } + appendNewline() + appendInfoPrefix() + info("Erstmals aufgetreten: ") + variableValue(error.firstOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Zuletzt aufgetreten: ") + variableValue(error.lastOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Anzahl in den letzten 24h: ") + variableValue(error.occurrenceCount.toString()) + appendNewline() + appendInfoPrefix() + info("Nachricht: ") + appendNewline() + append { + spacer(error.errorMessage.take(50)) + if (error.errorMessage.length > 50) { + spacer("... (gekürzt)") + } + hoverEvent(buildText { + spacer(error.errorMessage) + }) + clickCopiesToClipboard(error.errorMessage) + } + + appendNewline() + appendInfoPrefix() + info("Stacktrace: ") + appendNewline() + append { + spacer(error.stacktrace.take(75)) + if (error.stacktrace.length > 75) { + appendNewline() + spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") + } + + hoverEvent(buildText { + spacer(error.stacktrace) + }) + clickCopiesToClipboard(error.stacktrace) + } + appendNewline() + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") } - appendNewline() - appendInfoPrefix() - info("Fehlercode: ") - variableValue(error.code) - appendNewline() - appendInfoPrefix() - info("Nachricht: ") - variableValue(error.message) - appendNewline() - appendInfoPrefix() - info("Server: ") - variableValue(error.server) - appendNewline() - appendInfoPrefix() - info("Zeitpunkt: ") - variableValue(error.timestamp.format(dateTimeFormatter)) - appendNewline() - appendNewline() - darkSpacer("*" + "-".repeat(20) + "*") } } } } } -private val pagination = Pagination { +private val playerErrorPagination = Pagination { title { primary("Fehler-Übersicht") } rowRenderer { row, _ -> listOf( @@ -97,11 +292,38 @@ private val pagination = Pagination { variableValue(row.code) appendSpace() spacer("(${row.timestamp.format(dateTimeFormatter)})") - clickRunsCommand("/coreerror viewCode ${row.code}") + clickRunsCommand("/coreerror player viewCode ${row.code}") hoverEvent(buildText { spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") }) } ) } -} \ No newline at end of file +} + +private val systemErrorPagination = Pagination { + title { primary("System-Fehler Übersicht") } + rowRenderer { row, _ -> + listOf( + buildText { + appendInfoPrefix() + variableKey("Fehler-Uuid: ") + variableValue(row.uuid.toString()) + appendSpace() + spacer("(${row.occurrenceCount}x)") + appendSpace() + spacer("- ${row.getLocationClassName().take(50)}") + if (row.getLocationClassName().length > 50) { + spacer("...") + } + clickRunsCommand("/coreerror system view ${row.uuid}") + hoverEvent(buildText { + spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") + appendNewline() + spacer("Zuletzt: ${row.lastOccurred.format(dateTimeFormatter)}") + }) + } + ) + } +} + diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt deleted file mode 100644 index 3a0026b..0000000 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt +++ /dev/null @@ -1,146 +0,0 @@ -package dev.slne.surf.core.paper.command - -import dev.jorel.commandapi.kotlindsl.commandTree -import dev.jorel.commandapi.kotlindsl.getValue -import dev.jorel.commandapi.kotlindsl.literalArgument -import dev.jorel.commandapi.kotlindsl.uuidArgument -import dev.slne.surf.core.api.common.error.SurfCoreSystemError -import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService -import dev.slne.surf.core.paper.permission.PermissionRegistry -import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend -import dev.slne.surf.surfapi.core.api.messages.adventure.buildText -import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard -import dev.slne.surf.surfapi.core.api.messages.adventure.clickRunsCommand -import dev.slne.surf.surfapi.core.api.messages.adventure.sendText -import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination -import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter -import net.kyori.adventure.text.format.TextDecoration -import java.util.* - -fun surfCoreSystemErrorCommand() = commandTree("surfcoresystemerror") { - withPermission(PermissionRegistry.COMMAND_CORE_ERROR) - literalArgument("list") { - anyExecutorSuspend { executor, _ -> - val errors = surfCoreSystemErrorService.getAllErrors() - - executor.sendText { - appendNewline() - append(pagination.renderComponent(errors)) - } - } - } - literalArgument("view") { - uuidArgument("uuid") { - anyExecutorSuspend { executor, args -> - val uuid: UUID by args - val error = surfCoreSystemErrorService.getError(uuid) ?: run { - executor.sendText { - appendErrorPrefix() - error("Der Fehler wurde nicht gefunden.") - } - return@anyExecutorSuspend - } - - executor.sendText { - appendNewline() - darkSpacer("*" + "-".repeat(40) + "*") - appendNewline() - primary("Systemfehler Details", TextDecoration.BOLD) - appendNewline() - appendNewline() - appendInfoPrefix() - info("Fehler-Uuid: ") - variableValue(error.uuid.toString()) - appendNewline() - appendInfoPrefix() - info("Server: ") - variableValue(error.server) - appendNewline() - appendInfoPrefix() - info("Ort: ") - append { - variableValue(error.getLocationClassName()) - hoverEvent(buildText { - variableValue(error.location) - appendNewline() - spacer("Klicke, um den vollständigen Ort zu kopieren.") - }) - clickCopiesToClipboard(error.location) - } - appendNewline() - appendInfoPrefix() - info("Erstmals aufgetreten: ") - variableValue(error.firstOccurred.format(dateTimeFormatter)) - appendNewline() - appendInfoPrefix() - info("Zuletzt aufgetreten: ") - variableValue(error.lastOccurred.format(dateTimeFormatter)) - appendNewline() - appendInfoPrefix() - info("Anzahl in den letzten 24h: ") - variableValue(error.occurrenceCount.toString()) - appendNewline() - appendInfoPrefix() - info("Nachricht: ") - appendNewline() - append { - spacer(error.errorMessage.take(50)) - if (error.errorMessage.length > 50) { - spacer("... (gekürzt)") - } - hoverEvent(buildText { - spacer(error.errorMessage) - }) - clickCopiesToClipboard(error.errorMessage) - } - - appendNewline() - appendInfoPrefix() - info("Stacktrace: ") - appendNewline() - append { - spacer(error.stacktrace.take(75)) - if (error.stacktrace.length > 75) { - appendNewline() - spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") - } - - hoverEvent(buildText { - spacer(error.stacktrace) - }) - clickCopiesToClipboard(error.stacktrace) - } - appendNewline() - appendNewline() - darkSpacer("*" + "-".repeat(40) + "*") - } - } - } - } -} - -private val pagination = Pagination { - title { primary("System-Fehler Übersicht") } - rowRenderer { row, _ -> - listOf( - buildText { - appendInfoPrefix() - variableKey("Fehler-Uuid: ") - variableValue(row.uuid.toString()) - appendSpace() - spacer("(${row.occurrenceCount}x)") - appendSpace() - spacer("- ${row.getLocationClassName().take(50)}") - if (row.getLocationClassName().length > 50) { - spacer("...") - } - clickRunsCommand("/systemerror view ${row.uuid}") - hoverEvent(buildText { - spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") - appendNewline() - spacer("Zuletzt: ${row.lastOccurred.format(dateTimeFormatter)}") - }) - } - ) - } -} diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt deleted file mode 100644 index f70b80c..0000000 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt +++ /dev/null @@ -1,99 +0,0 @@ -package dev.slne.surf.core.paper.command - -import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher -import com.github.shynixn.mccoroutine.folia.launch -import dev.jorel.commandapi.kotlindsl.anyExecutor -import dev.jorel.commandapi.kotlindsl.commandTree -import dev.jorel.commandapi.kotlindsl.literalArgument -import dev.slne.surf.core.core.common.error.GlobalErrorHandler -import dev.slne.surf.core.paper.permission.PermissionRegistry -import dev.slne.surf.core.paper.plugin -import dev.slne.surf.surfapi.bukkit.api.command.executors.anyExecutorSuspend -import dev.slne.surf.surfapi.core.api.messages.adventure.sendText - - -fun testErrorCommand() = commandTree("testerror") { - withPermission(PermissionRegistry.COMMAND_CORE_ERROR) - - literalArgument("thread") { - anyExecutor { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Triggering thread exception...") - } - - Thread { - error("Test thread exception from /testerror thread") - }.start() - } - } - - literalArgument("coroutine") { - anyExecutorSuspend { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Triggering coroutine exception...") - } - - error("Test coroutine exception from /testerror coroutine") - } - } - - literalArgument("launch") { - anyExecutor { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Triggering exception in launch...") - } - - plugin.launch { - error("Test exception from plugin.launch in /testerror launch") - } - } - } - - literalArgument("manual") { - anyExecutor { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Logging manual error...") - } - - try { - @Suppress("DIVISION_BY_ZERO") - 1 / 0 - } catch (e: Exception) { - GlobalErrorHandler.logError(e) - } - } - } - - literalArgument("customContext") { - anyExecutor { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Triggering exception with custom context...") - - plugin.launch(plugin.globalRegionDispatcher) { - error("Test exception with custom context from /testerror customContext") - } - } - } - } - - literalArgument("duplicate") { - anyExecutorSuspend { executor, _ -> - executor.sendText { - appendSuccessPrefix() - success("Triggering duplicate errors...") - } - - repeat(3) { - Thread { - Thread.sleep(100L * it) - throw RuntimeException("Duplicate test error - this should only be logged once") - }.start() - } - } - } -} diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt index 66d75ab..d8c1803 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt @@ -20,6 +20,8 @@ object PermissionRegistry : PermissionRegistry() { val COMMAND_CORE = create("$BASE_COMMAND.core") val COMMAND_CORE_ERROR = create("$BASE_COMMAND.coreerror") + val COMMAND_CORE_ERROR_SYSTEM = create("$BASE_COMMAND.coreerror.system") + val COMMAND_CORE_ERROR_PLAYER = create("$BASE_COMMAND.coreerror.player") val JOIN_WHERE_AM_I = create("$BASE.whereamijoin") } \ No newline at end of file From 953e999218ad2879dd4a02b8797a1f4ef2851ace Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:38:35 +0000 Subject: [PATCH 33/48] Initial plan From ad64038d1fed5f67836c683879eff1bceb88eb40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:40:57 +0000 Subject: [PATCH 34/48] Add 10-character error code system for system errors Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../api/common/error/SurfCoreSystemError.kt | 1 + .../SurfCoreSystemErrorRepository.kt | 24 +++++++++++++++++++ .../service/SurfCoreSystemErrorServiceImpl.kt | 3 +++ .../table/SurfCoreSystemErrorTable.kt | 1 + .../error/SurfCoreSystemErrorService.kt | 9 +++++++ 5 files changed, 38 insertions(+) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt index dad4296..c773e5a 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -5,6 +5,7 @@ import java.util.* data class SurfCoreSystemError( val uuid: UUID, + val errorCode: String, val errorMessage: String, val stacktrace: String, val location: String, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 895d817..1212795 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -1,6 +1,7 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq @@ -37,6 +38,7 @@ class SurfCoreSystemErrorRepository { .map { row -> SurfCoreSystemError( uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -50,6 +52,7 @@ class SurfCoreSystemErrorRepository { SurfCoreSystemErrorTable.upsert { it[SurfCoreSystemErrorTable.uuid] = existingError?.uuid ?: UUID.randomUUID() + it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: surfCoreSystemErrorService.generateCode() it[SurfCoreSystemErrorTable.errorMessage] = message it[SurfCoreSystemErrorTable.stacktrace] = stacktrace it[SurfCoreSystemErrorTable.location] = location @@ -68,6 +71,7 @@ class SurfCoreSystemErrorRepository { .map { row -> SurfCoreSystemError( uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -88,6 +92,7 @@ class SurfCoreSystemErrorRepository { .map { row -> SurfCoreSystemError( uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], @@ -105,6 +110,25 @@ class SurfCoreSystemErrorRepository { .map { row -> SurfCoreSystemError( uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + }.firstOrNull() + } + + suspend fun getError(code: String): SurfCoreSystemError? = suspendTransaction { + SurfCoreSystemErrorTable.selectAll() + .where(SurfCoreSystemErrorTable.errorCode eq code) + .map { row -> + SurfCoreSystemError( + uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], errorMessage = row[SurfCoreSystemErrorTable.errorMessage], stacktrace = row[SurfCoreSystemErrorTable.stacktrace], location = row[SurfCoreSystemErrorTable.location], diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt index e6c5346..088ef00 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -23,4 +23,7 @@ class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fall override suspend fun getError(uuid: UUID): SurfCoreSystemError? = surfCoreSystemErrorRepository.getError(uuid) + + override suspend fun getError(code: String): SurfCoreSystemError? = + surfCoreSystemErrorRepository.getError(code) } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt index 79739bd..a08481c 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt @@ -6,6 +6,7 @@ import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTa object SurfCoreSystemErrorTable : LongIdTable("surf_core_system_errors") { val uuid = nativeUuid("error_uuid").uniqueIndex() + val errorCode = varchar("error_code", 10) val errorMessage = text("error_message") val stacktrace = text("stacktrace") val location = varchar("location", 500) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt index 4ce5030..a0c53c4 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt @@ -47,4 +47,13 @@ interface SurfCoreSystemErrorService { suspend fun getAllErrors(): ObjectList suspend fun getError(uuid: UUID): SurfCoreSystemError? + + suspend fun getError(code: String): SurfCoreSystemError? + + fun generateCode(): String { + val chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + return (1..10) + .map { chars[dev.slne.surf.surfapi.core.api.util.random.nextInt(chars.length)] } + .joinToString("") + } } From 494593177e3dd6e0246932de38c16fd2215928fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:42:23 +0000 Subject: [PATCH 35/48] Add unique constraint to error code and implement retry mechanism Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../repository/SurfCoreSystemErrorRepository.kt | 13 ++++++++++++- .../core/fallback/table/SurfCoreSystemErrorTable.kt | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 1212795..f849319 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -20,6 +20,17 @@ import java.util.* val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() class SurfCoreSystemErrorRepository { + private fun generateUniqueCode(): String { + // Generate a unique code with retry mechanism + // Try up to 10 times to generate a unique code + repeat(10) { + val code = surfCoreSystemErrorService.generateCode() + // Return the first generated code; uniqueness will be enforced by the database + return code + } + error("Failed to generate unique error code after 10 attempts") + } + suspend fun logError( message: String, stacktrace: String, @@ -52,7 +63,7 @@ class SurfCoreSystemErrorRepository { SurfCoreSystemErrorTable.upsert { it[SurfCoreSystemErrorTable.uuid] = existingError?.uuid ?: UUID.randomUUID() - it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: surfCoreSystemErrorService.generateCode() + it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: generateUniqueCode() it[SurfCoreSystemErrorTable.errorMessage] = message it[SurfCoreSystemErrorTable.stacktrace] = stacktrace it[SurfCoreSystemErrorTable.location] = location diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt index a08481c..001e714 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt @@ -6,7 +6,7 @@ import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.dao.id.LongIdTa object SurfCoreSystemErrorTable : LongIdTable("surf_core_system_errors") { val uuid = nativeUuid("error_uuid").uniqueIndex() - val errorCode = varchar("error_code", 10) + val errorCode = varchar("error_code", 10).uniqueIndex() val errorMessage = text("error_message") val stacktrace = text("stacktrace") val location = varchar("location", 500) From fc258fb89253bbc9c3473286da53db1b13cb255f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:43:05 +0000 Subject: [PATCH 36/48] Remove ineffective retry mechanism, rely on database uniqueness Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../repository/SurfCoreSystemErrorRepository.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index f849319..1212795 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -20,17 +20,6 @@ import java.util.* val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() class SurfCoreSystemErrorRepository { - private fun generateUniqueCode(): String { - // Generate a unique code with retry mechanism - // Try up to 10 times to generate a unique code - repeat(10) { - val code = surfCoreSystemErrorService.generateCode() - // Return the first generated code; uniqueness will be enforced by the database - return code - } - error("Failed to generate unique error code after 10 attempts") - } - suspend fun logError( message: String, stacktrace: String, @@ -63,7 +52,7 @@ class SurfCoreSystemErrorRepository { SurfCoreSystemErrorTable.upsert { it[SurfCoreSystemErrorTable.uuid] = existingError?.uuid ?: UUID.randomUUID() - it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: generateUniqueCode() + it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: surfCoreSystemErrorService.generateCode() it[SurfCoreSystemErrorTable.errorMessage] = message it[SurfCoreSystemErrorTable.stacktrace] = stacktrace it[SurfCoreSystemErrorTable.location] = location From 102b4077c026bd9aca9e0d0dae93b2dfe3c73927 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:46:29 +0000 Subject: [PATCH 37/48] Use error codes in command and logs for system errors Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../core/common/error/GlobalErrorHandler.kt | 2 +- .../core/paper/command/CoreErrorCommand.kt | 102 +++++++++++++++++- 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index e8fa257..d7bc658 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -26,7 +26,7 @@ object GlobalErrorHandler { server = SurfServer.current().name ) logger.atInfo() - .log("This error has been logged with ID: ${surfCoreSystemError.uuid}") + .log("This error has been logged with code: ${surfCoreSystemError.errorCode} (ID: ${surfCoreSystemError.uuid})") }.onFailure { logger.atSevere().log("Failed to log error to database") } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 67e9efa..a2926ef 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -191,6 +191,98 @@ fun coreErrorCommand() = commandTree("coreerror") { } } } + literalArgument("viewCode") { + stringArgument("code") { + anyExecutorSuspend { executor, args -> + val code: String by args + val error = surfCoreSystemErrorService.getError(code) ?: run { + executor.sendText { + appendErrorPrefix() + error("Der Fehler wurde nicht gefunden.") + } + return@anyExecutorSuspend + } + + executor.sendText { + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") + appendNewline() + primary("Systemfehler Details", TextDecoration.BOLD) + appendNewline() + appendNewline() + appendInfoPrefix() + info("Fehlercode: ") + variableValue(error.errorCode) + appendNewline() + appendInfoPrefix() + info("Fehler-Uuid: ") + variableValue(error.uuid.toString()) + appendNewline() + appendInfoPrefix() + info("Server: ") + variableValue(error.server) + appendNewline() + appendInfoPrefix() + info("Ort: ") + append { + variableValue(error.getLocationClassName()) + hoverEvent(buildText { + variableValue(error.location) + appendNewline() + spacer("Klicke, um den vollständigen Ort zu kopieren.") + }) + clickCopiesToClipboard(error.location) + } + appendNewline() + appendInfoPrefix() + info("Erstmals aufgetreten: ") + variableValue(error.firstOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Zuletzt aufgetreten: ") + variableValue(error.lastOccurred.format(dateTimeFormatter)) + appendNewline() + appendInfoPrefix() + info("Anzahl in den letzten 24h: ") + variableValue(error.occurrenceCount.toString()) + appendNewline() + appendInfoPrefix() + info("Nachricht: ") + appendNewline() + append { + spacer(error.errorMessage.take(50)) + if (error.errorMessage.length > 50) { + spacer("... (gekürzt)") + } + hoverEvent(buildText { + spacer(error.errorMessage) + }) + clickCopiesToClipboard(error.errorMessage) + } + + appendNewline() + appendInfoPrefix() + info("Stacktrace: ") + appendNewline() + append { + spacer(error.stacktrace.take(75)) + if (error.stacktrace.length > 75) { + appendNewline() + spacer("... (gekürzt, ${error.stacktrace.length} Zeichen total)") + } + + hoverEvent(buildText { + spacer(error.stacktrace) + }) + clickCopiesToClipboard(error.stacktrace) + } + appendNewline() + appendNewline() + darkSpacer("*" + "-".repeat(40) + "*") + } + } + } + } literalArgument("view") { uuidArgument("uuid") { anyExecutorSuspend { executor, args -> @@ -211,6 +303,10 @@ fun coreErrorCommand() = commandTree("coreerror") { appendNewline() appendNewline() appendInfoPrefix() + info("Fehlercode: ") + variableValue(error.errorCode) + appendNewline() + appendInfoPrefix() info("Fehler-Uuid: ") variableValue(error.uuid.toString()) appendNewline() @@ -307,8 +403,8 @@ private val systemErrorPagination = Pagination { listOf( buildText { appendInfoPrefix() - variableKey("Fehler-Uuid: ") - variableValue(row.uuid.toString()) + variableKey("Fehlercode: ") + variableValue(row.errorCode) appendSpace() spacer("(${row.occurrenceCount}x)") appendSpace() @@ -316,7 +412,7 @@ private val systemErrorPagination = Pagination { if (row.getLocationClassName().length > 50) { spacer("...") } - clickRunsCommand("/coreerror system view ${row.uuid}") + clickRunsCommand("/coreerror system viewCode ${row.errorCode}") hoverEvent(buildText { spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") appendNewline() From a392fddf721beb0f30fc3f3a188a3fa227c229e4 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 17:59:04 +0100 Subject: [PATCH 38/48] feat: enhance global error handling with detailed logging of exceptions --- .../fallback/repository/SurfCoreSystemErrorRepository.kt | 9 +++++---- .../surf/core/core/common/error/GlobalErrorHandler.kt | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 1212795..f087eed 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -5,7 +5,7 @@ import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert @@ -33,7 +33,7 @@ class SurfCoreSystemErrorRepository { (SurfCoreSystemErrorTable.errorMessage eq message) and (SurfCoreSystemErrorTable.location eq location) and (SurfCoreSystemErrorTable.server eq server) and - (SurfCoreSystemErrorTable.lastOccurred lessEq now.minusDays(1)) + (SurfCoreSystemErrorTable.lastOccurred greaterEq now.minusDays(1)) ) .map { row -> SurfCoreSystemError( @@ -52,7 +52,8 @@ class SurfCoreSystemErrorRepository { SurfCoreSystemErrorTable.upsert { it[SurfCoreSystemErrorTable.uuid] = existingError?.uuid ?: UUID.randomUUID() - it[SurfCoreSystemErrorTable.errorCode] = existingError?.errorCode ?: surfCoreSystemErrorService.generateCode() + it[SurfCoreSystemErrorTable.errorCode] = + existingError?.errorCode ?: surfCoreSystemErrorService.generateCode() it[SurfCoreSystemErrorTable.errorMessage] = message it[SurfCoreSystemErrorTable.stacktrace] = stacktrace it[SurfCoreSystemErrorTable.location] = location @@ -121,7 +122,7 @@ class SurfCoreSystemErrorRepository { ) }.firstOrNull() } - + suspend fun getError(code: String): SurfCoreSystemError? = suspendTransaction { SurfCoreSystemErrorTable.selectAll() .where(SurfCoreSystemErrorTable.errorCode eq code) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt index d7bc658..c566d3d 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt @@ -17,7 +17,8 @@ object GlobalErrorHandler { private fun handleError(source: String, throwable: Throwable) { runCatching { - logger.atSevere().log("Uncaught exception from $source", throwable) + logger.atSevere() + .log("Uncaught exception from $source: ${throwable.message}\n${throwable.stackTraceToString()}") errorScope.launch { runCatching { From 4e588707914b2aeec388ceff8667bd1349d17c87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:02:59 +0000 Subject: [PATCH 39/48] Initial plan From fdc7647f60aa1460a9676241fef0b44e63e1f24d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:06:16 +0000 Subject: [PATCH 40/48] Add filtering support to error list commands Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../api/common/error/SurfCoreErrorFilter.kt | 18 +++ .../common/error/SurfCoreSystemErrorFilter.kt | 22 +++ .../SurfCoreErrorLoggingRepository.kt | 45 ++++++ .../SurfCoreSystemErrorRepository.kt | 63 ++++++++ .../SurfCoreErrorLoggingServiceImpl.kt | 4 + .../service/SurfCoreSystemErrorServiceImpl.kt | 4 + .../error/SurfCoreSystemErrorService.kt | 3 + .../player/SurfCoreErrorLoggingService.kt | 2 + .../core/paper/command/CoreErrorCommand.kt | 138 +++++++++++++++++- 9 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreErrorFilter.kt create mode 100644 surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemErrorFilter.kt diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreErrorFilter.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreErrorFilter.kt new file mode 100644 index 0000000..875fc08 --- /dev/null +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreErrorFilter.kt @@ -0,0 +1,18 @@ +package dev.slne.surf.core.api.common.error + +import java.time.OffsetDateTime +import java.util.* + +data class SurfCoreErrorFilter( + val playerUuid: UUID? = null, + val code: String? = null, + val messageLike: String? = null, + val server: String? = null, + val timestampAfter: OffsetDateTime? = null, + val timestampBefore: OffsetDateTime? = null, + val limit: Int = 50 +) { + companion object { + fun empty() = SurfCoreErrorFilter() + } +} diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemErrorFilter.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemErrorFilter.kt new file mode 100644 index 0000000..df9a04d --- /dev/null +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemErrorFilter.kt @@ -0,0 +1,22 @@ +package dev.slne.surf.core.api.common.error + +import java.time.OffsetDateTime +import java.util.* + +data class SurfCoreSystemErrorFilter( + val uuid: UUID? = null, + val errorCode: String? = null, + val messageLike: String? = null, + val locationLike: String? = null, + val server: String? = null, + val firstOccurredAfter: OffsetDateTime? = null, + val firstOccurredBefore: OffsetDateTime? = null, + val lastOccurredAfter: OffsetDateTime? = null, + val lastOccurredBefore: OffsetDateTime? = null, + val minOccurrenceCount: Int? = null, + val limit: Int = 50 +) { + companion object { + fun empty() = SurfCoreSystemErrorFilter() + } +} diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 12bcbbd..9f26bad 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -1,8 +1,13 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.like import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction @@ -48,6 +53,46 @@ class SurfCoreErrorLoggingRepository { }.toList().toObjectList() } + suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = suspendTransaction { + var query = SurfCoreErrorLogsTable.selectAll() + + filter.playerUuid?.let { + query = query.where(SurfCoreErrorLogsTable.playerUuid eq it) + } + + filter.code?.let { + query = query.andWhere { SurfCoreErrorLogsTable.errorCode eq it } + } + + filter.messageLike?.let { + query = query.andWhere { SurfCoreErrorLogsTable.errorMessage like "%$it%" } + } + + filter.server?.let { + query = query.andWhere { SurfCoreErrorLogsTable.server eq it } + } + + filter.timestampAfter?.let { + query = query.andWhere { SurfCoreErrorLogsTable.timestamp greaterEq it } + } + + filter.timestampBefore?.let { + query = query.andWhere { SurfCoreErrorLogsTable.timestamp lessEq it } + } + + query + .limit(filter.limit) + .map { + SurfCoreError( + playerUuid = it[SurfCoreErrorLogsTable.playerUuid], + code = it[SurfCoreErrorLogsTable.errorCode], + message = it[SurfCoreErrorLogsTable.errorMessage], + server = it[SurfCoreErrorLogsTable.server], + timestamp = it[SurfCoreErrorLogsTable.timestamp] + ) + }.toList().toObjectList() + } + suspend fun getError(code: String): SurfCoreError? = suspendTransaction { SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.errorCode eq code).map { SurfCoreError( diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index f087eed..bf7128c 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -1,11 +1,14 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.like import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert @@ -105,6 +108,66 @@ class SurfCoreSystemErrorRepository { }.toList().toObjectList() } + suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = suspendTransaction { + var query = SurfCoreSystemErrorTable.selectAll() + + filter.uuid?.let { + query = query.where(SurfCoreSystemErrorTable.uuid eq it) + } + + filter.errorCode?.let { + query = query.andWhere { SurfCoreSystemErrorTable.errorCode eq it } + } + + filter.messageLike?.let { + query = query.andWhere { SurfCoreSystemErrorTable.errorMessage like "%$it%" } + } + + filter.locationLike?.let { + query = query.andWhere { SurfCoreSystemErrorTable.location like "%$it%" } + } + + filter.server?.let { + query = query.andWhere { SurfCoreSystemErrorTable.server eq it } + } + + filter.firstOccurredAfter?.let { + query = query.andWhere { SurfCoreSystemErrorTable.firstOccurred greaterEq it } + } + + filter.firstOccurredBefore?.let { + query = query.andWhere { SurfCoreSystemErrorTable.firstOccurred lessEq it } + } + + filter.lastOccurredAfter?.let { + query = query.andWhere { SurfCoreSystemErrorTable.lastOccurred greaterEq it } + } + + filter.lastOccurredBefore?.let { + query = query.andWhere { SurfCoreSystemErrorTable.lastOccurred lessEq it } + } + + filter.minOccurrenceCount?.let { + query = query.andWhere { SurfCoreSystemErrorTable.occurrenceCount greaterEq it } + } + + query + .limit(filter.limit) + .map { row -> + SurfCoreSystemError( + uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + }.toList().toObjectList() + } + suspend fun getError(uuid: UUID): SurfCoreSystemError? = suspendTransaction { SurfCoreSystemErrorTable.selectAll() .where(SurfCoreSystemErrorTable.uuid eq uuid) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt index a34bff2..36fdde2 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt @@ -2,6 +2,7 @@ package dev.slne.surf.core.fallback.service import com.google.auto.service.AutoService import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.core.common.player.SurfCoreErrorLoggingService import dev.slne.surf.core.fallback.repository.surfCoreErrorLoggingRepository import it.unimi.dsi.fastutil.objects.ObjectList @@ -20,6 +21,9 @@ class SurfCoreErrorLoggingServiceImpl : SurfCoreErrorLoggingService, Services.Fa override suspend fun getErrors(playerUuid: UUID): ObjectList = surfCoreErrorLoggingRepository.getErrors(playerUuid) + override suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = + surfCoreErrorLoggingRepository.getErrors(filter) + override suspend fun getError(code: String): SurfCoreError? = surfCoreErrorLoggingRepository.getError(code) } \ No newline at end of file diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt index 088ef00..25d7720 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -2,6 +2,7 @@ package dev.slne.surf.core.fallback.service import com.google.auto.service.AutoService import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.core.core.common.error.SurfCoreSystemErrorService import dev.slne.surf.core.fallback.repository.surfCoreSystemErrorRepository import it.unimi.dsi.fastutil.objects.ObjectList @@ -21,6 +22,9 @@ class SurfCoreSystemErrorServiceImpl : SurfCoreSystemErrorService, Services.Fall override suspend fun getAllErrors(): ObjectList = surfCoreSystemErrorRepository.getAllErrors() + override suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = + surfCoreSystemErrorRepository.getAllErrors(filter) + override suspend fun getError(uuid: UUID): SurfCoreSystemError? = surfCoreSystemErrorRepository.getError(uuid) diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt index a0c53c4..a6480f1 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt @@ -1,6 +1,7 @@ package dev.slne.surf.core.core.common.error import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.surfapi.core.api.util.requiredService import it.unimi.dsi.fastutil.objects.ObjectList import java.util.* @@ -45,6 +46,8 @@ interface SurfCoreSystemErrorService { ): SurfCoreSystemError suspend fun getAllErrors(): ObjectList + + suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList suspend fun getError(uuid: UUID): SurfCoreSystemError? diff --git a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt index 310f8c4..e492128 100644 --- a/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt @@ -1,6 +1,7 @@ package dev.slne.surf.core.core.common.player import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.surfapi.core.api.util.random import dev.slne.surf.surfapi.core.api.util.requiredService import it.unimi.dsi.fastutil.objects.ObjectList @@ -17,6 +18,7 @@ interface SurfCoreErrorLoggingService { ): SurfCoreError suspend fun getErrors(playerUuid: UUID): ObjectList + suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList suspend fun getError(code: String): SurfCoreError? fun generateCode(): String { diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index a2926ef..b635d61 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -2,9 +2,12 @@ package dev.slne.surf.core.paper.command import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher import com.github.shynixn.mccoroutine.folia.launch +import dev.jorel.commandapi.arguments.MapArgumentBuilder import dev.jorel.commandapi.kotlindsl.* import dev.slne.surf.core.api.common.error.SurfCoreError +import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.api.common.error.SurfCoreSystemError +import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.core.core.common.error.GlobalErrorHandler import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.core.common.player.surfCoreErrorLoggingService @@ -19,6 +22,7 @@ import dev.slne.surf.surfapi.core.api.messages.pagination.Pagination import dev.slne.surf.surfapi.core.api.service.PlayerLookupService import dev.slne.surf.surfapi.core.api.util.dateTimeFormatter import net.kyori.adventure.text.format.TextDecoration +import java.time.OffsetDateTime import java.util.* fun coreErrorCommand() = commandTree("coreerror") { @@ -93,6 +97,54 @@ fun coreErrorCommand() = commandTree("coreerror") { } } } + literalArgument("list") { + optionalArgument( + MapArgumentBuilder("query", ' ') + .withKeyMapper { it } + .withValueMapper { it } + .withKeyList( + listOf( + "--player", + "--code", + "--message", + "--server", + "--after", + "--before", + "--limit", + "--page" + ) + ) + .withoutValueList() + .build() + ) + + anyExecutorSuspend { executor, args -> + val query: Map? by args + + executor.sendText { + appendInfoPrefix() + info("Es wird nach Ergebnissen gesucht...") + } + + val filter = query?.parsePlayerErrorFilters() ?: SurfCoreErrorFilter.empty() + val page = query?.get("--page")?.toIntOrNull() ?: 1 + + val errors = surfCoreErrorLoggingService.getErrors(filter) + + if (errors.isEmpty()) { + executor.sendText { + appendErrorPrefix() + error("Es wurden keine Ergebnisse gefunden.") + } + return@anyExecutorSuspend + } + + executor.sendText { + appendNewline() + append(playerErrorPagination.renderComponent(errors, page)) + } + } + } } literalArgument("system") { @@ -182,12 +234,54 @@ fun coreErrorCommand() = commandTree("coreerror") { } literalArgument("list") { - anyExecutorSuspend { executor, _ -> - val errors = surfCoreSystemErrorService.getAllErrors() + optionalArgument( + MapArgumentBuilder("query", ' ') + .withKeyMapper { it } + .withValueMapper { it } + .withKeyList( + listOf( + "--uuid", + "--code", + "--message", + "--location", + "--server", + "--firstAfter", + "--firstBefore", + "--lastAfter", + "--lastBefore", + "--minCount", + "--limit", + "--page" + ) + ) + .withoutValueList() + .build() + ) + + anyExecutorSuspend { executor, args -> + val query: Map? by args + + executor.sendText { + appendInfoPrefix() + info("Es wird nach Ergebnissen gesucht...") + } + + val filter = query?.parseSystemErrorFilters() ?: SurfCoreSystemErrorFilter.empty() + val page = query?.get("--page")?.toIntOrNull() ?: 1 + + val errors = surfCoreSystemErrorService.getAllErrors(filter) + + if (errors.isEmpty()) { + executor.sendText { + appendErrorPrefix() + error("Es wurden keine Ergebnisse gefunden.") + } + return@anyExecutorSuspend + } executor.sendText { appendNewline() - append(systemErrorPagination.renderComponent(errors)) + append(systemErrorPagination.renderComponent(errors, page)) } } } @@ -423,3 +517,41 @@ private val systemErrorPagination = Pagination { } } +private suspend fun Map.parsePlayerErrorFilters(): SurfCoreErrorFilter { + val playerUuid = this["--player"]?.let { PlayerLookupService.getUuid(it) } + + fun parseDateTime(input: String): OffsetDateTime? { + return runCatching { OffsetDateTime.parse(input) }.getOrNull() + } + + return SurfCoreErrorFilter( + playerUuid = playerUuid, + code = this["--code"], + messageLike = this["--message"], + server = this["--server"], + timestampAfter = this["--after"]?.let { parseDateTime(it) }, + timestampBefore = this["--before"]?.let { parseDateTime(it) }, + limit = this["--limit"]?.toIntOrNull() ?: 50 + ) +} + +private fun Map.parseSystemErrorFilters(): SurfCoreSystemErrorFilter { + fun parseDateTime(input: String): OffsetDateTime? { + return runCatching { OffsetDateTime.parse(input) }.getOrNull() + } + + return SurfCoreSystemErrorFilter( + uuid = this["--uuid"]?.let { runCatching { UUID.fromString(it) }.getOrNull() }, + errorCode = this["--code"], + messageLike = this["--message"], + locationLike = this["--location"], + server = this["--server"], + firstOccurredAfter = this["--firstAfter"]?.let { parseDateTime(it) }, + firstOccurredBefore = this["--firstBefore"]?.let { parseDateTime(it) }, + lastOccurredAfter = this["--lastAfter"]?.let { parseDateTime(it) }, + lastOccurredBefore = this["--lastBefore"]?.let { parseDateTime(it) }, + minOccurrenceCount = this["--minCount"]?.toIntOrNull(), + limit = this["--limit"]?.toIntOrNull() ?: 50 + ) +} + From f693fd8ab5eb02fceab403a3a2e41982134eb3d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:08:18 +0000 Subject: [PATCH 41/48] Fix query building to use proper where clause construction Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../SurfCoreErrorLoggingRepository.kt | 21 +++++++----- .../SurfCoreSystemErrorRepository.kt | 33 ++++++++++++------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 9f26bad..880cdcc 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -56,30 +56,35 @@ class SurfCoreErrorLoggingRepository { suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = suspendTransaction { var query = SurfCoreErrorLogsTable.selectAll() - filter.playerUuid?.let { - query = query.where(SurfCoreErrorLogsTable.playerUuid eq it) - } + var whereCondition = filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } filter.code?.let { - query = query.andWhere { SurfCoreErrorLogsTable.errorCode eq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.errorCode eq it) } + ?: (SurfCoreErrorLogsTable.errorCode eq it) } filter.messageLike?.let { - query = query.andWhere { SurfCoreErrorLogsTable.errorMessage like "%$it%" } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.errorMessage like "%$it%") } + ?: (SurfCoreErrorLogsTable.errorMessage like "%$it%") } filter.server?.let { - query = query.andWhere { SurfCoreErrorLogsTable.server eq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.server eq it) } + ?: (SurfCoreErrorLogsTable.server eq it) } filter.timestampAfter?.let { - query = query.andWhere { SurfCoreErrorLogsTable.timestamp greaterEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.timestamp greaterEq it) } + ?: (SurfCoreErrorLogsTable.timestamp greaterEq it) } filter.timestampBefore?.let { - query = query.andWhere { SurfCoreErrorLogsTable.timestamp lessEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.timestamp lessEq it) } + ?: (SurfCoreErrorLogsTable.timestamp lessEq it) } + whereCondition?.let { query = query.where(it) } + query .limit(filter.limit) .map { diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index bf7128c..680499a 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -111,46 +111,55 @@ class SurfCoreSystemErrorRepository { suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = suspendTransaction { var query = SurfCoreSystemErrorTable.selectAll() - filter.uuid?.let { - query = query.where(SurfCoreSystemErrorTable.uuid eq it) - } + var whereCondition = filter.uuid?.let { SurfCoreSystemErrorTable.uuid eq it } filter.errorCode?.let { - query = query.andWhere { SurfCoreSystemErrorTable.errorCode eq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.errorCode eq it) } + ?: (SurfCoreSystemErrorTable.errorCode eq it) } filter.messageLike?.let { - query = query.andWhere { SurfCoreSystemErrorTable.errorMessage like "%$it%" } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.errorMessage like "%$it%") } + ?: (SurfCoreSystemErrorTable.errorMessage like "%$it%") } filter.locationLike?.let { - query = query.andWhere { SurfCoreSystemErrorTable.location like "%$it%" } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.location like "%$it%") } + ?: (SurfCoreSystemErrorTable.location like "%$it%") } filter.server?.let { - query = query.andWhere { SurfCoreSystemErrorTable.server eq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.server eq it) } + ?: (SurfCoreSystemErrorTable.server eq it) } filter.firstOccurredAfter?.let { - query = query.andWhere { SurfCoreSystemErrorTable.firstOccurred greaterEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.firstOccurred greaterEq it) } + ?: (SurfCoreSystemErrorTable.firstOccurred greaterEq it) } filter.firstOccurredBefore?.let { - query = query.andWhere { SurfCoreSystemErrorTable.firstOccurred lessEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.firstOccurred lessEq it) } + ?: (SurfCoreSystemErrorTable.firstOccurred lessEq it) } filter.lastOccurredAfter?.let { - query = query.andWhere { SurfCoreSystemErrorTable.lastOccurred greaterEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.lastOccurred greaterEq it) } + ?: (SurfCoreSystemErrorTable.lastOccurred greaterEq it) } filter.lastOccurredBefore?.let { - query = query.andWhere { SurfCoreSystemErrorTable.lastOccurred lessEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.lastOccurred lessEq it) } + ?: (SurfCoreSystemErrorTable.lastOccurred lessEq it) } filter.minOccurrenceCount?.let { - query = query.andWhere { SurfCoreSystemErrorTable.occurrenceCount greaterEq it } + whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.occurrenceCount greaterEq it) } + ?: (SurfCoreSystemErrorTable.occurrenceCount greaterEq it) } + whereCondition?.let { query = query.where(it) } + query .limit(filter.limit) .map { row -> From 40023b29f7f55d22a1c91414712292d5736760ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:10:18 +0000 Subject: [PATCH 42/48] Improve code quality: Extract common functions and reduce duplication Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../SurfCoreErrorLoggingRepository.kt | 22 ++++++------ .../SurfCoreSystemErrorRepository.kt | 34 ++++++++----------- .../core/paper/command/CoreErrorCommand.kt | 12 +++---- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 880cdcc..e24554a 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -3,6 +3,8 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.SqlExpressionBuilder import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq @@ -21,6 +23,9 @@ import java.util.* val surfCoreErrorLoggingRepository = SurfCoreErrorLoggingRepository() +private fun Op?.andCondition(newCondition: Op): Op = + this?.let { it and newCondition } ?: newCondition + class SurfCoreErrorLoggingRepository { suspend fun logError( playerUuid: UUID, @@ -56,31 +61,26 @@ class SurfCoreErrorLoggingRepository { suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = suspendTransaction { var query = SurfCoreErrorLogsTable.selectAll() - var whereCondition = filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } + var whereCondition: Op? = filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } filter.code?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.errorCode eq it) } - ?: (SurfCoreErrorLogsTable.errorCode eq it) + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorCode eq it) } filter.messageLike?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.errorMessage like "%$it%") } - ?: (SurfCoreErrorLogsTable.errorMessage like "%$it%") + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorMessage like "%$it%") } filter.server?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.server eq it) } - ?: (SurfCoreErrorLogsTable.server eq it) + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.server eq it) } filter.timestampAfter?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.timestamp greaterEq it) } - ?: (SurfCoreErrorLogsTable.timestamp greaterEq it) + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp greaterEq it) } filter.timestampBefore?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreErrorLogsTable.timestamp lessEq it) } - ?: (SurfCoreErrorLogsTable.timestamp lessEq it) + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp lessEq it) } whereCondition?.let { query = query.where(it) } diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 680499a..1011f1e 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -4,6 +4,8 @@ import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.SqlExpressionBuilder import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq @@ -22,6 +24,9 @@ import java.util.* val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() +private fun Op?.andCondition(newCondition: Op): Op = + this?.let { it and newCondition } ?: newCondition + class SurfCoreSystemErrorRepository { suspend fun logError( message: String, @@ -111,51 +116,42 @@ class SurfCoreSystemErrorRepository { suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = suspendTransaction { var query = SurfCoreSystemErrorTable.selectAll() - var whereCondition = filter.uuid?.let { SurfCoreSystemErrorTable.uuid eq it } + var whereCondition: Op? = filter.uuid?.let { SurfCoreSystemErrorTable.uuid eq it } filter.errorCode?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.errorCode eq it) } - ?: (SurfCoreSystemErrorTable.errorCode eq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.errorCode eq it) } filter.messageLike?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.errorMessage like "%$it%") } - ?: (SurfCoreSystemErrorTable.errorMessage like "%$it%") + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.errorMessage like "%$it%") } filter.locationLike?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.location like "%$it%") } - ?: (SurfCoreSystemErrorTable.location like "%$it%") + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.location like "%$it%") } filter.server?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.server eq it) } - ?: (SurfCoreSystemErrorTable.server eq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.server eq it) } filter.firstOccurredAfter?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.firstOccurred greaterEq it) } - ?: (SurfCoreSystemErrorTable.firstOccurred greaterEq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred greaterEq it) } filter.firstOccurredBefore?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.firstOccurred lessEq it) } - ?: (SurfCoreSystemErrorTable.firstOccurred lessEq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred lessEq it) } filter.lastOccurredAfter?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.lastOccurred greaterEq it) } - ?: (SurfCoreSystemErrorTable.lastOccurred greaterEq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred greaterEq it) } filter.lastOccurredBefore?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.lastOccurred lessEq it) } - ?: (SurfCoreSystemErrorTable.lastOccurred lessEq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred lessEq it) } filter.minOccurrenceCount?.let { - whereCondition = whereCondition?.let { cond -> cond and (SurfCoreSystemErrorTable.occurrenceCount greaterEq it) } - ?: (SurfCoreSystemErrorTable.occurrenceCount greaterEq it) + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.occurrenceCount greaterEq it) } whereCondition?.let { query = query.where(it) } diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index b635d61..14d7319 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -517,12 +517,12 @@ private val systemErrorPagination = Pagination { } } +private fun parseDateTime(input: String): OffsetDateTime? { + return runCatching { OffsetDateTime.parse(input) }.getOrNull() +} + private suspend fun Map.parsePlayerErrorFilters(): SurfCoreErrorFilter { val playerUuid = this["--player"]?.let { PlayerLookupService.getUuid(it) } - - fun parseDateTime(input: String): OffsetDateTime? { - return runCatching { OffsetDateTime.parse(input) }.getOrNull() - } return SurfCoreErrorFilter( playerUuid = playerUuid, @@ -536,10 +536,6 @@ private suspend fun Map.parsePlayerErrorFilters(): SurfCoreError } private fun Map.parseSystemErrorFilters(): SurfCoreSystemErrorFilter { - fun parseDateTime(input: String): OffsetDateTime? { - return runCatching { OffsetDateTime.parse(input) }.getOrNull() - } - return SurfCoreSystemErrorFilter( uuid = this["--uuid"]?.let { runCatching { UUID.fromString(it) }.getOrNull() }, errorCode = this["--code"], From 69a28445f44e009ec339365a85685255df035d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:11:16 +0000 Subject: [PATCH 43/48] Extract andCondition helper to shared utility file Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com> --- .../slne/surf/core/fallback/repository/QueryUtils.kt | 12 ++++++++++++ .../repository/SurfCoreErrorLoggingRepository.kt | 4 ---- .../repository/SurfCoreSystemErrorRepository.kt | 3 --- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt new file mode 100644 index 0000000..6950f66 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt @@ -0,0 +1,12 @@ +package dev.slne.surf.core.fallback.repository + +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and + +/** + * Helper extension function to chain WHERE conditions. + * If the receiver is null, returns the new condition. + * Otherwise, combines the existing condition with the new one using AND. + */ +internal fun Op?.andCondition(newCondition: Op): Op = + this?.let { it and newCondition } ?: newCondition diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index e24554a..0ae4027 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -5,7 +5,6 @@ import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.SqlExpressionBuilder -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq @@ -23,9 +22,6 @@ import java.util.* val surfCoreErrorLoggingRepository = SurfCoreErrorLoggingRepository() -private fun Op?.andCondition(newCondition: Op): Op = - this?.let { it and newCondition } ?: newCondition - class SurfCoreErrorLoggingRepository { suspend fun logError( playerUuid: UUID, diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 1011f1e..15671ae 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -24,9 +24,6 @@ import java.util.* val surfCoreSystemErrorRepository = SurfCoreSystemErrorRepository() -private fun Op?.andCondition(newCondition: Op): Op = - this?.let { it and newCondition } ?: newCondition - class SurfCoreSystemErrorRepository { suspend fun logError( message: String, From d49107da7648aa26d57e27fbd679c80da0793419 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 18:14:20 +0100 Subject: [PATCH 44/48] feat: rename QueryUtils.kt to query-util.kt and streamline error filtering logic --- .../core/fallback/repository/QueryUtils.kt | 12 -- .../SurfCoreSystemErrorRepository.kt | 114 +++++++++--------- .../core/fallback/repository/query-util.kt | 7 ++ 3 files changed, 66 insertions(+), 67 deletions(-) delete mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt create mode 100644 surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/query-util.kt diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt deleted file mode 100644 index 6950f66..0000000 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/QueryUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.slne.surf.core.fallback.repository - -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and - -/** - * Helper extension function to chain WHERE conditions. - * If the receiver is null, returns the new condition. - * Otherwise, combines the existing condition with the new one using AND. - */ -internal fun Op?.andCondition(newCondition: Op): Op = - this?.let { it and newCondition } ?: newCondition diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt index 15671ae..6d2e136 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -4,13 +4,7 @@ import dev.slne.surf.core.api.common.error.SurfCoreSystemError import dev.slne.surf.core.api.common.error.SurfCoreSystemErrorFilter import dev.slne.surf.core.core.common.error.surfCoreSystemErrorService import dev.slne.surf.core.fallback.table.SurfCoreSystemErrorTable -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.SqlExpressionBuilder -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.like +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.* import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.upsert @@ -110,65 +104,75 @@ class SurfCoreSystemErrorRepository { }.toList().toObjectList() } - suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = suspendTransaction { - var query = SurfCoreSystemErrorTable.selectAll() + suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = + suspendTransaction { + var query = SurfCoreSystemErrorTable.selectAll() - var whereCondition: Op? = filter.uuid?.let { SurfCoreSystemErrorTable.uuid eq it } + var whereCondition: Op? = + filter.uuid?.let { SurfCoreSystemErrorTable.uuid eq it } - filter.errorCode?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.errorCode eq it) - } - - filter.messageLike?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.errorMessage like "%$it%") - } + filter.errorCode?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.errorCode eq it) + } - filter.locationLike?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.location like "%$it%") - } + filter.messageLike?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.errorMessage like "%$it%") + } - filter.server?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.server eq it) - } + filter.locationLike?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.location like "%$it%") + } - filter.firstOccurredAfter?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred greaterEq it) - } + filter.server?.let { + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.server eq it) + } - filter.firstOccurredBefore?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred lessEq it) - } + filter.firstOccurredAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred greaterEq it) + } - filter.lastOccurredAfter?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred greaterEq it) - } + filter.firstOccurredBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred lessEq it) + } - filter.lastOccurredBefore?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred lessEq it) - } + filter.lastOccurredAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred greaterEq it) + } - filter.minOccurrenceCount?.let { - whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.occurrenceCount greaterEq it) - } + filter.lastOccurredBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred lessEq it) + } - whereCondition?.let { query = query.where(it) } + filter.minOccurrenceCount?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.occurrenceCount greaterEq it) + } - query - .limit(filter.limit) - .map { row -> - SurfCoreSystemError( - uuid = row[SurfCoreSystemErrorTable.uuid], - errorCode = row[SurfCoreSystemErrorTable.errorCode], - errorMessage = row[SurfCoreSystemErrorTable.errorMessage], - stacktrace = row[SurfCoreSystemErrorTable.stacktrace], - location = row[SurfCoreSystemErrorTable.location], - server = row[SurfCoreSystemErrorTable.server], - firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], - lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], - occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] - ) - }.toList().toObjectList() - } + whereCondition?.let { query = query.where(it) } + + query + .limit(filter.limit) + .map { row -> + SurfCoreSystemError( + uuid = row[SurfCoreSystemErrorTable.uuid], + errorCode = row[SurfCoreSystemErrorTable.errorCode], + errorMessage = row[SurfCoreSystemErrorTable.errorMessage], + stacktrace = row[SurfCoreSystemErrorTable.stacktrace], + location = row[SurfCoreSystemErrorTable.location], + server = row[SurfCoreSystemErrorTable.server], + firstOccurred = row[SurfCoreSystemErrorTable.firstOccurred], + lastOccurred = row[SurfCoreSystemErrorTable.lastOccurred], + occurrenceCount = row[SurfCoreSystemErrorTable.occurrenceCount] + ) + }.toList().toObjectList() + } suspend fun getError(uuid: UUID): SurfCoreSystemError? = suspendTransaction { SurfCoreSystemErrorTable.selectAll() diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/query-util.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/query-util.kt new file mode 100644 index 0000000..9f871d5 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/query-util.kt @@ -0,0 +1,7 @@ +package dev.slne.surf.core.fallback.repository + +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.and + +fun Op?.andCondition(newCondition: Op): Op = + this?.let { it and newCondition } ?: newCondition From cfb02b5cb29a2d6760aebcd0df37e9656d000ff6 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 18:23:21 +0100 Subject: [PATCH 45/48] feat: streamline error handling logic in CoreErrorCommand and SurfCoreErrorLoggingRepository --- .../SurfCoreErrorLoggingRepository.kt | 74 +++++++++--------- .../core/paper/command/CoreErrorCommand.kt | 77 ++++++++++--------- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt index 0ae4027..acfe546 100644 --- a/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -3,12 +3,7 @@ package dev.slne.surf.core.fallback.repository import dev.slne.surf.core.api.common.error.SurfCoreError import dev.slne.surf.core.api.common.error.SurfCoreErrorFilter import dev.slne.surf.core.fallback.table.SurfCoreErrorLogsTable -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.Op -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.SqlExpressionBuilder -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.eq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.greaterEq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.lessEq -import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.like +import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.core.* import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.insert import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.selectAll import dev.slne.surf.database.libs.org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction @@ -54,45 +49,50 @@ class SurfCoreErrorLoggingRepository { }.toList().toObjectList() } - suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = suspendTransaction { - var query = SurfCoreErrorLogsTable.selectAll() + suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = + suspendTransaction { + var query = SurfCoreErrorLogsTable.selectAll() - var whereCondition: Op? = filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } + var whereCondition: Op? = + filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } - filter.code?.let { - whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorCode eq it) - } + filter.code?.let { + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorCode eq it) + } - filter.messageLike?.let { - whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorMessage like "%$it%") - } + filter.messageLike?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.errorMessage like "%$it%") + } - filter.server?.let { - whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.server eq it) - } + filter.server?.let { + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.server eq it) + } - filter.timestampAfter?.let { - whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp greaterEq it) - } + filter.timestampAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp greaterEq it) + } - filter.timestampBefore?.let { - whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp lessEq it) - } + filter.timestampBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp lessEq it) + } - whereCondition?.let { query = query.where(it) } + whereCondition?.let { query = query.where(it) } - query - .limit(filter.limit) - .map { - SurfCoreError( - playerUuid = it[SurfCoreErrorLogsTable.playerUuid], - code = it[SurfCoreErrorLogsTable.errorCode], - message = it[SurfCoreErrorLogsTable.errorMessage], - server = it[SurfCoreErrorLogsTable.server], - timestamp = it[SurfCoreErrorLogsTable.timestamp] - ) - }.toList().toObjectList() - } + query + .limit(filter.limit) + .map { + SurfCoreError( + playerUuid = it[SurfCoreErrorLogsTable.playerUuid], + code = it[SurfCoreErrorLogsTable.errorCode], + message = it[SurfCoreErrorLogsTable.errorMessage], + server = it[SurfCoreErrorLogsTable.server], + timestamp = it[SurfCoreErrorLogsTable.timestamp] + ) + }.toList().toObjectList() + } suspend fun getError(code: String): SurfCoreError? = suspendTransaction { SurfCoreErrorLogsTable.selectAll().where(SurfCoreErrorLogsTable.errorCode eq code).map { diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 14d7319..63827a1 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -116,32 +116,32 @@ fun coreErrorCommand() = commandTree("coreerror") { ) .withoutValueList() .build() - ) + ) { + anyExecutorSuspend { executor, args -> + val query: Map? by args - anyExecutorSuspend { executor, args -> - val query: Map? by args + executor.sendText { + appendInfoPrefix() + info("Es wird nach Ergebnissen gesucht...") + } - executor.sendText { - appendInfoPrefix() - info("Es wird nach Ergebnissen gesucht...") - } + val filter = query?.parsePlayerErrorFilters() ?: SurfCoreErrorFilter.empty() + val page = query?.get("--page")?.toIntOrNull() ?: 1 - val filter = query?.parsePlayerErrorFilters() ?: SurfCoreErrorFilter.empty() - val page = query?.get("--page")?.toIntOrNull() ?: 1 + val errors = surfCoreErrorLoggingService.getErrors(filter) - val errors = surfCoreErrorLoggingService.getErrors(filter) + if (errors.isEmpty()) { + executor.sendText { + appendErrorPrefix() + error("Es wurden keine Ergebnisse gefunden.") + } + return@anyExecutorSuspend + } - if (errors.isEmpty()) { executor.sendText { - appendErrorPrefix() - error("Es wurden keine Ergebnisse gefunden.") + appendNewline() + append(playerErrorPagination.renderComponent(errors, page)) } - return@anyExecutorSuspend - } - - executor.sendText { - appendNewline() - append(playerErrorPagination.renderComponent(errors, page)) } } } @@ -256,32 +256,33 @@ fun coreErrorCommand() = commandTree("coreerror") { ) .withoutValueList() .build() - ) + ) { + anyExecutorSuspend { executor, args -> + val query: Map? by args - anyExecutorSuspend { executor, args -> - val query: Map? by args + executor.sendText { + appendInfoPrefix() + info("Es wird nach Ergebnissen gesucht...") + } - executor.sendText { - appendInfoPrefix() - info("Es wird nach Ergebnissen gesucht...") - } + val filter = + query?.parseSystemErrorFilters() ?: SurfCoreSystemErrorFilter.empty() + val page = query?.get("--page")?.toIntOrNull() ?: 1 - val filter = query?.parseSystemErrorFilters() ?: SurfCoreSystemErrorFilter.empty() - val page = query?.get("--page")?.toIntOrNull() ?: 1 + val errors = surfCoreSystemErrorService.getAllErrors(filter) - val errors = surfCoreSystemErrorService.getAllErrors(filter) + if (errors.isEmpty()) { + executor.sendText { + appendErrorPrefix() + error("Es wurden keine Ergebnisse gefunden.") + } + return@anyExecutorSuspend + } - if (errors.isEmpty()) { executor.sendText { - appendErrorPrefix() - error("Es wurden keine Ergebnisse gefunden.") + appendNewline() + append(systemErrorPagination.renderComponent(errors, page)) } - return@anyExecutorSuspend - } - - executor.sendText { - appendNewline() - append(systemErrorPagination.renderComponent(errors, page)) } } } From 5607948f307f8a6f55f436359798ca3f8a7f0464 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 8 Feb 2026 18:26:45 +0100 Subject: [PATCH 46/48] feat: add error retrieval functionality for player and system errors in CoreErrorCommand --- .../core/paper/command/CoreErrorCommand.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt index 63827a1..8d47821 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -144,6 +144,15 @@ fun coreErrorCommand() = commandTree("coreerror") { } } } + + anyExecutorSuspend { executor, _ -> + val errors = surfCoreErrorLoggingService.getErrors(SurfCoreErrorFilter.empty()) + + executor.sendText { + appendNewline() + append(playerErrorPagination.renderComponent(errors)) + } + } } } @@ -285,6 +294,15 @@ fun coreErrorCommand() = commandTree("coreerror") { } } } + + anyExecutorSuspend { executor, _ -> + val errors = surfCoreSystemErrorService.getAllErrors() + + executor.sendText { + appendNewline() + append(systemErrorPagination.renderComponent(errors)) + } + } } literalArgument("viewCode") { stringArgument("code") { From 62169e0a497e7776ee37dcac0f74af377e23ee14 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 22 Feb 2026 10:17:39 +0100 Subject: [PATCH 47/48] feat: notify players of background system restart during proxy shutdown --- .../core/velocity/listener/VelocityServerListener.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/VelocityServerListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/VelocityServerListener.kt index 594929c..907392f 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/VelocityServerListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/VelocityServerListener.kt @@ -7,6 +7,7 @@ import com.velocitypowered.api.event.proxy.ProxyPreShutdownEvent import dev.slne.surf.core.api.common.server.SurfProxyServer import dev.slne.surf.core.api.common.server.connection.SurfProxyServerConnectionResult import dev.slne.surf.core.api.common.surfCoreApi +import dev.slne.surf.core.api.common.util.sendText import dev.slne.surf.core.api.velocity.util.surfPlayer import dev.slne.surf.core.core.common.server.surfServerService import dev.slne.surf.core.velocity.plugin @@ -17,6 +18,7 @@ import it.unimi.dsi.fastutil.objects.ObjectSet import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.supervisorScope +import net.kyori.adventure.text.format.TextDecoration import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger @@ -27,6 +29,15 @@ object VelocityServerListener { fun onPreShutdown(event: ProxyPreShutdownEvent, continuation: Continuation) { val currentProxy = SurfProxyServer.current() + surfCoreApi.getOnlinePlayers().forEach { + it.sendText { + appendInfoPrefix() + error("SYSTEM-NEUSTART", TextDecoration.BOLD) + spacer(":") + spacer("Derzeit werden Hintergrundsysteme neugestartet. Bitte habt Verständnis, sollten in diesem Zeitraum Probleme auftreten!") + } + } + val targetProxies = surfServerService.proxyServers .filter { it.name != currentProxy.name } .sortedBy { it.getPlayerCount() } From dccb0b92fae3b4ad2d9ebc1f059c9a99695a6759 Mon Sep 17 00:00:00 2001 From: TheBjoRedCraft Date: Sun, 22 Feb 2026 10:51:14 +0100 Subject: [PATCH 48/48] feat: improve error logging and fix variable naming inconsistencies --- .../slne/surf/core/api/common/SurfCoreApi.kt | 3 +-- .../core/paper/api/SurfCoreApiPaperImpl.kt | 4 ++-- .../paper/listener/PlayerConnectListener.kt | 24 +++++++++---------- .../slne/surf/core/velocity/VelocityMain.kt | 5 +--- .../velocity/api/SurfCoreApiVelocityImpl.kt | 4 ++-- .../velocity/auth/AuthenticationListener.kt | 4 ++-- 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt index 89f4393..6cf54c7 100644 --- a/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt @@ -52,8 +52,7 @@ interface SurfCoreApi { suspend fun logErrorAwaiting(playerUuid: UUID, message: String, server: String): SurfCoreError fun generateCode(): String - - fun sendPlayer(player: SurfPlayer, server: SurfServer) + fun sendPlayer(player: SurfPlayer, server: CommonSurfServer) /** diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt index 055fb94..023fee3 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt @@ -31,12 +31,12 @@ class SurfCoreApiPaperImpl : SurfCoreApiImpl(), Services.Fallback { } } - override fun logError(playerUUid: UUID, message: String): String { + override fun logError(playerUuid: UUID, message: String): String { val code = surfCoreErrorLoggingService.generateCode() plugin.launch { surfCoreErrorLoggingService.logError( - playerUUid, + playerUuid, code, message, SurfServer.current().name diff --git a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt index 3d5cfa6..d5df6c4 100644 --- a/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt @@ -1,8 +1,8 @@ package dev.slne.surf.core.paper.listener -import dev.slne.surf.core.api.common.surfCoreApi import com.github.shynixn.mccoroutine.folia.launch import dev.slne.surf.core.api.common.server.connection.SurfProxyServerConnectionResult +import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.api.paper.util.surfPlayer import dev.slne.surf.core.core.common.player.surfPlayerService import dev.slne.surf.core.core.common.redis.redisApi @@ -11,15 +11,13 @@ import dev.slne.surf.core.core.common.redis.watcher.PlayerProxyConnectionResultW import dev.slne.surf.core.core.common.util.formatMillis import dev.slne.surf.core.core.common.util.renderErrorCodeDisconnectMessage import dev.slne.surf.core.paper.permission.PermissionRegistry -import dev.slne.surf.surfapi.core.api.messages.adventure.buildText -import dev.slne.surf.surfapi.core.api.messages.adventure.clickCopiesToClipboard -import dev.slne.surf.surfapi.core.api.messages.adventure.sendText import dev.slne.surf.core.paper.plugin import dev.slne.surf.surfapi.bukkit.api.command.util.idOrThrow import dev.slne.surf.surfapi.core.api.messages.adventure.* import io.papermc.paper.event.connection.configuration.AsyncPlayerConnectionConfigureEvent import io.papermc.paper.event.player.PlayerClientLoadedWorldEvent import io.papermc.paper.event.player.PlayerServerFullCheckEvent +import kotlinx.coroutines.runBlocking import net.luckperms.api.LuckPermsProvider import org.bukkit.event.EventHandler import org.bukkit.event.Listener @@ -80,7 +78,15 @@ object PlayerConnectListener : Listener { if (surfPlayer == null) { plugin.logger.severe("Failed to load player data for player with UUID ${event.connection.audience.uuidOrNull()}. The player will be disconnected.") - event.connection.disconnect(buildDisconnectComponent()) + + runBlocking { + val code = surfCoreApi.logError( + event.connection.audience.uuid(), + "Failed to load player data on AsyncPlayerPreLoginEvent" + ) + + event.connection.disconnect(buildDisconnectComponent(code)) + } } } @@ -114,14 +120,6 @@ object PlayerConnectListener : Listener { plugin.launch { PlayerProxyConnectionResultWatcher.cleanUp(player.uuid) - val code = surfCoreApi.logError( - event.uniqueId, - "Failed to load player data on AsyncPlayerPreLoginEvent" - ) - event.disallow( - AsyncPlayerPreLoginEvent.Result.KICK_OTHER, - buildDisconnectComponent(code) - ) } } diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt index 3248767..53045c1 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt @@ -52,7 +52,6 @@ class VelocityMain @Inject constructor( init { suspendingPluginContainer.initialize(this) - // Install global error handler early GlobalErrorHandler.install() instance = this @@ -60,13 +59,11 @@ class VelocityMain @Inject constructor( redisLoader.load() surfPlayerService.init() surfServerService.init() - authentificationService.init() + authenticationService.init() redisLoader.withListener(VelocityRedisListener) redisLoader.withRequestResponseHandler(SendPlayerToProxyHandler) redisLoader.withRequestResponseHandler(SendPlayerToServerHandler) redisLoader.connect() - authenticationService.init() - redisLoader.connect(VelocitySurfPlayerRedisListener) val server = SurfProxyServer( name = surfServerConfig.serverName, diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt index a9e6846..a3f4a8d 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt @@ -36,12 +36,12 @@ class SurfCoreApiVelocityImpl : SurfCoreApiImpl(), Services.Fallback { } } - override fun logError(playerUUid: UUID, message: String): String { + override fun logError(playerUuid: UUID, message: String): String { val code = surfCoreErrorLoggingService.generateCode() plugin.pluginContainer.launch { surfCoreErrorLoggingService.logError( - playerUUid, + playerUuid, code, message, SurfServer.current().name diff --git a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt index 49fe8a6..da414a0 100644 --- a/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt @@ -72,8 +72,8 @@ object AuthenticationListener { event.player.requestCookie(authenticationService.key) transfers.add(event.player.uniqueId) - authentificationService.continuations[event.player.uniqueId] = continuation - event.player.requestCookie(authentificationService.key) + authenticationService.continuations[event.player.uniqueId] = continuation + event.player.requestCookie(authenticationService.key) } @Subscribe