diff --git a/CommandFramework/build.gradle.kts b/CommandFramework/build.gradle.kts index 8513c822..24541771 100644 --- a/CommandFramework/build.gradle.kts +++ b/CommandFramework/build.gradle.kts @@ -24,4 +24,5 @@ plugins { dependencies { testImplementation(libs.junit) testImplementation(libs.hamcrest) + compileOnly(project(":CommonCore:SQL")) } \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/ServerInfo.kt b/CommonCore/SQL/src/de/steamwar/ServerInfo.kt new file mode 100644 index 00000000..7959ca83 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/ServerInfo.kt @@ -0,0 +1,22 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar + +data class ServerInfo(val name: String, val version: Int, val checkpointed: Boolean) diff --git a/CommonCore/SQL/src/de/steamwar/logger/LogEntry.kt b/CommonCore/SQL/src/de/steamwar/logger/LogEntry.kt new file mode 100644 index 00000000..637301d5 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/logger/LogEntry.kt @@ -0,0 +1,71 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.logger + +import de.steamwar.sql.AuditLog +import de.steamwar.sql.AuditLogTable +import de.steamwar.sql.SQLWrapper +import de.steamwar.sql.SteamwarUser +import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.jdbc.insertIgnore + +class LogEntry(val type: AuditLog.Type, val user: SteamwarUser) { + private val start = System.currentTimeMillis() + + var text: String = "" + var arguments: String = "" + + private var exceptionType: String? = null + private var exceptionText: String? = null + private var exceptionStacktrace: String? = null + + private var owner: SteamwarUser? = null + + fun addException(e: Throwable) { + exceptionType = e.javaClass.name + exceptionText = e.message + exceptionStacktrace = e.stackTraceToString() + } + + fun addServerOwner(owner: SteamwarUser) { + this.owner = owner + } + + fun finish() { + val end = System.currentTimeMillis() + val info = SQLWrapper.impl.serverInfo + useDb { + AuditLogTable.insertIgnore { + it[this.action] = type + it[this.actor] = user.getId() + it[this.actionText] = text + it[this.actionArguments] = arguments + it[this.duration] = end - start + it[this.errorType] = exceptionType + it[this.errorText] = exceptionText + it[this.stackTrace] = exceptionStacktrace + it[this.server] = info.name + it[this.checkpointed] = info.checkpointed + it[this.duration] = end - start + it[this.serverOwner] = owner?.getId() + } + } + } +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/logger/SWLogger.kt b/CommonCore/SQL/src/de/steamwar/logger/SWLogger.kt new file mode 100644 index 00000000..3813e180 --- /dev/null +++ b/CommonCore/SQL/src/de/steamwar/logger/SWLogger.kt @@ -0,0 +1,27 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.logger + +import de.steamwar.sql.AuditLog +import de.steamwar.sql.SteamwarUser + +object SWLogger { + fun startCommand(user: SteamwarUser) = LogEntry(AuditLog.Type.COMMAND, user) +} \ No newline at end of file diff --git a/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt b/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt index 7ab0c8c4..5a2fdd78 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/AuditLog.kt @@ -19,94 +19,47 @@ package de.steamwar.sql -import de.steamwar.sql.internal.useDb import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.javatime.timestamp -import java.time.Instant object AuditLogTable: IntIdTable("AuditLog", "AuditLogId") { val time = timestamp("Time") val server = varchar("ServerName", 255) - val serverOwner = reference("ServerOwner", SteamwarUserTable).nullable() + val serverType = varchar("ServerType", 255) + val serverOwner = optReference("ServerOwner", SteamwarUserTable) + val checkpointed = bool("Checkpointed") val actor = reference("Actor", SteamwarUserTable) val action = enumerationByName("ActionType", 255, AuditLog.Type::class) val actionText = text("ActionText") + val actionArguments = text("ActionArguments") + val duration = long("Duration") + val errorType = text("ErrorType").nullable() + val errorText = text("ErrorText").nullable() + val stackTrace = text("StackTrace").nullable() } class AuditLog(id: EntityID): IntEntity(id) { companion object: IntEntityClass(AuditLogTable) { - const val SERVER_NAME_VELOCITY: String = "Velocity" - - private fun create( - serverName: String, - serverOwner: SteamwarUser?, - actor: SteamwarUser, - actionType: Type, - text: String = "" - ) = useDb { - new { - this.time = Instant.now() - this.server = serverName - this.serverOwner = serverOwner?.id - this.actor = actor.id - this.action = actionType - this.actionText = text - } - } - - @JvmStatic - fun createJoin(jointServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(jointServerName, serverOwner, joinedPlayer, Type.JOIN) - - @JvmStatic - fun createLeave(leftServerName: String, serverOwner: SteamwarUser?, joinedPlayer: SteamwarUser) = create(leftServerName, serverOwner, joinedPlayer, Type.LEAVE) - - @JvmStatic - fun createCommand(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser?, command: String) = player?.let { create(serverName, serverOwner, it, Type.COMMAND, command) } - - @JvmStatic - fun createSensitiveCommand( - serverName: String, - serverOwner: SteamwarUser?, - player: SteamwarUser?, - command: String - ) = player?.let { create(serverName, serverOwner, it, Type.SENSITIVE_COMMAND, command) } - - @JvmStatic - fun createChat(serverName: String, serverOwner: SteamwarUser?, chatter: SteamwarUser, chat: String) = create(serverName, serverOwner, chatter, Type.CHAT, chat) - - @JvmStatic - fun createGuiOpen(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_OPEN, guiName) - - @JvmStatic - fun createGuiClick( - serverName: String, - serverOwner: SteamwarUser?, - player: SteamwarUser, - guiName: String, - clickType: String, - slot: Int, - itemName: String - ) = create( - serverName, - serverOwner, - player, - Type.GUI_CLICK, - "Gui: $guiName\nSlot: $slot\nClickType: $clickType\nItemName: $itemName" - ) - - @JvmStatic - fun createGuiClose(serverName: String, serverOwner: SteamwarUser?, player: SteamwarUser, guiName: String) = create(serverName, serverOwner, player, Type.GUI_CLOSE, guiName) + @JvmField + val SERVER_NAME_VELOCITY: String = "Velocity" } var time by AuditLogTable.time var server by AuditLogTable.server + val serverType by AuditLogTable.serverType var serverOwner by AuditLogTable.serverOwner var actor by AuditLogTable.actor + var checkpointed by AuditLogTable.checkpointed var action by AuditLogTable.action var actionText by AuditLogTable.actionText + var actionArguments by AuditLogTable.actionArguments + var duration by AuditLogTable.duration + var errorType by AuditLogTable.errorType + var errorText by AuditLogTable.errorText + var stackTrace by AuditLogTable.stackTrace enum class Type { JOIN, diff --git a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java index 47331406..d823fbeb 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java +++ b/CommonCore/SQL/src/de/steamwar/sql/SQLWrapper.java @@ -20,6 +20,7 @@ package de.steamwar.sql; import de.steamwar.ImplementationProvider; +import de.steamwar.ServerInfo; import java.io.File; import java.util.Collections; @@ -40,4 +41,6 @@ public interface SQLWrapper { } void additionalExceptionMetadata(StringBuilder builder); + + ServerInfo getServerInfo(); } diff --git a/CommonCore/SQL/src/de/steamwar/sql/SWException.kt b/CommonCore/SQL/src/de/steamwar/sql/SWException.kt index fb57e24f..1d8b5c9d 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/SWException.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/SWException.kt @@ -36,6 +36,8 @@ object ExceptionTable: IntIdTable("Exception") { class SWException { companion object { + private val exceptionCache = HashSet() + val cwd = System.getProperty("user.dir") val serverName = File(cwd).name @@ -43,7 +45,9 @@ class SWException { fun init() = Unit @JvmStatic - fun log(message: String, stacktrace: String) = useDb { + fun log(message: String, stacktrace: String) = if (exceptionCache.contains(stacktrace)) Unit else useDb { + exceptionCache.add(stacktrace) + ExceptionTable.insert { it[ExceptionTable.server] = serverName it[ExceptionTable.message] = generateMessage(message) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index 7fe128ec..7c3071ce 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -19,9 +19,14 @@ package de.steamwar.core; +import lombok.Getter; + public class CheckpointUtils { private CheckpointUtils() {} + @Getter + private static boolean restored = false; + public static void signalHandler() { try { CheckpointUtilsJ9.signalHandler(); @@ -33,6 +38,7 @@ public class CheckpointUtils { public static void freeze() { try { CheckpointUtilsJ9.freeze(); + restored = true; } catch (NoClassDefFoundError e) { //ignore } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/sql/SQLWrapperImpl.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/sql/SQLWrapperImpl.java index 4fd2bebe..3fbb4377 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/sql/SQLWrapperImpl.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/sql/SQLWrapperImpl.java @@ -19,6 +19,8 @@ package de.steamwar.sql; +import de.steamwar.ServerInfo; +import de.steamwar.core.CheckpointUtils; import de.steamwar.core.Core; import de.steamwar.data.GameModeConfigUtils; import org.bukkit.Bukkit; @@ -68,4 +70,9 @@ public class SQLWrapperImpl implements SQLWrapper { builder.append(world.getName()).append(" "); builder.append("\nServer: ").append(SERVER_VERSION); } + + @Override + public ServerInfo getServerInfo() { + return new ServerInfo(Core.getServerName(), Core.getVersion(), CheckpointUtils.isRestored()); + } }