summaryrefslogtreecommitdiff
path: root/platform-paper
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-31 18:25:59 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-31 20:16:53 +0900
commit2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6 (patch)
tree39dadbf4a26405a9de492108b0dbccb776180a7c /platform-paper
parent3f7021a3bd407f4133e76dab22cc68e31ffd018c (diff)
downloadLunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.gz
LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.bz2
LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.zip
docs: Add channel chat log
Diffstat (limited to 'platform-paper')
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt23
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMessageLogger.kt100
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt2
3 files changed, 81 insertions, 44 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
index e6ea864..70495a4 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
@@ -240,6 +240,7 @@ class ServiceInitializer(
io.ktor.util.logging
.KtorSimpleLogger("ChannelMessageLogger"),
maxFileSizeBytes = configuration.features.channelChat.messageLogging.maxFileSizeMB * 1024L * 1024L,
+ retentionDays = configuration.features.channelChat.messageLogging.retentionDays,
).also {
channelMessageLogger = it
logger.info(
@@ -299,26 +300,6 @@ class ServiceInitializer(
saveInterval,
)
}
-
- // Schedule cleanup of old channel message logs
- if (configuration.features.channelChat.messageLogging.enabled &&
- configuration.features.channelChat.messageLogging.retentionDays > 0 &&
- channelMessageLogger != null
- ) {
- val cleanupInterval = 24 * 60 * 60 * 20L // 24 hours in ticks
- val initialDelay = 5 * 60 * 20L // 5 minutes after startup
-
- plugin.server.scheduler.runTaskTimerAsynchronously(
- plugin,
- Runnable {
- channelMessageLogger?.cleanupOldLogs(
- configuration.features.channelChat.messageLogging.retentionDays,
- )
- },
- initialDelay,
- cleanupInterval,
- )
- }
}
/**
@@ -329,6 +310,6 @@ class ServiceInitializer(
conversionCache?.saveToDisk()
services.channelManager?.saveToDisk()
services.chatModeManager?.shutdown()
- channelMessageLogger?.flushSync()
+ channelMessageLogger?.shutdown()
}
}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMessageLogger.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMessageLogger.kt
index d14b097..206968c 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMessageLogger.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMessageLogger.kt
@@ -28,22 +28,27 @@ import kotlin.io.path.name
* @property plugin Bukkit plugin instance for scheduling tasks
* @property logger Logger for diagnostic messages
* @property maxFileSizeBytes Maximum size of a single log file
+ * @property retentionDays Number of days to retain log files (0 = keep forever)
*/
class ChannelMessageLogger(
private val logsDirectory: Path,
private val plugin: Plugin,
private val logger: Logger,
private val maxFileSizeBytes: Long,
+ private val retentionDays: Int,
) {
private val pendingEntries = ConcurrentLinkedQueue<ChannelMessageLogEntry>()
private val json = Json { encodeDefaults = true }
private var flushTaskId: Int? = null
+ private var cleanupTaskId: Int? = null
companion object {
private const val LOG_FILE_PREFIX = "channel-messages-"
private const val LOG_FILE_EXTENSION = ".json"
private val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
private const val FLUSH_INTERVAL_TICKS = 20L // 1 second
+ private const val CLEANUP_INTERVAL_TICKS = 24 * 60 * 60 * 20L // 24 hours
+ private const val CLEANUP_INITIAL_DELAY_TICKS = 5 * 60 * 20L // 5 minutes
}
init {
@@ -52,6 +57,7 @@ class ChannelMessageLogger(
Files.createDirectories(logsDirectory)
logger.info("Channel message logger initialized at: $logsDirectory")
schedulePeriodicFlush()
+ schedulePeriodicCleanup()
} catch (e: Exception) {
logger.error("Failed to initialize channel message logger", e)
}
@@ -81,8 +87,34 @@ class ChannelMessageLogger(
}
/**
+ * Schedules periodic cleanup of old log files.
+ */
+ private fun schedulePeriodicCleanup() {
+ if (retentionDays <= 0) {
+ logger.info("Log retention disabled (retentionDays = $retentionDays)")
+ return
+ }
+
+ cleanupTaskId =
+ plugin.server.scheduler
+ .runTaskTimerAsynchronously(
+ plugin,
+ Runnable { cleanupOldLogs(retentionDays) },
+ CLEANUP_INITIAL_DELAY_TICKS,
+ CLEANUP_INTERVAL_TICKS,
+ ).taskId
+
+ logger.info("Scheduled log cleanup task (retention: $retentionDays days)")
+ }
+
+ /**
* Flushes all pending entries to the current day's log file.
+ * Automatically creates new files with suffixes when size limit is exceeded.
+ *
+ * This method is synchronized to prevent race conditions between periodic
+ * flush operations and shutdown flush.
*/
+ @Synchronized
private fun flushPendingEntries() {
if (pendingEntries.isEmpty()) {
return
@@ -101,12 +133,6 @@ class ChannelMessageLogger(
try {
val logFile = getCurrentLogFile()
- // Check file size before writing
- if (Files.exists(logFile) && logFile.fileSize() >= maxFileSizeBytes) {
- logger.warn("Log file ${logFile.name} exceeded maximum size, skipping flush")
- return
- }
-
BufferedWriter(
Files.newBufferedWriter(
logFile,
@@ -128,20 +154,22 @@ class ChannelMessageLogger(
}
/**
- * Synchronously flushes all pending entries.
+ * Shuts down the logger by cancelling scheduled tasks and flushing pending entries.
* Should be called during plugin shutdown.
*/
- fun flushSync() {
- // Cancel scheduled task
+ fun shutdown() {
+ // Cancel scheduled tasks
flushTaskId?.let { plugin.server.scheduler.cancelTask(it) }
+ cleanupTaskId?.let { plugin.server.scheduler.cancelTask(it) }
// Flush remaining entries
flushPendingEntries()
- logger.info("Channel message logger flushed all pending entries")
+ logger.info("Channel message logger shut down (flushed all pending entries)")
}
/**
* Deletes log files older than the specified retention period.
+ * Handles both base files (YYYY-MM-DD.json) and suffixed files (YYYY-MM-DD-N.json).
*
* @param retentionDays Number of days to retain log files
*/
@@ -154,20 +182,28 @@ class ChannelMessageLogger(
val cutoffDate = LocalDate.now(ZoneOffset.UTC).minusDays(retentionDays.toLong())
val logFiles = logsDirectory.listDirectoryEntries("$LOG_FILE_PREFIX*$LOG_FILE_EXTENSION")
+ // Pattern: channel-messages-YYYY-MM-DD(-N)?.json
+ val datePattern = Regex("""${Regex.escape(LOG_FILE_PREFIX)}(\d{4}-\d{2}-\d{2})(?:-\d+)?${Regex.escape(LOG_FILE_EXTENSION)}""")
+
var deletedCount = 0
for (logFile in logFiles) {
val fileName = logFile.name
- val dateStr = fileName.removePrefix(LOG_FILE_PREFIX).removeSuffix(LOG_FILE_EXTENSION)
-
- try {
- val fileDate = LocalDate.parse(dateStr, DATE_FORMATTER)
- if (fileDate.isBefore(cutoffDate)) {
- logFile.deleteIfExists()
- deletedCount++
- logger.info("Deleted old log file: $fileName")
+ val matchResult = datePattern.matchEntire(fileName)
+
+ if (matchResult != null) {
+ val dateStr = matchResult.groupValues[1]
+ try {
+ val fileDate = LocalDate.parse(dateStr, DATE_FORMATTER)
+ if (fileDate.isBefore(cutoffDate)) {
+ logFile.deleteIfExists()
+ deletedCount++
+ logger.info("Deleted old log file: $fileName")
+ }
+ } catch (e: Exception) {
+ logger.warn("Failed to parse date from log file: $fileName", e)
}
- } catch (e: Exception) {
- logger.warn("Failed to parse date from log file: $fileName", e)
+ } else {
+ logger.warn("Log file name does not match expected pattern: $fileName")
}
}
@@ -181,11 +217,31 @@ class ChannelMessageLogger(
/**
* Gets the log file path for the current UTC date.
+ * If the current file exceeds the size limit, returns a new file with a suffix.
+ * Filenames follow the pattern: channel-messages-YYYY-MM-DD(-N).json
*/
private fun getCurrentLogFile(): Path {
val currentDate = LocalDate.now(ZoneOffset.UTC)
val dateStr = currentDate.format(DATE_FORMATTER)
- val fileName = "$LOG_FILE_PREFIX$dateStr$LOG_FILE_EXTENSION"
- return logsDirectory.resolve(fileName)
+
+ // Try base filename first
+ var fileName = "$LOG_FILE_PREFIX$dateStr$LOG_FILE_EXTENSION"
+ var logFile = logsDirectory.resolve(fileName)
+
+ // If file exists and exceeds size limit, find next available suffix
+ var suffix = 1
+ while (Files.exists(logFile) && logFile.fileSize() >= maxFileSizeBytes) {
+ fileName = "$LOG_FILE_PREFIX$dateStr-$suffix$LOG_FILE_EXTENSION"
+ logFile = logsDirectory.resolve(fileName)
+ suffix++
+
+ // Safety limit to prevent infinite loop
+ if (suffix > 1000) {
+ logger.error("Too many log files for date $dateStr (limit: 1000), using latest")
+ break
+ }
+ }
+
+ return logFile
}
}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt
index 861707a..03423e5 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt
@@ -26,7 +26,7 @@ class PlayerChatListener(
) : Listener {
private val plainTextSerializer = PlainTextComponentSerializer.plainText()
- @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
+ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun onChat(event: AsyncChatEvent) {
val player = event.player
val settings = settingsManager.getSettings(player.uniqueId)