diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-31 18:25:59 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-31 20:16:53 +0900 |
| commit | 2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6 (patch) | |
| tree | 39dadbf4a26405a9de492108b0dbccb776180a7c /platform-paper | |
| parent | 3f7021a3bd407f4133e76dab22cc68e31ffd018c (diff) | |
| download | LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.gz LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.bz2 LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.zip | |
docs: Add channel chat log
Diffstat (limited to 'platform-paper')
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) |
