Amazon Bedrockのモデル呼び出しログの上限値100KBってどんだけ?CloudWatch Logs/S3で実測してみた

記事タイトルとURLをコピーする

はじめに

こんにちは、久保(賢)です。

本記事ではAmazon Bedrockではモデル呼び出しログのCloudWatch Logsの上限値100KBが具体的にはどのくらいのサイズ感なのか?を調べた結果を共有します。

先に結論を書くと、CloudWatch Logsにインラインで残る本文は inputBodyJson / outputBodyJson のシリアライズ後UTF-8バイト数が基準で、100,000バイトまでは残り、100KBを超えると本文はS3参照または省略に切り替わりました。
また、入力と出力は別々に判定され、日本語では約33,000文字程度で100KBに到達します。

対象読者

  • Amazon Bedrockのモデル呼び出しログのCloudWatch Logsの上限値100KBがどのくらいのサイズ感なのか知りたい方
  • モデル呼び出しログをCloudWatch Logsだけにしているがログがロストしていないか不安な方
  • モデル呼び出しログを結局どうしておくのが推奨なのか知りたい方

Amazon Bedrockのモデル呼び出しログのCloudWatch Logsの上限値

Amazon Bedrockではモデル呼び出しログと呼ばれるログ出力を設定することで、対象リージョン内のBedrockモデル呼び出しについての詳細なログを収集することが可能です。

モデル呼び出しログはCloudWatch LogsかS3、もしくは両方に出力を設定することができますが、 CloudWatch Logsの場合100KBを超えるモデル入出力データは "大容量データ配信用のS3設定" がない場合出力されないという仕様があります。

設定画面にも以下のように記載されています。

100 KB を超えるか、またはバイナリ形式のモデル入出力データは CloudWatch Logs に発行されません。大容量データ配信用の S3 設定が指定されていない場合、そのモデルデータは発行されません。

モデル呼び出しログの内容

そもそもモデル呼び出しログにはどのような内容が含まれているのでしょうか。

Invoke/Converse/ConverseStreamの場合

以下のスクリプトで実際にモデルを呼び出し、ログを確認します。

import boto3
 
session = boto3.Session(
    region_name="us-east-1",
)
client = session.client("bedrock-runtime")
 
response = client.converse(
    modelId="global.anthropic.claude-opus-4-8",
    messages=[
        {
            "role": "user",
            "content": [{"text": "こんにちは"}],
        }
    ],
)
 
print(response["output"]["message"]["content"][0]["text"])
% python bedrock_hello.py
こんにちは!😊
 
何かお手伝いできることはありますか?質問やお話したいことがあれば、お気軽にどうぞ。

CloudWatch Logsに出力されたモデル呼び出しログは以下のとおりです。

ログを表示する
{
    "timestamp": "2026-06-06T05:37:19Z",
    "accountId": "123456789012",
    "region": "us-east-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "global.anthropic.claude-opus-4-8",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "こんにちは"
                        }
                    ]
                }
            ]
        },
        "inputTokenCount": 11,
        "cacheReadInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "こんにちは!😊\n\n何かお手伝いできることはありますか?質問や相談など、お気軽にどうぞ。"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 1450
            },
            "usage": {
                "inputTokens": 11,
                "cacheReadInputTokens": 0,
                "outputTokens": 43,
                "totalTokens": 54
            }
        },
        "outputTokenCount": 43
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/MyRole/my-user"
    },
    "inferenceRegion": "us-east-1",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

InvokeModelWithResponseStreamの場合

InvokeModelWithResponseStreamの場合です。こちらの場合はログが順次生成されたテキストの差分(デルタ)形式で出力されるため、ログのサイズが大きくなりやすいです。

import json
 
import boto3
 
session = boto3.Session(
    region_name="us-east-1",
)
client = session.client("bedrock-runtime")
 
body = json.dumps(
    {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1024,
        "messages": [
            {
                "role": "user",
                "content": [{"type": "text", "text": "こんにちは"}],
            }
        ],
    }
)
 
response = client.invoke_model_with_response_stream(
    modelId="global.anthropic.claude-opus-4-8",
    contentType="application/json",
    accept="application/json",
    body=body,
)
 
for event in response["body"]:
    chunk = json.loads(event["chunk"]["bytes"])
    if chunk["type"] == "content_block_delta":
        print(chunk["delta"]["text"], end="", flush=True)
 
print()
 % python bedrock_hello_stream.py
こんにちは!😊

何かお手伝いできることはありますか?気軽に話しかけてくださいね。

CloudWatch Logsに出力されたモデル呼び出しログは以下のとおりです。

output.outputBodyJsonの配列で生成AIの出力が順次記録されています。jsonとしての構造を示すテキストが増える分消費バイト数も増加します。

ログを表示する
{
    "timestamp": "2026-06-06T07:06:34Z",
    "accountId": "123456789012",
    "region": "us-east-1",
    "requestId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
    "operation": "InvokeModelWithResponseStream",
    "modelId": "arn:aws:bedrock:us-east-1:123456789012:inference-profile/global.anthropic.claude-opus-4-8",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 1024,
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": "こんにちは"
                        }
                    ]
                }
            ],
            "model": "global.anthropic.claude-opus-4-8",
            "stream": true
        },
        "inputTokenCount": 11,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": [
            {
                "type": "message_start",
                "message": {
                    "model": "claude-opus-4-8",
                    "id": "msg_bdrk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                    "type": "message",
                    "role": "assistant",
                    "content": [],
                    "stop_reason": null,
                    "stop_sequence": null,
                    "stop_details": null,
                    "usage": {
                        "input_tokens": 11,
                        "cache_creation_input_tokens": 0,
                        "cache_read_input_tokens": 0,
                        "cache_creation": {
                            "ephemeral_5m_input_tokens": 0,
                            "ephemeral_1h_input_tokens": 0
                        },
                        "output_tokens": 7,
                        "service_tier": "standard"
                    }
                }
            },
            {
                "type": "content_block_start",
                "index": 0,
                "content_block": {
                    "type": "text",
                    "text": ""
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "こんにちは!"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "😊"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "\n\n"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": ""
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "お手伝いできることは"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "ありますか?質"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "問や相談など"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "、お"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "気軽にど"
                }
            },
            {
                "type": "content_block_delta",
                "index": 0,
                "delta": {
                    "type": "text_delta",
                    "text": "うぞ。"
                }
            },
            {
                "type": "content_block_stop",
                "index": 0
            },
            {
                "type": "message_delta",
                "delta": {
                    "stop_reason": "end_turn",
                    "stop_sequence": null,
                    "stop_details": null
                },
                "usage": {
                    "input_tokens": 11,
                    "cache_creation_input_tokens": 0,
                    "cache_read_input_tokens": 0,
                    "output_tokens": 43
                }
            },
            {
                "type": "message_stop",
                "amazon-bedrock-invocationMetrics": {
                    "inputTokenCount": 11,
                    "outputTokenCount": 43,
                    "invocationLatency": 3171,
                    "firstByteLatency": 2979
                }
            }
        ],
        "outputTokenCount": 43
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/MyRole/my-user"
    },
    "inferenceRegion": "us-east-1",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

100KBの境界を実測してみる

「100KBを超えると出力されない」とはいうものの、

  • そもそも何のサイズが100KBなのか(トークン数?文字数?バイト数?)
  • 100KBは1,000バイト換算なのか1,024バイト換算なのか
  • 超えたときログはどうなるのか

このあたりを確かめます。

検証方法

Converse APIに対して、inputBodyJson のサイズを50KB〜130KBまで細かく変えながら呼び出し、CloudWatch Logsに本文がインラインで残るか/S3参照に切り替わるかを観測します。

ポイントは「プロンプトの文字数」ではなく「実際にログへ記録される inputBodyJson のシリアライズ後バイト数」を基準に確認することです。inputBodyJson はモデル呼び出しログ上では空白なしのコンパクトなJSONとして記録されると考えられるため、ローカルでも同じ形で計算してサイズを合わせます。

また、各呼び出しの requestMetadatarun_idlabel を付与しています。 requestMetadata はモデル呼び出しログのトップレベルにそのまま記録されるため、後からどのリクエストがどのサイズだったかを突合するのに便利です(後述のログ例にも出てきます)。

検証に使ったスクリプトの要点だけ抜粋します。

import json
import boto3
 
client = boto3.Session(region_name="us-east-1").client("bedrock-runtime")
 
def body_bytes(text: str) -> int:
    # Bedrockが inputBodyJson として記録する形(空白なしコンパクトJSON)を
    # ローカルで再現してUTF-8バイト数を測る
    obj = {
        "messages": [{"role": "user", "content": [{"text": text}]}],
        "inferenceConfig": {"maxTokens": 5},
    }
    return len(json.dumps(obj, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))
 
def make_text(fill: str, target_bytes: int) -> str:
    # inputBodyJson のサイズが target_bytes 以下で最大になるよう text を作る
    base = body_bytes("")
    n = max(0, (target_bytes - base) // len(fill.encode("utf-8")))
    text = fill * n
    while body_bytes(text + fill) <= target_bytes:
        text += fill
    return text
 
# 例: inputBodyJson を狙ったバイト数ちょうどにして呼び出す
text = make_text("a", 101 * 1000)   # 101KB
resp = client.converse(
    modelId="us.anthropic.claude-haiku-4-5-20251001-v1:0",
    messages=[{"role": "user", "content": [{"text": text}]}],
    inferenceConfig={"maxTokens": 5},
    requestMetadata={"run_id": "demo", "label": "ascii_101KB"},
)
# Converseのレスポンスに含まれる RequestId は、ログの requestId と一致するため突合に使える
print(resp["ResponseMetadata"]["RequestId"])

なお、ここで使うモデルは何でもOKなので、今回はコストを抑えるためClaude Haiku 4.5を使い、maxTokens も最小(出力は捨てる前提)にして、入力サイズだけを動かしています。

※100KBの上限はモデルの推論ではなく、モデル呼び出しログを書き出すログ基盤側の制約だからです。

検証結果

inputBodyJson のバイトサイズを刻みながら投げた結果が以下です。
出力先には「S3 と CloudWatch Logs の両方」を設定し、CloudWatch Logsには「大容量データ配信のための S3 の場所」も指定済みの環境で実施しています。

inputBodyJson
実バイト数
文字種 文字数 入力
トークン
CloudWatch Logs S3 (AWSLogs)
50,000 英数字 49,912 16,646 インライン インライン
90,000 英数字 89,912 29,979 インライン インライン
99,000 英数字 98,912 32,979 インライン インライン
100,000 英数字 99,912 33,312 インライン インライン
101,000 英数字 100,912 33,646 S3参照 S3参照
102,000 英数字 101,912 33,979 S3参照 S3参照
102,400(=100KiB) 英数字 102,312 34,112 S3参照 S3参照
105,000 英数字 104,912 34,979 S3参照 S3参照
130,000 英数字 129,912 43,312 S3参照 S3参照
94,999 日本語 31,637 15,827 インライン インライン
98,998 日本語 32,970 16,494 インライン インライン
104,998 日本語 34,970 17,494 S3参照 S3参照

※インライン: CloudWatch Logsのログイベントの inputBodyJson に本文がそのまま入っている状態
※S3参照: CloudWatch Logsのログイベントの inputBodyJson は消えて、代わりに inputBodyS3Path というフィールドが入る状態

わかったこと1: 「100KB」は inputBodyJson のバイト数(10進の100,000バイト)

  • inputBodyJson が 100,000バイト → インライン記録
  • inputBodyJson が 101,000バイト → S3参照に切り替わり

ログに記録される inputBodyJson のシリアライズ後UTF-8バイト数です。

さらに、100KiB(102,400バイト)はすでにS3参照に切り替わっています。
もし上限が1,024バイト換算(102,400バイト)なら102,400バイトはまだインラインのはずですが、実際には101,000バイトの時点で参照に切り替わっています。つまりこの「100KB」は1,000バイト換算の100,000バイトであり、100 KB = 102,400 bytes ではなさそうです。

わかったこと2: CloudWatch LogsとS3で境界は同じ

「CloudWatch Logsは100KB制限があるが、S3なら無制限なのでは?」と思うかもしれませんが、結果は CloudWatch LogsもS3(AWSLogs)も全く同じ100,000バイトで本文がオフロードされました。

これは、S3出力側のメインログ(AWSLogs/.../*.json.gz)も「CloudWatch Logsと同一のJSONスキーマ」で書かれているためです。本文が大きい場合はメインログには inputBodyS3Path という参照だけが入り、本文は別オブジェクト(data/{requestId}_input.json.gz)に退避されました。

わかったこと3: 100KBを超えると本文はS3参照に切り替わる

100KBを超えると、input の中身が inputBodyJson(本文インライン)から inputBodyS3Path(S3参照)に置き換わります。実際のCloudWatch Logsのログイベントが以下です。

{
    "timestamp": "2026-06-06T07:57:29Z",
    "accountId": "123456789012",
    "region": "us-east-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
    "requestMetadata": {
        "case": "10",
        "run_id": "20260606T075703Z",
        "label": "ascii_101KB"
    },
    "input": {
        "inputContentType": "application/json",
  #ココ--> "inputBodyS3Path": "s3://bedrock-logs-123456789012/long-logs/AWSLogs/123456789012/BedrockModelInvocationLogs/us-east-1/2026/06/06/07/data/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_input.json.gz",
        "inputTokenCount": 33646,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [{ "text": "# Hello! " }]
                }
            },
            "stopReason": "max_tokens",
            "metrics": { "latencyMs": 1182 },
            "usage": {
                "inputTokens": 33646,
                "outputTokens": 5,
                "totalTokens": 33651
            }
        },
        "outputTokenCount": 5
    },
    "inferenceRegion": "us-east-2",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

以下のとおりです。

  • inputBodyJson が消え、代わりに inputBodyS3Path が入っている(本文はインラインには残っていない)
  • inputTokenCount などのメタデータは残る(トークン数や呼び出し情報だけは追える)
  • 出力(output)は小さいので outputBodyJson はインラインのまま(入力と出力は別々に100KB判定される)

そして参照先のS3パスについては、CloudWatch Logs側の大容量データ退避先として指定していた long-logs/ プレフィックス配下に入るパスと、S3出力側のメインログに入るパスの両方が存在します。

  • CloudWatch Logs側のログイベントの inputBodyS3Path
    s3://バケット/long-logs/AWSLogs/.../data/{requestId}_input.json.gz (「大容量データ配信のための S3 の場所」に指定した long-logs プレフィックス配下)
  • S3出力側のメインログの inputBodyS3Path
    s3://バケット/AWSLogs/.../data/{requestId}_input.json.gz (S3出力先のツリー配下)

CloudWatch Logsで本文を追い切れなくても、long-logs/ 配下のS3オブジェクトを開けば本文を確認できます。逆に言うと、この「大容量データ配信のための S3 の場所」を設定していないと、100KB超の本文はロストします。

わかったこと4: 日本語は英数字の約3倍速く100KBに達する

UTF-8では日本語1文字がおよそ3バイトになります。今回の結果でも、

  • 英数字: inputBodyJson が100,000バイトに達するのは 約100,000文字
  • 日本語: inputBodyJson が約99,000バイトに達するのは 約33,000文字(98,998バイト=32,970文字)

「100KBなら10万文字くらい入るだろう」と英語感覚で考えていると、日本語では3分の1の文字数で到達します。RAGで日本語ドキュメントを大量に詰めるような使い方では、想像よりはるかに早く境界を超えます。

また、トークン数とも一致しません。英数字100,000バイトは約33,000トークン、日本語99,000バイトは約16,000トークンと、同じバイト数でもトークン数は倍ほど違います。100KB判定はあくまでバイト数で行われることを認識しておくとよさそうです。(当たり前なのですが)

100KBは日本語でどのくらいの分量なのか

実測では日本語の inputBodyJson は 98,998バイトで 32,970文字(3.003バイト/文字)でした。つまり 100KB ≒ 約33,000文字 です。
身近なものに換算すると以下のようになります。

基準 換算 100KB(≒33,000字)相当
原稿用紙(400字/枚) 33,000 ÷ 400 約83枚
文庫本(約600字/ページ) 33,000 ÷ 600 約55ページ
A4ワード文書(約1,600字/ページ) 33,000 ÷ 1,600 約20ページ
技術ブログ記事(1本3,000〜5,000字) 約7〜11本分

原稿用紙83枚分です。人間が手で書くプロンプトとしては、かなりの大きさだと分かります。「そんな量を1リクエストで送ることはまずないだろう」と感じるのも当然で、純粋な手書きテキストなら容易には100KBには届きません。
ただ会話が繰り返されていけば過去分の会話が入力として増加していくため、100KBになることも想定されます。

また、AI Agent では tool_usetool_result の往復、RAGで詰め込んだ検索結果、構造化出力、累積していく会話履歴などが乗ると、1リクエストの inputBodyJson は容易に数百KBへ膨らむ可能性はありそうです。

「大容量データ配信のための S3 の場所」を外すと本文はどうなるか

ここまでは「CloudWatch Logs + 大容量データ配信用S3 + S3出力」を全部設定した環境での挙動でした。次に、「大容量データ配信のための S3 の場所」だけを設定から外して、100KB超の本文がどうなるかを確かめます。設定としては以下の状態です。

  • CloudWatch Logs 出力:あり(大容量データ配信用S3はなし)
  • S3 出力:あり

入力が100KBを超える場合

先ほどと同じ要領で、対照として90KB、そして100KB超の105KB・120KBの入力を投げ、CloudWatch Logs と S3 のそれぞれを確認しました。

入力 inputBodyJson CloudWatch Logs の入力本文 CloudWatch の inputTokenCount S3(AWSLogs) の入力本文
90 KB(対照) インライン 29,979 インライン
105 KB 消失(フィールドごと無し) 34,979 data/ に保持
120 KB 消失(フィールドごと無し) 39,979 data/ に保持

100KBを超えた入力では、CloudWatch Logs のログイベントから inputBodyJsoninputBodyS3Path もまるごと消えました。
大容量データ配信用S3が無いので、参照すら残らない状態になりました。実際のログイベントが以下です。

{
    "timestamp": "2026-06-06T08:35:19Z",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
    "requestMetadata": {
        "label": "ascii_105KB"
    },
    "input": {
        "inputContentType": "application/json",
        "inputTokenCount": 34979,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": { "...": "(出力は小さいのでインライン)" },
        "outputTokenCount": 5
    },
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

input の中には inputContentTypeinputTokenCount、キャッシュ系のトークン数しか残っていません。本文は完全に消えていますが、トークン数や requestMetadatamodelId といったメタデータは残っています。
「何トークンの入力があったか」は分かるが「何を入力したか」は分からない、という状態です。

出力が100KBを超える場合

入力だけでなく出力でも確認します。出力本文(outputBodyJson)を100KB超にするため、小さな指示(入力185トークン)で長大な文章を生成させ、出力が64,000トークン(およそ数百KB)になる呼び出しを行いました。そのログイベントが以下です。

{
    "timestamp": "2026-06-06T08:51:17Z",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
    "requestMetadata": {
        "label": "output_over_100kb"
    },
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": { "messages": [ "...(プロンプト本文・小さいのでインライン)..." ] },
        "inputTokenCount": 185,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputTokenCount": 64000
    },
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

同じ1つのイベントの中で、入力はインライン記録され、出力本文だけが消えています。

  • input 側:inputBodyJson がインラインで残っている(入力は185トークンと小さいため)
  • output 側:outputBodyJsonoutputBodyS3Path も無く、outputContentTypeoutputTokenCount(64,000)だけが残っている

これは「入力と出力は別々に100KB判定される」ということです。
入力が小さければ残り、出力が大きければ消える。逆もまた然りです。

モデル呼び出しログをどう設定しておくのがよいか

目的別の推奨構成

ここまでの挙動を踏まえると、出力先の設定は「メトリクス(トークン数)が欲しいのか」と「本文を保全したいのか」の2軸で整理できます。目的別に3パターンを記載します。

目的 推奨構成 100KB超の本文の扱い
① 利用状況の把握(Logs Insightやメトリクスフィルタでユーザ別のトークン数など)。本文の中身は見なくてよい CloudWatch Logs のみ(大容量データ配信用S3は指定しない) CloudWatchから消える。ただし inputTokenCount/outputTokenCount は残る
② テキスト・バイナリすべての利用ログを漏れなく保全したい。CloudWatchでの検索・メトリクス化は不要 S3 出力 S3に全件保全(超過分は data/ へ)
③ CloudWatchでトークン数の監視・検索はしたいが、全コピーをS3に持つ必要はない CloudWatch Logs + 大容量データ配信用S3 超過分“だけ”S3(long-logs/)へ、CloudWatchには参照が残る

① 利用状況の監視が目的なら、CloudWatch Logsのみも選択肢

inputTokenCount/outputTokenCount は、本文が100KB超でロストしてもログに残ります。トークン数・呼び出し回数・モデルID・メタデータなどの利用状況把握が目的で、本文の保全が不要であれば、CloudWatch Logsのみでも運用できます。

ただし注意点として、これは「本文を記録しない」設定ではありません。100KB以下の入出力本文はCloudWatchにインラインで記録され続けます(消えるのは100KB超だけ)。
PII(個人情報)処理に関する規則等の理由で「内容を記録してはいけない」要件がある場合は、この構成では小さい本文が残ってしまうため、モデル呼び出しログ自体を使わず、アプリケーション側のメトリクスで記録する等の検討が必要です。

② 監査・全保全が目的なら、S3出力

テキストもバイナリも全件がS3に残り(100KB超は data/ にオフロード)、安価で、ライフサイクル・暗号化・アクセス制御も効きます。後から Athena で分析することもできます。CloudWatchでの即時検索やメトリクスが不要な、監査・コンプライアンス用途に向いています。

③ 監視しつつ、稀に出る巨大本文も追いたいなら、CloudWatch Logs + 大容量データ配信用S3

大容量データ配信用S3は「全コピー」ではなく、100KBを超えてあふれた分だけを受け取る退避先です。≤100KBはCloudWatchにインライン、超過分のみS3参照になります。「全コピーをS3に持つ必要はないが、稀に出る巨大プロンプト・巨大レスポンスは追えるようにしておきたい」というニーズにマッチします。

おわりに

Bedrockのモデル呼び出しログに関する100KBの制限と挙動を確認してみました。

最後に各条件のときのinput, outputのログ保存についてまとめます。

条件 CloudWatch Logs S3出力のメインログ 本文の実体
100KB以下 inputBodyJson / outputBodyJson にインライン インライン ログイベント内
100KB超 + 大容量データ配信用S3あり inputBodyS3Path / outputBodyS3Path S3参照 data/ 配下
100KB超 + 大容量データ配信用S3なし + CloudWatchのみ 本文フィールドなし なし 保全されない
100KB超 + S3出力あり CloudWatch側は設定次第 S3参照 S3の data/ 配下

ログを設定する際の参考になれば幸いです。

久保 賢二(執筆記事の一覧)

猫とAWSが好きです。