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 da0b4de..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 @@ -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.CommonSurfServer @@ -39,6 +40,19 @@ 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 generateCode(): String + fun sendPlayer(player: SurfPlayer, server: CommonSurfServer) /** 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..0549e1b --- /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,12 @@ +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, + val timestamp: OffsetDateTime +) 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/SurfCoreSystemError.kt b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt new file mode 100644 index 0000000..c773e5a --- /dev/null +++ b/surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt @@ -0,0 +1,25 @@ +package dev.slne.surf.core.api.common.error + +import java.time.OffsetDateTime +import java.util.* + +data class SurfCoreSystemError( + val uuid: UUID, + val errorCode: String, + val errorMessage: String, + val stacktrace: String, + val location: String, + val server: String, + val firstOccurred: OffsetDateTime, + val lastOccurred: OffsetDateTime, + val occurrenceCount: Int +) { + fun getLocationClassName(): String = + location + .substringBefore(':') + .substringBeforeLast('.') + .substringAfterLast('.') + .substringBefore('$') + + +} 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/DatabaseLoaderImpl.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt index 8b87056..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 @@ -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,9 @@ class DatabaseLoaderImpl : DatabaseLoader, Services.Fallback { SurfPlayerTable, SurfPlayerNameHistoryTable, SurfPlayerIpAddressHistoryTable, - SurfPlayerTexturesHistoryTable + SurfPlayerTexturesHistoryTable, + SurfCoreErrorLogsTable, + SurfCoreSystemErrorTable ) } } 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..acfe546 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt @@ -0,0 +1,108 @@ +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.* +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.time.OffsetDateTime +import java.util.* + +val surfCoreErrorLoggingRepository = SurfCoreErrorLoggingRepository() + +class SurfCoreErrorLoggingRepository { + suspend fun logError( + playerUuid: UUID, + code: String, + 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(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], + timestamp = it[SurfCoreErrorLogsTable.timestamp] + ) + }.toList().toObjectList() + } + + suspend fun getErrors(filter: SurfCoreErrorFilter): ObjectList = + suspendTransaction { + var query = SurfCoreErrorLogsTable.selectAll() + + var whereCondition: Op? = + filter.playerUuid?.let { SurfCoreErrorLogsTable.playerUuid eq it } + + filter.code?.let { + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.errorCode eq it) + } + + filter.messageLike?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.errorMessage like "%$it%") + } + + filter.server?.let { + whereCondition = whereCondition.andCondition(SurfCoreErrorLogsTable.server eq it) + } + + filter.timestampAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp greaterEq it) + } + + filter.timestampBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreErrorLogsTable.timestamp lessEq 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() + } + + 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], + timestamp = it[SurfCoreErrorLogsTable.timestamp] + ) + }.firstOrNull() + } +} \ No newline at end of file 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..6d2e136 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt @@ -0,0 +1,212 @@ +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.* +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 +import java.util.* + +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) and + (SurfCoreSystemErrorTable.lastOccurred greaterEq now.minusDays(1)) + ) + .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() + + 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 + 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( + 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() + + return@suspendTransaction resultError + ?: error("Failed to retrieve error after upsert") + } + + suspend fun getAllErrors(): ObjectList = suspendTransaction { + SurfCoreSystemErrorTable.selectAll() + .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 getAllErrors(filter: SurfCoreSystemErrorFilter): ObjectList = + suspendTransaction { + var query = SurfCoreSystemErrorTable.selectAll() + + 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.locationLike?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.location like "%$it%") + } + + filter.server?.let { + whereCondition = whereCondition.andCondition(SurfCoreSystemErrorTable.server eq it) + } + + filter.firstOccurredAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred greaterEq it) + } + + filter.firstOccurredBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.firstOccurred lessEq it) + } + + filter.lastOccurredAfter?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred greaterEq it) + } + + filter.lastOccurredBefore?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.lastOccurred lessEq it) + } + + filter.minOccurrenceCount?.let { + whereCondition = + whereCondition.andCondition(SurfCoreSystemErrorTable.occurrenceCount greaterEq it) + } + + 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() + .where(SurfCoreSystemErrorTable.uuid eq uuid) + .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], + 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/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 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..36fdde2 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt @@ -0,0 +1,29 @@ +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 +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 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 new file mode 100644 index 0000000..25d7720 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt @@ -0,0 +1,33 @@ +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 +import net.kyori.adventure.util.Services +import java.util.* + +@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) + + 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) + + 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/SurfCoreErrorLogsTable.kt b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt new file mode 100644 index 0000000..a710e8a --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt @@ -0,0 +1,13 @@ +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 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-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 new file mode 100644 index 0000000..001e714 --- /dev/null +++ b/surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt @@ -0,0 +1,17 @@ +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 errorCode = varchar("error_code", 10).uniqueIndex() + 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) +} 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 c9059e6..2bde0af 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,6 +1,7 @@ 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.CommonSurfServer @@ -9,6 +10,7 @@ import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.api.common.server.connection.SurfProxyServerConnectionResult import dev.slne.surf.core.api.common.server.connection.SurfServerConnectResult 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 @@ -102,6 +104,27 @@ abstract class SurfCoreApiImpl : SurfCoreApi { 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 + ) + + override fun generateCode() = surfCoreErrorLoggingService.generateCode() + override suspend fun sendPlayerAwaiting( surfPlayer: SurfPlayer, surfServer: SurfServer 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..c566d3d --- /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,54 @@ +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.* + +object GlobalErrorHandler { + private val errorScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val logger = logger() + + fun install() { + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + handleError(thread.name, throwable) + } + logger.atInfo().log("Global error handler installed") + } + + private fun handleError(source: String, throwable: Throwable) { + runCatching { + logger.atSevere() + .log("Uncaught exception from $source: ${throwable.message}\n${throwable.stackTraceToString()}") + + errorScope.launch { + runCatching { + val surfCoreSystemError = surfCoreSystemErrorService.logError( + throwable = throwable, + server = SurfServer.current().name + ) + logger.atInfo() + .log("This error has been logged with code: ${surfCoreSystemError.errorCode} (ID: ${surfCoreSystemError.uuid})") + }.onFailure { + logger.atSevere().log("Failed to log error to database") + } + } + }.onFailure { + logger.atSevere().log("Error handler failed") + } + } + + fun logError(throwable: Throwable) { + handleError("Manual", throwable) + } + + fun shutdown() { + runBlocking { + runCatching { + errorScope.coroutineContext.job.cancelAndJoin() + 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 new file mode 100644 index 0000000..a6480f1 --- /dev/null +++ b/surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt @@ -0,0 +1,62 @@ +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.* + +val surfCoreSystemErrorService = requiredService() + +fun extractErrorLocation(throwable: Throwable): String { + val stackTrace = throwable.stackTrace + 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.") + } ?: stackTrace.first() + + return "${relevantElement.className}.${relevantElement.methodName}:${relevantElement.lineNumber}" +} + +interface SurfCoreSystemErrorService { + suspend fun logError( + throwable: Throwable, + server: String + ): SurfCoreSystemError { + 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, + location: String, + server: String + ): SurfCoreSystemError + + suspend fun getAllErrors(): ObjectList + + suspend fun getAllErrors(filter: SurfCoreSystemErrorFilter): 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("") + } +} 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..e492128 --- /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,30 @@ +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 +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 getErrors(filter: SurfCoreErrorFilter): 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-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..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,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.clickCopiesToClipboard +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,41 @@ 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.renderErrorCodeDisconnectMessage( + errorCode: String, + titleReason: String, + reason: SurfComponentBuilder.() -> Unit, + footer: SurfComponentBuilder.() -> Unit = { spacer("Sollte das Problem weiterhin bestehen, wende dich bitte an den Support.") } +) = + renderDisconnectMessage(titleReason, { + spacer("Fehlercode: ") + append { + error("#$errorCode") + clickCopiesToClipboard(errorCode) + } + appendNewline() + append(reason) + }, 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/PaperMain.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt index 9edc5be..8c6196f 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,11 +6,13 @@ 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 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.core.paper.task.surfServerInformationSyncTask import dev.slne.surf.surfapi.bukkit.api.event.register @@ -22,6 +24,8 @@ val plugin get() = JavaPlugin.getPlugin(PaperMain::class.java) class PaperMain : SuspendingJavaPlugin() { override fun onLoad() { + GlobalErrorHandler.install() + surfEventBus.registerListener(SurfServerEventListener) } @@ -38,10 +42,12 @@ class PaperMain : SuspendingJavaPlugin() { networkServerCommand() networkBroadcastCommand() networkSendCommand() + coreErrorCommand() networkServerMaxPlayersCommand() hubCommand() PlayerConnectListener.register() + MCCoroutineExceptionListener.register() surfServerService.addServer( SurfServer.current().copy(maxPlayers = Bukkit.getMaxPlayers()) @@ -60,6 +66,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/api/SurfCoreApiPaperImpl.kt b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt index b0a7dd5..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 @@ -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 @@ -8,14 +9,43 @@ import dev.slne.surf.core.api.common.server.SurfProxyServer import dev.slne.surf.core.api.common.server.SurfServer 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 getCurrentServerDisplayName() = surfServerConfig.serverDisplayName override fun sendPlayer( 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..8d47821 --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt @@ -0,0 +1,572 @@ +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 +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 +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 +import net.kyori.adventure.text.format.TextDecoration +import java.time.OffsetDateTime +import java.util.* + +fun coreErrorCommand() = commandTree("coreerror") { + withPermission(PermissionRegistry.COMMAND_CORE_ERROR) + + 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 { + appendNewline() + append(playerErrorPagination.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("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("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)) + } + } + } + + anyExecutorSuspend { executor, _ -> + val errors = surfCoreErrorLoggingService.getErrors(SurfCoreErrorFilter.empty()) + + executor.sendText { + appendNewline() + append(playerErrorPagination.renderComponent(errors)) + } + } + } + } + + 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 { + appendSuccessPrefix() + success("Triggering duplicate errors...") + } + + repeat(3) { + Thread { + Thread.sleep(100L * it) + throw RuntimeException("Duplicate test error - this should only be logged once") + }.start() + } + } + } + } + + literalArgument("list") { + 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, page)) + } + } + } + + anyExecutorSuspend { executor, _ -> + val errors = surfCoreSystemErrorService.getAllErrors() + + executor.sendText { + appendNewline() + append(systemErrorPagination.renderComponent(errors)) + } + } + } + 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 -> + 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("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) + "*") + } + } + } + } + } +} + +private val playerErrorPagination = Pagination { + title { primary("Fehler-Übersicht") } + rowRenderer { row, _ -> + listOf( + buildText { + appendInfoPrefix() + variableKey("Fehlercode: ") + variableValue(row.code) + appendSpace() + spacer("(${row.timestamp.format(dateTimeFormatter)})") + clickRunsCommand("/coreerror player viewCode ${row.code}") + hoverEvent(buildText { + spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") + }) + } + ) + } +} + +private val systemErrorPagination = Pagination { + title { primary("System-Fehler Übersicht") } + rowRenderer { row, _ -> + listOf( + buildText { + appendInfoPrefix() + variableKey("Fehlercode: ") + variableValue(row.errorCode) + appendSpace() + spacer("(${row.occurrenceCount}x)") + appendSpace() + spacer("- ${row.getLocationClassName().take(50)}") + if (row.getLocationClassName().length > 50) { + spacer("...") + } + clickRunsCommand("/coreerror system viewCode ${row.errorCode}") + hoverEvent(buildText { + spacer("Klicke, um Details zu diesem Fehler anzuzeigen.") + appendNewline() + spacer("Zuletzt: ${row.lastOccurred.format(dateTimeFormatter)}") + }) + } + ) + } +} + +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) } + + 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 { + 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 + ) +} + 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..421c0c3 --- /dev/null +++ b/surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt @@ -0,0 +1,51 @@ +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 kotlin.coroutines.cancellation.CancellationException + +object MCCoroutineExceptionListener : Listener { + private val logger = logger() + + @EventHandler + fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { + if (event.exception is CancellationException) { + return + } + + event.isCancelled = true + + logger.atSevere() + .log( + "MCCoroutine exception occurred: ${event.exception.message}\n${event.exception.stackTraceToString()}" + ) + + + runCatching { + runBlocking { + launch { + runCatching { + val surfCoreSystemError = surfCoreSystemErrorService.logError( + throwable = event.exception, + server = surfServerConfig.serverName + ) + logger.atInfo() + .log("This error has been logged with Uuid: ${surfCoreSystemError.uuid}") + }.onFailure { + logger.atSevere().log( + "Failed to log MCCoroutine exception to database" + ) + } + } + } + }.onFailure { + logger.atSevere().log("Error while handling MCCoroutine exception") + } + } +} 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 c1e84e7..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 @@ -2,12 +2,14 @@ package dev.slne.surf.core.paper.listener 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 import dev.slne.surf.core.core.common.redis.request.SendPlayerToProxyRequest import dev.slne.surf.core.core.common.redis.watcher.PlayerProxyConnectionResultWatcher 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.core.paper.plugin import dev.slne.surf.surfapi.bukkit.api.command.util.idOrThrow @@ -15,6 +17,7 @@ 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 @@ -32,7 +35,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 } @@ -71,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)) + } } } @@ -108,20 +123,9 @@ object PlayerConnectListener : Listener { } } - private fun buildDisconnectComponent() = buildText { - appendNewline(2) - primary("CASTCRAFTER") - appendNewline() - primary("COMMUNITY SERVER") - appendNewline(2) - error("DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN.") - appendNewline() - error("Code: 1921186215185: 1") // surf-core in A1Z26-Cipher + Error Code 1 - 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") + private fun buildDisconnectComponent(code: String) = buildText { + renderErrorCodeDisconnectMessage(code, "DEINE SPIELERDATEN KONNTEN NICHT GELADEN WERDEN", { + spacer("Beim laden deiner Spielerdaten ist ein interner Fehler aufgetreten.") + }) } } \ No newline at end of file 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 69660d4..7652dfd 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 @@ -24,6 +24,9 @@ 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 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 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 d446634..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 @@ -19,14 +19,16 @@ import dev.slne.surf.core.api.common.surfCoreApi import dev.slne.surf.core.api.common.util.sendText 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 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.MCCoroutineExceptionListener import dev.slne.surf.core.velocity.listener.VelocityServerListener import dev.slne.surf.core.velocity.redis.handler.SendPlayerToProxyHandler import dev.slne.surf.core.velocity.redis.handler.SendPlayerToServerHandler @@ -50,12 +52,14 @@ class VelocityMain @Inject constructor( init { suspendingPluginContainer.initialize(this) + GlobalErrorHandler.install() + instance = this surfServerConfigHolder = SurfServerConfigHolder(dataPath) redisLoader.load() surfPlayerService.init() surfServerService.init() - authentificationService.init() + authenticationService.init() redisLoader.withListener(VelocityRedisListener) redisLoader.withRequestResponseHandler(SendPlayerToProxyHandler) redisLoader.withRequestResponseHandler(SendPlayerToServerHandler) @@ -87,6 +91,7 @@ class VelocityMain @Inject constructor( eventManager.register(this, ConnectionListener) eventManager.register(this, AuthenticationListener) eventManager.register(this, VelocityServerListener) + eventManager.register(this, MCCoroutineExceptionListener) surfServerService.changeState(SurfProxyServer.current(), SurfServerState.RUNNING) } @@ -119,6 +124,7 @@ class VelocityMain @Inject constructor( }) } + GlobalErrorHandler.shutdown() redisLoader.disconnect() databaseLoader.disconnect() } 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 a3def0a..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 @@ -1,5 +1,6 @@ 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 @@ -8,11 +9,13 @@ import dev.slne.surf.core.api.common.server.SurfProxyServer import dev.slne.surf.core.api.common.server.SurfServer import dev.slne.surf.core.api.common.server.connection.SurfServerConnectResult 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.redis.handler.convertResult import dev.slne.surf.core.velocity.surfServerConfig import kotlinx.coroutines.future.await import net.kyori.adventure.util.Services +import java.util.* import kotlin.jvm.optionals.getOrNull @AutoService(SurfCoreApi::class) @@ -21,6 +24,33 @@ class SurfCoreApiVelocityImpl : SurfCoreApiImpl(), Services.Fallback { override fun getCurrentServerCategory() = surfServerConfig.serverCategory override fun getCurrentServerDisplayName() = surfServerConfig.serverDisplayName + + 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: CommonSurfServer 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 b8026ba..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 @@ -10,9 +10,11 @@ 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.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.mutableObjectSetOf @@ -26,15 +28,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( @@ -43,16 +36,29 @@ 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, - "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 } @@ -62,25 +68,27 @@ object AuthenticationListener { return } + authenticationService.continuations[event.player.uniqueId] = continuation + 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 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 if (event.player.handshakeIntent != HandshakeIntent.TRANSFER) { return @@ -96,12 +104,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 83% 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 9600e31..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 @@ -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 @@ -8,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() @@ -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 } 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..955208e --- /dev/null +++ b/surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt @@ -0,0 +1,45 @@ +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.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 + +object MCCoroutineExceptionListener { + private val logger = logger() + + @Subscribe + fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { + event.result = ResultedEvent.GenericResult.denied() + + logger.atSevere() + .log( + "MCCoroutine exception occurred: ${event.exception.message}\n${event.exception.stackTraceToString()}" + ) + + runCatching { + runBlocking { + launch { + runCatching { + val surfCoreSystemError = surfCoreSystemErrorService.logError( + throwable = event.exception, + server = surfServerConfig.serverName + ) + logger.atInfo() + .log("This error has been logged with Uuid: ${surfCoreSystemError.uuid}") + }.onFailure { + logger.atSevere().log( + "Failed to log MCCoroutine exception to database" + ) + } + } + } + }.onFailure { + logger.atSevere().log("Error while handling MCCoroutine exception") + } + } +} 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() }