summaryrefslogtreecommitdiff
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
parent3f7021a3bd407f4133e76dab22cc68e31ffd018c (diff)
downloadLunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.gz
LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.tar.bz2
LunaticChat-2b8f53067b7c497bb8c4a90d9dcc41e2d5ca78e6.zip
docs: Add channel chat log
-rw-r--r--CHANGELOG.md13
-rw-r--r--README.md3
-rw-r--r--build.gradle.kts2
-rw-r--r--docs/.vitepress/config/en.ts23
-rw-r--r--docs/.vitepress/config/ja.ts69
-rw-r--r--docs/biome.jsonc2
-rw-r--r--docs/src/en/guide/admin/channel-chat/introduction.md (renamed from docs/src/en/guide/admin/introduction-channel-chat.md)24
-rw-r--r--docs/src/en/guide/admin/channel-chat/logs.md119
-rw-r--r--docs/src/en/guide/admin/configuration.md70
-rw-r--r--docs/src/en/guide/admin/management-data.md (renamed from docs/src/en/guide/admin/data-and-logs.md)12
-rw-r--r--docs/src/en/index.md3
-rw-r--r--docs/src/guide/admin/channel-chat/introduction.md (renamed from docs/src/guide/admin/introduction-channel-chat.md)24
-rw-r--r--docs/src/guide/admin/channel-chat/logs.md119
-rw-r--r--docs/src/guide/admin/configuration.md33
-rw-r--r--docs/src/guide/admin/management-data.md (renamed from docs/src/guide/admin/data-and-logs.md)12
-rw-r--r--docs/src/index.md3
-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
19 files changed, 534 insertions, 122 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df917cd..6563e83 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,19 @@
## v0
+### v0.7.0
+
+#### Breaking Changes
+
+- Other plugins such as CoreProtect can no longer intercept channel chat.
+ - This has broken the integration functionality with CoreProtect.
+ - Channel Chat logs are now uniformly accessible via a text-based viewing method.
+
+----
+
+- Channel chat logging functionality has been implemented.
+ - Logs are now recorded daily in `plugins/LunaticChat/logs/channelchat/`.
+
### v0.6.0
- Added experimental feature to Channel Chat support.
diff --git a/README.md b/README.md
index ea2ec8d..13cb669 100644
--- a/README.md
+++ b/README.md
@@ -36,10 +36,9 @@ See the [Documentation](https://lc.m1sk9.dev/guide/getting-started).
- 1on1 Direct Messaging System (`/tell`, `/msg`)
- Quick Reply Functionality (`/reply`)
- Romaji to Japanese Conversion
-- CoreProtect-compatible chat logging
+- Channel Chat System
- Multi-platform support (Paper, Velocity) (coming soon)
- Spigot? No problem, just use Paper!
-- Channel Chat System (coming soon)
## License
diff --git a/build.gradle.kts b/build.gradle.kts
index c275e7d..039b977 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,7 +14,7 @@ plugins {
allprojects {
group = "dev.m1sk9"
- version = "0.6.0"
+ version = "0.7.0"
repositories {
mavenCentral()
diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts
index 5718a4e..24a3691 100644
--- a/docs/.vitepress/config/en.ts
+++ b/docs/.vitepress/config/en.ts
@@ -19,6 +19,19 @@ export const en: DefaultTheme.Config = {
text: 'For Server Administrators',
items: [
{
+ text: 'Channel Chat',
+ items: [
+ {
+ text: 'Deployment Guide',
+ link: '/en/guide/admin/channel-chat/introduction',
+ },
+ {
+ text: 'Logs',
+ link: '/en/guide/admin/channel-chat/logs',
+ },
+ ],
+ },
+ {
text: 'Cache System',
link: '/en/guide/admin/cache',
},
@@ -31,12 +44,8 @@ export const en: DefaultTheme.Config = {
link: '/en/guide/admin/velocity',
},
{
- text: 'Channel Chat Deployment Guide',
- link: '/en/guide/admin/introduction-channel-chat',
- },
- {
- text: 'Data and Logs',
- link: '/en/guide/admin/data-and-logs',
+ text: 'Data Management',
+ link: '/en/guide/admin/management-data',
},
],
},
@@ -59,7 +68,7 @@ export const en: DefaultTheme.Config = {
text: 'Moderation',
link: '/en/guide/player/channel-chat/moderation',
},
- ]
+ ],
},
{
text: 'Direct Messages',
diff --git a/docs/.vitepress/config/ja.ts b/docs/.vitepress/config/ja.ts
index 9882687..4beec42 100644
--- a/docs/.vitepress/config/ja.ts
+++ b/docs/.vitepress/config/ja.ts
@@ -19,6 +19,19 @@ export const ja: DefaultTheme.Config = {
text: 'サーバー管理者向け',
items: [
{
+ text: 'チャンネルチャット',
+ items: [
+ {
+ text: '展開ガイド',
+ link: '/guide/admin/channel-chat/introduction',
+ },
+ {
+ text: 'ログ',
+ link: '/guide/admin/channel-chat/logs',
+ },
+ ],
+ },
+ {
text: 'キャッシュシステム',
link: '/guide/admin/cache',
},
@@ -30,14 +43,10 @@ export const ja: DefaultTheme.Config = {
text: 'Velocity 連携',
link: '/guide/admin/velocity',
},
- {
- text: "チャンネルチャット展開ガイド",
- link: "/guide/admin/introduction-channel-chat",
- },
- {
- text: "データ・ログ",
- link: "/guide/admin/data-and-logs",
- }
+ {
+ text: 'データの管理',
+ link: '/guide/admin/management-data',
+ },
],
},
{
@@ -45,21 +54,21 @@ export const ja: DefaultTheme.Config = {
items: [
{
text: 'チャンネルチャット',
- link: "/guide/player/channel-chat/about",
+ link: '/guide/player/channel-chat/about',
items: [
- {
- text: 'チャットモード',
- link: '/guide/player/channel-chat/chatmode',
- },
- {
- text: 'プライベートチャンネル',
- link: '/guide/player/channel-chat/private-channel',
- },
- {
- text: 'モデレーション',
- link: '/guide/player/channel-chat/moderation',
- },
- ]
+ {
+ text: 'チャットモード',
+ link: '/guide/player/channel-chat/chatmode',
+ },
+ {
+ text: 'プライベートチャンネル',
+ link: '/guide/player/channel-chat/private-channel',
+ },
+ {
+ text: 'モデレーション',
+ link: '/guide/player/channel-chat/moderation',
+ },
+ ],
},
{
text: 'ダイレクトメッセージ',
@@ -120,14 +129,14 @@ export const ja: DefaultTheme.Config = {
text: '/lc status',
link: '/reference/commands/lc/status',
},
- {
- text: '/lc channel',
- link: '/reference/commands/lc/channel',
- },
- {
- text: '/lc chatmode',
- link: '/reference/commands/lc/chatmode',
- },
+ {
+ text: '/lc channel',
+ link: '/reference/commands/lc/channel',
+ },
+ {
+ text: '/lc chatmode',
+ link: '/reference/commands/lc/chatmode',
+ },
],
},
],
diff --git a/docs/biome.jsonc b/docs/biome.jsonc
index 5d91e18..7b0325f 100644
--- a/docs/biome.jsonc
+++ b/docs/biome.jsonc
@@ -2,7 +2,7 @@
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"files": {
"ignoreUnknown": true,
- "includes": [".vitepress/config.mts", "biome.jsonc"]
+ "includes": [".vitepress/config.mts", "biome.jsonc", ".vitepress/config/**"]
},
"formatter": {
"enabled": true,
diff --git a/docs/src/en/guide/admin/introduction-channel-chat.md b/docs/src/en/guide/admin/channel-chat/introduction.md
index 2a81d63..e755139 100644
--- a/docs/src/en/guide/admin/introduction-channel-chat.md
+++ b/docs/src/en/guide/admin/channel-chat/introduction.md
@@ -1,4 +1,4 @@
-# Channel Chat Deployment Guide
+# Channel Chat: Deployment Guide
This guide explains how to deploy channel chat.
@@ -6,7 +6,7 @@ This guide explains how to deploy channel chat.
Channel chat is a feature that allows players to create channels and share chat among specific players.
-For detailed features, please refer to the [Player Guide](../player/channel-chat/about.md).
+For detailed features, please refer to the [Player Guide](../../player/channel-chat/about.md).
## Preparing to Deploy Channel Chat
@@ -43,9 +43,21 @@ Recommended settings are as follows:
:::
+## Channel Chat Logging
+
+::: warning Compatibility with Plugins like CoreProtect
+
+As of v0.7.0, LunaticChat's channel chat feature is not compatible with logging plugins like CoreProtect.
+
+:::
+
+Channel chat logging is enabled by default.
+
+For more details, see [Channel Chat: Logs](logs.md).
+
## Channel Management
-Basically, [players manage channels themselves](../player/channel-chat/moderation.md).
+Basically, [players manage channels themselves](../../player/channel-chat/moderation.md).
However, as a server administrator, please note the following:
@@ -54,3 +66,9 @@ However, as a server administrator, please note the following:
- If inappropriate channels or member behavior occurs, take appropriate action.
If you want to avoid troubles related to channel management, consider restricting moderate commands such as `/lc channel ban`.
+
+## Plugins that Intercept Channel Chat
+
+Logging plugins like CoreProtect do not intercept LunaticChat's channel chat messages by default.
+
+However, plugins that use Paper API's `originalMessage()` to retrieve messages may intercept LunaticChat's channel chat messages.
diff --git a/docs/src/en/guide/admin/channel-chat/logs.md b/docs/src/en/guide/admin/channel-chat/logs.md
new file mode 100644
index 0000000..b65750b
--- /dev/null
+++ b/docs/src/en/guide/admin/channel-chat/logs.md
@@ -0,0 +1,119 @@
+# Channel Chat: Logs <Badge type="tip" text="v0.7.0" />
+
+Channel chat logs are a record of all messages and activities that occur within the chat.
+
+::: warning Compatibility with Plugins like CoreProtect
+
+As of v0.7.0, LunaticChat's channel chat feature is not compatible with logging plugins like CoreProtect.
+
+:::
+
+## Checking Channel Chat Logs
+
+Channel chat logs are stored in the `plugins/LunaticChat/logs/channelchat/` directory.
+
+Channel chat log files are saved in the following format:
+
+```
+{"timestamp":"2026-01-31T08:54:18.504343071Z","playerId":"ceaea267-39dd-3bac-931c-761ada671ebe","playerName":"m1sk9","channelId":"test","message":"Hello"}
+```
+
+## About File Size
+
+Each line is a complete JSON object, separated by line breaks, and the file as a whole is not a JSON array.
+
+```text
+plugins/LunaticChat/logs/
+├── channel-messages-2026-01-17.json (5.2 MB)
+├── channel-messages-2026-01-18.json (4.8 MB)
+├── channel-messages-2026-01-19.json (6.1 MB)
+├── channel-messages-2026-01-20.json (5.5 MB)
+├── channel-messages-2026-01-21.json (7.2 MB) <- Weekend, active
+├── channel-messages-2026-01-22.json (6.9 MB)
+├── channel-messages-2026-01-23.json (4.3 MB)
+├── channel-messages-2026-01-24.json (5.0 MB)
+├── channel-messages-2026-01-25.json (5.4 MB)
+├── channel-messages-2026-01-26.json (4.9 MB)
+├── channel-messages-2026-01-27.json (6.2 MB)
+├── channel-messages-2026-01-28.json (7.5 MB)
+├── channel-messages-2026-01-29.json (5.8 MB)
+├── channel-messages-2026-01-30.json (6.0 MB)
+└── channel-messages-2026-01-31.json (2.1 MB) <- Today (in progress)
+```
+
+Total: approximately 83 MB
+
+With a 30-day retention setting, the January 17 file will be automatically deleted tomorrow.
+
+### Size per Message
+
+Channel chat log entries are approximately 200 bytes per line.
+
+Assuming 1000 messages per hour:
+
+```text
+1000 msg/h × 24h × 220 bytes = 5,280,000 bytes ≈ 5.3 MB/day
+```
+
+With the default 30-day retention setting, this amounts to approximately **159 MB**.
+
+```text
+5.3 MB × 30 days = 159 MB
+```
+
+## Visualization with Grafana Loki
+
+Due to the JSON format, using Promtail to ingest channel chat logs into Grafana Loki will parse them for easier reading.
+
+```text
+2026-01-31 10:23:45.123 {job="lunatichat", player="Steve", channel="Global"}
+Hello everyone!
+
+2026-01-31 10:24:12.456 {job="lunatichat", player="Alex", channel="Global"}
+Hi Steve!
+
+2026-01-31 10:25:03.789 {job="lunatichat", player="Notch", channel="Development Team"}
+Working on new features
+```
+
+::: tip Filtering Examples
+
+Examples of querying logs in Grafana Loki:
+
+```text
+{job="lunatichat"} |= "new features"
+{job="lunatichat", channel="Global"}
+{job="lunatichat", player="Steve"}
+```
+
+:::
+
+## Command Line Checking Examples
+
+### View the latest 10 entries
+
+```bash
+tail -n 10 plugins/LunaticChat/logs/channel-messages-2026-01-31.json | jq
+```
+
+### Extract messages from a specific player
+
+```bash
+cat plugins/LunaticChat/logs/channel-messages-*.json | \
+jq 'select(.playerName=="Steve")'
+```
+
+### Count messages from a specific channel
+
+```bash
+cat plugins/LunaticChat/logs/channel-messages-*.json | \
+jq 'select(.channelId=="global")' | wc -l
+```
+
+### Message count by date
+
+```bash
+for file in plugins/LunaticChat/logs/channel-messages-*.json; do
+echo "$file: $(wc -l < $file) messages"
+done
+```
diff --git a/docs/src/en/guide/admin/configuration.md b/docs/src/en/guide/admin/configuration.md
index 6ce63b4..ef33ddc 100644
--- a/docs/src/en/guide/admin/configuration.md
+++ b/docs/src/en/guide/admin/configuration.md
@@ -54,6 +54,20 @@ features:
channelChat:
# If enabled, channel-based chat functionality will be activated.
enabled: false
+ # Maximum number of channels that can be created per server. Set to 0 for unlimited.
+ maxChannelsPerServer: 0
+ # Maximum number of members allowed in a single channel. Set to 0 for unlimited.
+ maxMembersPerChannel: 0
+ # Maximum number of channels a single player can join. Set to 0 for unlimited.
+ maxMembershipPerPlayer: 0
+ # Channel message logging configuration
+ messageLogging:
+ # If enabled, all channel messages will be logged to NDJSON files for analysis and archival.
+ enabled: true
+ # Number of days to retain log files. Set to 0 to keep logs indefinitely.
+ retentionDays: 30
+ # Maximum size of a single log file in megabytes. Files exceeding this size will stop accepting new entries.
+ maxFileSizeMB: 100
# ----------------------------------------------
# --------- Message Format Settings --------
@@ -171,13 +185,67 @@ Specifies the timeout duration (in milliseconds) for API requests to the romaniz
Specifies the number of retry attempts for failed API requests to the romanization conversion service.
-### `features.channelChat.enabled`
+### `features.channelChat`
+
+#### `enabled`
- Type: `boolean`
- Default: `false`
Enables channel-based chat functionality.
+#### `maxChannelsPerServer`
+
+- Type: `integer`
+- Default: `0`
+
+Specifies the maximum number of channels that can be created per server.
+
+Set to `0` for unlimited.
+
+#### `maxMembersPerChannel`
+
+- Type: `integer`
+- Default: `0`
+
+Specifies the maximum number of members that can join a single channel.
+
+Set to `0` for unlimited.
+
+#### `maxMembershipPerPlayer`
+
+- Type: `integer`
+- Default: `0`
+
+Specifies the maximum number of channels a single player can join.
+
+Set to `0` for unlimited.
+
+#### `messageLogging.enabled`
+
+- Type: `boolean`
+- Default: `true`
+
+Specifies whether to log channel chat messages in NDJSON format.
+
+#### `messageLogging.retentionDays`
+
+- Type: `integer`
+- Default: `30`
+
+Specifies the number of days to retain channel chat log files.
+
+Set to `0` to never delete log files.
+
+#### `messageLogging.maxFileSizeMB`
+
+- Type: `integer`
+- Default: `100`
+
+Specifies the maximum size (in megabytes) of channel chat log files.
+
+Log files exceeding `maxFileSizeMB` will stop accepting new entries.
+
## Message Format Settings
Available placeholders:
diff --git a/docs/src/en/guide/admin/data-and-logs.md b/docs/src/en/guide/admin/management-data.md
index e869552..ffed59d 100644
--- a/docs/src/en/guide/admin/data-and-logs.md
+++ b/docs/src/en/guide/admin/management-data.md
@@ -105,15 +105,3 @@ If the version does not match, LunaticChat recognizes the cache file as **old fo
```json
{"version":"1","entries":{}}
```
-
-## About CoreProtect
-
-Various chat logs in LunaticChat can also be recorded in CoreProtect without requiring an API.
-
-Logs for each feature can be checked with the following actions. When using the `/co lookup` command, specify the following actions:
-
-- Direct messages: `command`
-- Global chat and channel chat: `chat`
- - Japanese and romanization conversion is saved according to the player's settings.
-
-
diff --git a/docs/src/en/index.md b/docs/src/en/index.md
index c88e080..0fe8a53 100644
--- a/docs/src/en/index.md
+++ b/docs/src/en/index.md
@@ -32,9 +32,6 @@ features:
- title: Japanese Romanization Conversion
details: Automatically converts messages input in romaji to Japanese
icon: 🌍
- - title: CoreProtect Support
- details: LunaticChat's chat logs are compatible with CoreProtect
- icon: 🗒️
- title: Channel Chat Feature (Planned)
details: Create and manage chat channels, send private messages between specific players
icon: ☎️
diff --git a/docs/src/guide/admin/introduction-channel-chat.md b/docs/src/guide/admin/channel-chat/introduction.md
index 6993435..cf9fe52 100644
--- a/docs/src/guide/admin/introduction-channel-chat.md
+++ b/docs/src/guide/admin/channel-chat/introduction.md
@@ -1,4 +1,4 @@
-# チャンネルチャット展開ガイド
+# チャンネルチャット: 展開ガイド
このガイドでは、チャンネルチャットの展開方法について説明します.
@@ -6,7 +6,7 @@
プレイヤー間でチャンネルを作成し,特定のプレイヤー間で,チャットを共有できる機能です.
-詳しい機能については [プレイヤー向けガイド版](../player/channel-chat/about.md) を参照してください.
+詳しい機能については [プレイヤー向けガイド版](../../player/channel-chat/about.md) を参照してください.
## チャンネルチャットの展開準備
@@ -43,9 +43,21 @@
:::
+## チャンネルチャットのログ
+
+::: warning CoreProtect などのプラグインとの互換性
+
+LunaticChat では,チャンネルチャット機能に限り v0.7.0 以降 CoreProtect などのログ記録プラグインと互換性はありません.
+
+:::
+
+チャンネルチャットのログ機能はデフォルトで有効になっています.
+
+詳しくは [チャンネルチャット: ログ](logs.md) を参照してください.
+
## チャンネルの管理
-基本的に,[チャンネルの管理はプレイヤー自身が行います](../player/channel-chat/moderation.md).
+基本的に,[チャンネルの管理はプレイヤー自身が行います](../../player/channel-chat/moderation.md).
ただし,サーバー管理者として,以下の点に注意してください:
@@ -54,3 +66,9 @@
- 不適切なチャンネルやメンバー行動が発生した場合は,適切な措置を講じてください.
また,それらのチャンネルの管理によるトラブルを回避したい場合は `/lc channel ban` などのモデレートコマンドの制限を検討してください.
+
+## チャンネルチャットを捕捉してしまうプラグイン
+
+基本的に CoreProtect などのログ記録プラグインは,LunaticChat のチャンネルチャットメッセージを捕捉しません.
+
+ただし,Paper API の `originalMessage()` を使用してメッセージを取得しているプラグインは,LunaticChat のチャンネルチャットメッセージを捕捉してしまう可能性があります.
diff --git a/docs/src/guide/admin/channel-chat/logs.md b/docs/src/guide/admin/channel-chat/logs.md
new file mode 100644
index 0000000..9b88bdd
--- /dev/null
+++ b/docs/src/guide/admin/channel-chat/logs.md
@@ -0,0 +1,119 @@
+# チャンネルチャット: ログ <Badge type="tip" text="v0.7.0" />
+
+チャンネルチャットのログは,チャット内で行われたすべてのメッセージとアクティビティの記録です.
+
+::: warning CoreProtect などのプラグインとの互換性
+
+LunaticChat では,チャンネルチャット機能に限り v0.7.0 以降 CoreProtect などのログ記録プラグインと互換性はありません.
+
+:::
+
+## チャンネルチャットのログを確認する
+
+チャンネルチャットのログは `plugins/LunaticChat/logs/channelchat/` ディレクトリに保存されます.
+
+チャンネルチャットのログファイルは,以下のフォーマットで保存されます:
+
+```
+{"timestamp":"2026-01-31T08:54:18.504343071Z","playerId":"ceaea267-39dd-3bac-931c-761ada671ebe","playerName":"m1sk9","channelId":"test","message":"こんにちは"}
+```
+
+## ファイルサイズについて
+
+各行が1つの完全な JSON オブジェクトであり,各行で区切られているだけで,ファイル全体としては JSON 配列ではありません.
+
+```text
+plugins/LunaticChat/logs/
+├── channel-messages-2026-01-17.json (5.2 MB)
+├── channel-messages-2026-01-18.json (4.8 MB)
+├── channel-messages-2026-01-19.json (6.1 MB)
+├── channel-messages-2026-01-20.json (5.5 MB)
+├── channel-messages-2026-01-21.json (7.2 MB) <- 週末、アクティブ
+├── channel-messages-2026-01-22.json (6.9 MB)
+├── channel-messages-2026-01-23.json (4.3 MB)
+├── channel-messages-2026-01-24.json (5.0 MB)
+├── channel-messages-2026-01-25.json (5.4 MB)
+├── channel-messages-2026-01-26.json (4.9 MB)
+├── channel-messages-2026-01-27.json (6.2 MB)
+├── channel-messages-2026-01-28.json (7.5 MB)
+├── channel-messages-2026-01-29.json (5.8 MB)
+├── channel-messages-2026-01-30.json (6.0 MB)
+└── channel-messages-2026-01-31.json (2.1 MB) <- 今日(進行中)
+```
+
+合計: 約 83 MB
+
+30日保持設定 の場合,1月17日のファイルは明日自動削除されます.
+
+### 1メッセージあたりのサイズ
+
+チャンネルチャットのログエントリは1行につき,約200バイトです.
+
+1時間に1000メッセージの計算として
+
+```text
+1000 msg/h × 24h × 220 bytes = 5,280,000 bytes ≈ 5.3 MB/日
+```
+
+デフォルト設定の30日間保持の場合は **159 MB** 程度になります.
+
+```text
+5.3 MB × 30日 = 159 MB
+```
+
+## Grafana Loki での可視化
+
+JSON 形式のため,Promtail を使用し,Grafana Loki にチャンネルチャットのログを取り込むとパースされ読みやすくなります.
+
+```text
+2026-01-31 10:23:45.123 {job="lunatichat", player="Steve", channel="Global"}
+Hello everyone!
+
+2026-01-31 10:24:12.456 {job="lunatichat", player="Alex", channel="Global"}
+Hi Steve!
+
+2026-01-31 10:25:03.789 {job="lunatichat", player="Notch", channel="Development Team"}
+Working on new features
+```
+
+::: tip フィルタリング例
+
+Grafana Loki でログをクエリ化する例:
+
+```text
+{job="lunatichat"} |= "new features"
+{job="lunatichat", channel="Global"}
+{job="lunatichat", player="Steve"}
+```
+
+:::
+
+## コマンドラインでの確認例
+
+### 最新10件を見る
+
+```bash
+tail -n 10 plugins/LunaticChat/logs/channel-messages-2026-01-31.json | jq
+```
+
+### 特定プレイヤーのメッセージを抽出
+
+```bash
+cat plugins/LunaticChat/logs/channel-messages-*.json | \
+jq 'select(.playerName=="Steve")'
+```
+
+### 特定チャンネルのメッセージ数をカウント
+
+```bash
+cat plugins/LunaticChat/logs/channel-messages-*.json | \
+jq 'select(.channelId=="global")' | wc -l
+```
+
+### 日付別メッセージ数
+
+```bash
+for file in plugins/LunaticChat/logs/channel-messages-*.json; do
+echo "$file: $(wc -l < $file) messages"
+done
+```
diff --git a/docs/src/guide/admin/configuration.md b/docs/src/guide/admin/configuration.md
index dfa41f9..5eb5fc6 100644
--- a/docs/src/guide/admin/configuration.md
+++ b/docs/src/guide/admin/configuration.md
@@ -60,6 +60,14 @@ features:
maxMembersPerChannel: 0
# Maximum number of channels a single player can join. Set to 0 for unlimited.
maxMembershipPerPlayer: 0
+ # Channel message logging configuration
+ messageLogging:
+ # If enabled, all channel messages will be logged to NDJSON files for analysis and archival.
+ enabled: true
+ # Number of days to retain log files. Set to 0 to keep logs indefinitely.
+ retentionDays: 30
+ # Maximum size of a single log file in megabytes. Files exceeding this size will stop accepting new entries.
+ maxFileSizeMB: 100
# ----------------------------------------------
# --------- Message Format Settings --------
@@ -213,6 +221,31 @@ LunaticChat の [`/reply`](../../reference/commands/reply.md) コマンドによ
`0` に設定すると無制限になります.
+#### `messageLogging.enabled`
+
+- Type: `boolean`
+- Default: `true`
+
+チャンネルチャットのメッセージを NDJSON 形式でログに記録するかどうかを指定します.
+
+#### `messageLogging.retentionDays`
+
+- Type: `integer`
+- Default: `30`
+
+チャンネルチャットのログファイルを保存する日数を指定します.
+
+`0` に設定すると,ログファイルは削除されません.
+
+#### `messageLogging.maxFileSizeMB`
+
+- Type: `integer`
+- Default: `100`
+
+チャンネルチャットのログファイルの最大サイズ(メガバイト)を指定します.
+
+`maxFileSizeMB` を超えたログファイルは新しいエントリを受け付けなくなります.
+
## Message Format Settings
使用できるプレースホルダー:
diff --git a/docs/src/guide/admin/data-and-logs.md b/docs/src/guide/admin/management-data.md
index 6f48857..5527ffa 100644
--- a/docs/src/guide/admin/data-and-logs.md
+++ b/docs/src/guide/admin/management-data.md
@@ -105,15 +105,3 @@ channelMessageNotification:
```json
{"version":"1","entries":{}}
```
-
-## CoreProtect について
-
-LunaticChat の各種チャットログは API 不要で CoreProtect でも記録することができます.
-
-各機能のログは次のアクションで確認できます. `/co lookup` コマンド使用時に以下のアクションを指定してください:
-
-- ダイレクトメッセージ: `command`
-- 全体チャット・チャンネルチャット: `chat`
- - かな・ローマ字変換はそのプレイヤーの設定状況により保存されます.
-
-
diff --git a/docs/src/index.md b/docs/src/index.md
index 430077f..c3410cb 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -32,9 +32,6 @@ features:
- title: かな・ローマ字変換
details: ローマ字で入力したメッセージを,自動的に日本語に変換
icon: 🌍
- - title: CoreProtect への対応
- details: LunaticChat のチャットログは CoreProtect と互換性があります
- icon: 🗒️
- title: チャンネルチャット機能
details: チャットチャンネルを作成・管理し,特定のプレイヤー間でのプライベートメッセージを送信可能
icon: ☎️
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)