Conversation
…AuthenticationListener and PlayerConnectListener
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…anup Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…error handling Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…structure' into copilot/implement-error-logging-structure
…gin check to listener Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…rvice implementations
…logging-structure Implement system-wide error logging with automatic deduplication and MCCoroutine integration
There was a problem hiding this comment.
Pull request overview
Introduces a cross-platform (Paper + Velocity) error logging system that persists player-facing error codes and system exceptions to the backend database, and adds admin commands to inspect logged errors.
Changes:
- Added global + coroutine exception handling that logs “system errors” to the database (with occurrence aggregation).
- Added player error-code logging via
SurfCoreApi.logError(...), plus shared disconnect message rendering that includes a copyable error code. - Added backend DB tables/repositories/services and Paper commands to view player/system error logs.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/listener/MCCoroutineExceptionListener.kt | New Velocity listener to capture MCCoroutine exceptions and persist them as system errors |
| surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationService.kt | Renames authentication service and logs failed authentication attempts via SurfCore API |
| surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt | Adds blocked-domain error-code logging + new disconnect message rendering |
| surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt | Implements new SurfCore API error logging methods on Velocity |
| surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt | Installs global error handler and registers new exception listener |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/permission/PermissionRegistry.kt | Adds permission for new error-related commands |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt | Logs player-data load failures and kicks with error-code disconnect message |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt | New Paper listener to capture MCCoroutine exceptions and persist as system errors |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/TestErrorCommand.kt | New command to trigger test errors for validating the logging pipeline |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/SurfCoreSystemErrorCommand.kt | New command to list/view aggregated system errors |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/command/CoreErrorCommand.kt | New command to view player error-code logs |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt | Implements new SurfCore API error logging methods on Paper |
| surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt | Installs global error handler, registers new listener, and registers new commands |
| surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/util/core-common-util.kt | Adds reusable disconnect message builders including error-code rendering |
| surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/player/SurfCoreErrorLoggingService.kt | New common service interface for persisting player error-code logs |
| surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/SurfCoreSystemErrorService.kt | New common service interface for persisting aggregated system errors + location extraction |
| surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/error/GlobalErrorHandler.kt | New global uncaught exception handler that persists system errors asynchronously |
| surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt | Adds shared SurfCore API “awaiting” error logging methods + code generation passthrough |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreSystemErrorTable.kt | New DB table for aggregated system errors (message/location/server unique key) |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/table/SurfCoreErrorLogsTable.kt | New DB table for player error-code logs |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreSystemErrorServiceImpl.kt | Backend implementation of system error persistence service |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/service/SurfCoreErrorLoggingServiceImpl.kt | Backend implementation of player error-code logging service |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreSystemErrorRepository.kt | Repository for upserting system errors and incrementing occurrence counts |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/repository/SurfCoreErrorLoggingRepository.kt | Repository for inserting and retrieving player error-code logs |
| surf-core-backend/src/main/kotlin/dev/slne/surf/core/fallback/DatabaseLoaderImpl.kt | Registers new tables for schema creation/migration |
| surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreSystemError.kt | New API DTO for system errors |
| surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/error/SurfCoreError.kt | New API DTO for player error-code logs |
| surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt | Adds new error logging API surface (sync + awaiting variants) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fun shutdown() { | ||
| runBlocking { | ||
| try { | ||
| errorScope.coroutineContext.job.cancelAndJoin() | ||
| logger.info("Global error handler shutdown complete") |
There was a problem hiding this comment.
shutdown() cancels the entire errorScope job (cancelAndJoin()), which also cancels any in-flight DB logging coroutines and may drop errors during shutdown. Consider awaiting completion of existing children (optionally with a timeout) before cancelling the scope, if you want best-effort persistence.
| if (row.location.length > 50) { | ||
| spacer("...") | ||
| } | ||
| clickRunsCommand("/systemerror view ${row.id}") |
There was a problem hiding this comment.
The click action runs /systemerror view <id>, but the registered command is surfcoresystemerror view <id>. Update the click command (or add an alias) so clicking a row opens the details view.
| clickRunsCommand("/systemerror view ${row.id}") | |
| clickRunsCommand("/surfcoresystemerror view ${row.id}") |
| it[SurfCoreSystemErrorTable.server] = server | ||
| it[SurfCoreSystemErrorTable.firstOccurred] = existingError?.firstOccurred ?: now | ||
| it[SurfCoreSystemErrorTable.lastOccurred] = now | ||
| it[SurfCoreSystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 |
There was a problem hiding this comment.
occurrenceCount is computed from a previously read existingError and then written back as existing + 1. Under concurrent logging of the same error this can lose increments (two transactions can both read N and write N+1). Prefer an atomic DB-side increment in the upsert conflict update (e.g., occurrence_count = occurrence_count + 1).
| it[SurfCoreSystemErrorTable.occurrenceCount] = (existingError?.occurrenceCount ?: 0) + 1 | |
| it[SurfCoreSystemErrorTable.occurrenceCount] = | |
| if (existingError == null) 1 else SurfCoreSystemErrorTable.occurrenceCount + 1 |
| fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { | ||
| event.result = ResultedEvent.GenericResult.denied() | ||
|
|
||
| logger.log(Level.SEVERE, "MCCoroutine exception occurred", event.exception) |
There was a problem hiding this comment.
The Velocity MCCoroutine exception handler logs/persists all exceptions. Consider ignoring CancellationException (as done in the Paper listener) to avoid noisy logs/DB writes when coroutines are cancelled as part of normal control flow.
| val errorCode = varchar("error_code", 7) | ||
| val errorMessage = text("error_message") | ||
| val server = varchar("server", 255) | ||
| val timestamp = offsetDateTime("timestamp") |
There was a problem hiding this comment.
This table is queried by error_code (getError(code)) and by player_uuid (getErrors(uuid)), but defines no indexes/uniqueness. Consider adding indexes (and likely a uniqueness constraint on error_code if codes must be unambiguous for support).
| val timestamp = offsetDateTime("timestamp") | |
| val timestamp = offsetDateTime("timestamp") | |
| init { | |
| // Index for lookups by player UUID (e.g., getErrors(uuid)) | |
| index(isUnique = false, columns = arrayOf(playerUuid)) | |
| // Index for lookups by error code (e.g., getError(code)) | |
| index(isUnique = false, columns = arrayOf(errorCode)) | |
| } |
| runBlocking { | ||
| launch { | ||
| try { | ||
| val surfCoreSystemError = surfCoreSystemErrorService.logError( | ||
| throwable = event.exception, |
There was a problem hiding this comment.
runBlocking inside an event callback can block the Velocity event thread if the DB is slow/unavailable, potentially stalling the proxy. Prefer launching the DB logging asynchronously on a plugin/IO scope and remove the redundant launch nested inside runBlocking.
| val errorCode = surfCoreApi.logError( | ||
| event.player.uniqueId, | ||
| code, | ||
| "Blocked connection from domain: $domain" | ||
| ) |
There was a problem hiding this comment.
surfCoreApi.logError(playerUuid, code, message) returns Unit, so val errorCode = ... is always Unit and unused. Either remove the variable assignment, or use the 2-arg overload (logError(uuid, message)) that returns the generated code and reuse it in the disconnect message.
surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt
Outdated
Show resolved
Hide resolved
| override fun logError(playerUUid: UUID, message: String): String { | ||
| val code = surfCoreErrorLoggingService.generateCode() | ||
|
|
||
| plugin.launch { | ||
| surfCoreErrorLoggingService.logError( | ||
| playerUUid, |
There was a problem hiding this comment.
Parameter name playerUUid is inconsistent with the API (playerUuid). Rename it to playerUuid for consistency/readability across implementations.
| override fun logError(playerUUid: UUID, message: String): String { | |
| val code = surfCoreErrorLoggingService.generateCode() | |
| plugin.launch { | |
| surfCoreErrorLoggingService.logError( | |
| playerUUid, | |
| override fun logError(playerUuid: UUID, message: String): String { | |
| val code = surfCoreErrorLoggingService.generateCode() | |
| plugin.launch { | |
| surfCoreErrorLoggingService.logError( | |
| playerUuid, |
| val surfCoreSystemError = surfCoreSystemErrorService.logError( | ||
| throwable = throwable, | ||
| server = SurfServer.current().name |
There was a problem hiding this comment.
handleError uses SurfServer.current().name, which can throw if the current server hasn't been registered in surfServerService yet (e.g., very early startup). Use a non-throwing source for the server name (like surfCoreApi.getCurrentServerName() or a safe fallback) so the global handler can still persist errors during initialization.
| val surfCoreSystemError = surfCoreSystemErrorService.logError( | |
| throwable = throwable, | |
| server = SurfServer.current().name | |
| val serverName = try { | |
| SurfServer.current().name | |
| } catch (e: Exception) { | |
| logger.log( | |
| Level.WARNING, | |
| "Failed to resolve current SurfServer name while handling an error; using fallback name.", | |
| e | |
| ) | |
| "unknown" | |
| } | |
| val surfCoreSystemError = surfCoreSystemErrorService.logError( | |
| throwable = throwable, | |
| server = serverName |
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…or-system-errors Add 10-character error codes to system errors
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| success("Triggering exception with custom context...") | ||
|
|
||
| plugin.launch(plugin.globalRegionDispatcher) { | ||
| error("Test exception with custom context from /testerror customContext") |
There was a problem hiding this comment.
The thrown test exception message references /testerror customContext, but the actual command path is /coreerror system test customContext. Update the string to match so logs are easier to correlate.
| error("Test exception with custom context from /testerror customContext") | |
| error("Test exception with custom context from /coreerror system test customContext") |
| (SurfCoreSystemErrorTable.errorMessage eq message) and | ||
| (SurfCoreSystemErrorTable.location eq location) and | ||
| (SurfCoreSystemErrorTable.server eq server) and | ||
| (SurfCoreSystemErrorTable.lastOccurred lessEq now.minusDays(1)) |
There was a problem hiding this comment.
The existingError lookup uses lastOccurred <= now.minusDays(1), which matches errors last seen more than 24h ago. That inverts typical “dedupe/count within last 24h” behavior and will create new rows for duplicates happening within 24h; adjust the comparison/window logic.
| @Subscribe | ||
| fun onMCCoroutineException(event: MCCoroutineExceptionEvent) { | ||
| event.result = ResultedEvent.GenericResult.denied() |
There was a problem hiding this comment.
Unlike the Paper listener, this Velocity listener does not filter out cancellation-related exceptions (e.g., CancellationException). Those are typically expected and can produce noisy severe logs; consider ignoring them early in the handler.
| success("Triggering coroutine exception...") | ||
| } | ||
|
|
||
| error("Test coroutine exception from /testerror coroutine") |
There was a problem hiding this comment.
The thrown test exception message references /testerror coroutine, but the actual command path is /coreerror system test coroutine. Update the string to match so logs are easier to correlate.
| error("Test coroutine exception from /testerror coroutine") | |
| error("Test coroutine exception from /coreerror system test coroutine") |
| } | ||
|
|
||
| plugin.launch { | ||
| error("Test exception from plugin.launch in /testerror launch") |
There was a problem hiding this comment.
The thrown test exception message references /testerror launch, but the actual command path is /coreerror system test launch. Update the string to match so logs are easier to correlate.
| error("Test exception from plugin.launch in /testerror launch") | |
| error("Test exception from plugin.launch in /coreerror system test launch") |
| } | ||
|
|
||
| val code = surfCoreErrorLoggingService.generateCode() | ||
| val errorCode = surfCoreApi.logError( |
There was a problem hiding this comment.
surfCoreApi.logError(playerUuid, code, message) returns Unit, but its result is assigned to val errorCode. This will not compile (type mismatch) and the variable is unused; remove the assignment or call the String-returning overload if you need a code.
| val errorCode = surfCoreApi.logError( | |
| surfCoreApi.logError( |
| runCatching { | ||
| runBlocking { | ||
| launch { | ||
| runCatching { | ||
| val surfCoreSystemError = surfCoreSystemErrorService.logError( |
There was a problem hiding this comment.
runBlocking inside this event handler will block the Velocity event thread until the DB logging completes (and the nested launch is redundant because runBlocking waits for its children). Prefer launching the logging work on a background scope/dispatcher without blocking this handler.
...core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt
Show resolved
Hide resolved
| Thread { | ||
| error("Test thread exception from /testerror thread") | ||
| }.start() |
There was a problem hiding this comment.
The thrown test exception message references /testerror thread, but the actual command path is /coreerror system test thread. Update the string to match so logs are easier to correlate.
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
Co-authored-by: TheBjoRedCraft <143264463+TheBjoRedCraft@users.noreply.github.com>
…st-command Add filtering support to error list commands
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/MCCoroutineExceptionListener.kt
Show resolved
Hide resolved
| .map { row -> | ||
| SurfCoreSystemError( | ||
| uuid = row[SurfCoreSystemErrorTable.uuid], | ||
| errorCode = row[SurfCoreSystemErrorTable.errorCode], | ||
| errorMessage = row[SurfCoreSystemErrorTable.errorMessage], |
There was a problem hiding this comment.
After the upsert, the code re-selects by (errorMessage, location, server) without ordering or a time window. If multiple rows exist for the same triple (e.g., after the 24h window where a new row is inserted), firstOrNull() can return an older record and report the wrong uuid/errorCode. Select deterministically (e.g., by the uuid/errorCode you just wrote, or order by lastOccurred desc and limit 1).
| ) | ||
| .map { row -> | ||
| SurfCoreSystemError( | ||
| uuid = row[SurfCoreSystemErrorTable.uuid], | ||
| errorCode = row[SurfCoreSystemErrorTable.errorCode], | ||
| errorMessage = row[SurfCoreSystemErrorTable.errorMessage], | ||
| stacktrace = row[SurfCoreSystemErrorTable.stacktrace], |
There was a problem hiding this comment.
The “dedupe within 24h” logic does a SELECT then an UPSERT, but there is no uniqueness constraint on (errorMessage, location, server) to prevent concurrent log calls from inserting multiple rows for the same error. If you need deduplication, consider enforcing a unique key that matches the dedupe criteria (or otherwise serialize updates, e.g., via an explicit UPDATE/INSERT strategy).
| 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() |
There was a problem hiding this comment.
getError(code) assumes error_code uniquely identifies a single player error log, but the table schema does not enforce uniqueness on error_code. Even if collisions are rare, this can return the wrong player's error. Either enforce uniqueness at the DB level (and retry on conflict when generating codes) or change the lookup API to include a disambiguator (e.g., player UUID + code, or code + timestamp).
| 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() | |
| suspend fun getError(playerUuid: UUID, code: String): SurfCoreError? = suspendTransaction { | |
| SurfCoreErrorLogsTable | |
| .selectAll() | |
| .where( | |
| SqlExpressionBuilder.run { | |
| (SurfCoreErrorLogsTable.playerUuid eq playerUuid) and | |
| (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() |
…eErrorLoggingRepository
…in CoreErrorCommand
…-fix-server-info-command
…d-fix-server-info-command' into feat/error-logging-system # Conflicts: # surf-core-api/surf-core-api-common/src/main/kotlin/dev/slne/surf/core/api/common/SurfCoreApi.kt # surf-core-core/surf-core-core-common/src/main/kotlin/dev/slne/surf/core/core/common/SurfCoreApiImpl.kt # surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/PaperMain.kt # surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/api/SurfCoreApiPaperImpl.kt # surf-core-paper/src/main/kotlin/dev/slne/surf/core/paper/listener/PlayerConnectListener.kt # surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/VelocityMain.kt # surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/api/SurfCoreApiVelocityImpl.kt # surf-core-velocity/src/main/kotlin/dev/slne/surf/core/velocity/auth/AuthenticationListener.kt
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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) |
There was a problem hiding this comment.
authenticationService.continuations[...] = continuation and requestCookie(...) are executed twice for the same login (lines 71–76). This will trigger duplicate cookie requests and can lead to hard-to-debug auth/continuation behavior. Remove the duplicated block so it only runs once.
| runBlocking { | ||
| val code = surfCoreApi.logError( | ||
| event.connection.audience.uuid(), | ||
| "Failed to load player data on AsyncPlayerPreLoginEvent" | ||
| ) |
There was a problem hiding this comment.
runBlocking inside AsyncPlayerConnectionConfigureEvent will block the async login/config thread even though surfCoreApi.logError(uuid, message) already returns a code immediately and logs asynchronously. Prefer calling logError(...) directly (no runBlocking) and disconnect right away with the returned code.
|
|
||
| plugin.launch(plugin.globalRegionDispatcher) { | ||
| error("Test exception with custom context from /testerror customContext") | ||
| } |
There was a problem hiding this comment.
In the customContext test command, plugin.launch(...) is executed inside the sendText { ... } builder. Mixing side effects into component-building makes the flow harder to reason about and can cause unexpected behavior if the builder is reused; move the launch call outside the message-building lambda.
| plugin.launch(plugin.globalRegionDispatcher) { | |
| error("Test exception with custom context from /testerror customContext") | |
| } | |
| } | |
| plugin.launch(plugin.globalRegionDispatcher) { | |
| error("Test exception with custom context from /testerror customContext") |
| 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") | ||
| } |
There was a problem hiding this comment.
The thrown test exceptions reference /testerror ... in their messages, but the command registered here is /coreerror .... This makes the log output misleading when operators try to reproduce an error; update the strings to match the actual command path.
| val code = surfCoreErrorLoggingService.generateCode() | ||
| val errorCode = surfCoreApi.logError( | ||
| event.player.uniqueId, | ||
| code, | ||
| "Blocked connection from domain: $domain" | ||
| ) |
There was a problem hiding this comment.
In the blocked-domain branch, the result of surfCoreApi.logError(...) is assigned to val errorCode, but this overload returns Unit and the variable is never used. This is confusing and looks like a leftover from an earlier API design—either remove the variable or switch to the logError(uuid, message): String overload if you actually need a code.
| val resultError = SurfCoreSystemErrorTable.selectAll() | ||
| .where( | ||
| (SurfCoreSystemErrorTable.errorMessage eq message) and | ||
| (SurfCoreSystemErrorTable.location eq location) and | ||
| (SurfCoreSystemErrorTable.server eq server) | ||
| ) | ||
| .map { row -> | ||
| SurfCoreSystemError( |
There was a problem hiding this comment.
After the upsert, the code re-selects by (message, location, server) without ordering. If there are multiple rows with the same triple (e.g., older than 24h plus a new one), firstOrNull() may return an arbitrary/old row instead of the one just inserted/updated. Re-select by the uuid/errorCode you upserted (or return values from the upsert) to make the result deterministic.
No description provided.