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());
+ }
}