ボットのログ
アプリケーションの Bot タブから、実行中のボットに最近のログ行を要求できます。ボットが動いているマシンにログインしなくても、何が起きたかを確認できます。このページでは、ボットが応答するために実装するプロトコルを説明します。
仕組み
- 開発者(オーナー、またはチームの開発者以上)が Bot タブでログを要求を押し、期間と行数を選びます。
- GameVox はボットに
GAMEVOX_LOG_REQUESTという名前のゲートウェイ配信を送ります。配信先は、同じアプリケーションの接続中のゲートウェイセッションだけです。 - ボットはその期間のログ行を集め、REST 呼び出し 1 回でアップロードします。ポータルは届いた時点ですぐに表示します。
- 2 分以内に何も届かなければ、ポータルはボットが応答しなかったと表示します。このイベントを知らないボットは無視するだけなので、このイベントに対応していないライブラリでも何も壊れません。
ゲートウェイイベント
t が GAMEVOX_LOG_REQUEST の、通常の配信(オペコード 0)です。他の配信と同じくシーケンス番号を持ち、再開(resume)時には再送されます。
{
"op": 0,
"s": 4821,
"t": "GAMEVOX_LOG_REQUEST",
"d": {
"request_id": "6f0c2d1e-8a54-4b7e-9c3d-2a1f5e7b9d40",
"since": "2026-10-30T11:45:00Z",
"max_lines": 2000,
"nonce": "3f9a0c...e1b7",
"expires_at": "2026-10-30T12:02:00Z"
}
} request_id: リクエストの識別子。アップロード先 URL の一部になります。since: RFC 3339 の時刻。この時刻以降の行を送ってください。max_lines: 求める最大行数(50〜20,000)。それより多くある場合は新しいものを送ってください。nonce: このリクエスト専用の使い捨てのシークレット。アップロード時に送り返してください。リクエストを受け取ったボットからのアップロードであることの証明になります。expires_at: これを過ぎるとアップロードが拒否される RFC 3339 の時刻(リクエストの 2 分後)。
ログ行をアップロードする
他のボット呼び出しと同じ認証(Authorization: Bot <token>)で、1 回のリクエストで応答します。通常は JSON 形式です:
POST /api/v10/applications/@me/log-requests/6f0c2d1e-8a54-4b7e-9c3d-2a1f5e7b9d40
Authorization: Bot <token>
Content-Type: application/json
{
"nonce": "3f9a0c...e1b7",
"lines": [
{ "ts": "2026-10-30T11:58:02.114Z", "level": "info", "msg": "synced 12 guilds" },
{ "ts": "2026-10-30T11:58:09.870Z", "level": "error", "msg": "poll failed: timeout" }
]
}
HTTP/1.1 204 No Content 各行は {ts, level, msg} です。ts は RFC 3339 の時刻、level は debug、info、warn、error のいずれか、msg は本文です。オブジェクトの代わりに単なる文字列も受け付けます。
プレーンテキストも使えます。Content-Type: text/plain で 1 行ずつ送り、nonce は X-GameVox-Log-Nonce ヘッダーに入れてください。この方法で送った行にはタイムスタンプもレベルも付きません(GameVox が行内の単語からレベルを推測します)。このヘッダーは JSON でも使えます。ボディのフィールドは便宜上のものにすぎません。
上限と規則
- ボディは最大 2 MiB で、1 つのリクエストで保持されるのは最大 20,000 行です。行数の上限を超えた場合は最新の行が残ります。サイズの上限を超えるボディは 413 で拒否されます。
- リクエストに応答できるのは 1 回だけです。2 回目のアップロードには 409 が返ります。
- 応答できるのはリクエストの対象となったアプリケーションだけです。別のアプリケーションのリクエスト ID は、不明な ID と同じに見えます(404)。
- アップロードは
expires_atより前に届く必要があります(過ぎると 410)。 - nonce が一致する必要があります(一致しなければ 403)。
- リクエストはアプリケーションごとに 30 秒に 1 回、1 日 30 回までです。バンドルは 7 日間保存された後に削除されます。
- ログのテキストはプレーンテキストとしてのみ表示されます。制御文字は取り除かれ、各行は 4,000 文字で切り詰められます。
エラー
- 400: ボディが正しい JSON ではないか、nonce がありません。
- 403: nonce が一致しません。
- 404: このアプリケーションに該当する要求はありません。
- 409: リクエストにはすでに応答済みです。
- 410: リクエストの期限が切れています。
- 413: ボディが 2 MiB を超えています。
- 429: 他のルートと同じく、ボットがレート制限を受けています。
送信前にシークレットを取り除く
ログはあなたのマシンの外へ出るので、出してはいけないものは取り除いてください。ボットトークンやその他の資格情報、Authorization ヘッダー、URL 内のパスワード、メールアドレス、ユーザーの IP アドレスなどです。GameVox のファーストパーティボットは、アップロード前にこれらをすべて取り除いています。
GameVox も受信時に秘匿処理を行い(トークン、Authorization の値、キー名がシークレットを示す key=value の組、URL 内のパスワード、長い 16 進文字列、メールアドレス)、いくつの値を隠したかを閲覧者に伝えます。これは安全網であって、自分での秘匿処理を省いてよい理由にはなりません。メッセージの中の何が非公開かは GameVox にはわからないからです。
例
ほとんどのライブラリは、モデル化していないイベントを生のフック経由で渡します。discord.py では on_socket_raw_receive、discord.js では raw イベントです。直近の数千行をリングバッファに保持しておき、イベントが届いたら要求された範囲を送ってください。
# discord.py: keep recent lines, answer GAMEVOX_LOG_REQUEST
import collections, json, logging
from datetime import datetime, timezone
RING = collections.deque(maxlen=5000)
class Ring(logging.Handler):
def emit(self, record):
RING.append({"ts": datetime.fromtimestamp(record.created, timezone.utc).isoformat(),
"level": record.levelname.lower(),
"msg": redact(self.format(record))})
logging.getLogger().addHandler(Ring())
@client.event
async def on_socket_raw_receive(msg):
packet = json.loads(msg)
if packet.get("t") != "GAMEVOX_LOG_REQUEST":
return
d = packet["d"]
lines = [l for l in RING if l["ts"] >= d["since"]][-d["max_lines"]:]
await session.post(
f"https://app-api.gamevox.com/api/v10/applications/@me/log-requests/{d['request_id']}",
headers={"Authorization": f"Bot {TOKEN}"},
json={"nonce": d["nonce"], "lines": lines}) 互換性
このイベントは新しく、対応は任意です。ライブラリは知らない配信を無視するため、Discord のライブラリで書かれたボットは変更なしでそのまま動きます。GameVox のファーストパーティボット(League of Legends、VALORANT、Deadlock、Valheim、WARDOGS、WoW ギルド、Tickets、Tibia)は、共通のボットライブラリを通じて対応しています。