
はじめに
こんにちは。
アプリケーションサービス本部ディベロップメントサービス1課の森山です。
2026/03/16 に、Amazon CloudWatch Logs で HTTP ベースのログ取り込みがサポートされました。
今回はこの機能を、プレビュー段階の Bearer トークン認証を使って試してみます。
この記事で学べること
- CloudWatch Logs HTTP エンドポイントの概要と 4 種類の違い
- Bearer トークン認証のセットアップ手順
- 各エンドポイントへの curl での送信方法
前提知識・条件
- AWS CLI がインストール済みであること(v2.34 以上が必要)
CloudWatch Logs HTTPエンドポイントとは
CloudWatch Logs が提供する HTTP エンドポイントで、HTTP POST リクエストでログを直接送信できる機能です。
AWS SDK の統合が不要なので、言語やプラットフォームを問わず利用できるのが特徴ですね。
4 種類のエンドポイントがあり、それぞれ異なるフォーマットに対応しています。
| エンドポイント | パス | 用途 |
|---|---|---|
| Structured JSON | /ingest/json |
JSON配列で送信 |
| ND-JSON | /ingest/bulk |
1行1JSONの改行区切りで送信 |
| HLC | /services/collector/event |
HLC(HTTP Log Collector)形式で送信 |
| OpenTelemetry | /v1/logs |
OTLP(OpenTelemetry Protocol)形式で送信 |
なお、HTTP エンドポイントを利用するには、認証が必要で、認証方式は 2 つ用意されています。
- SigV4 署名: 標準的な AWS Signature Version 4 による認証。IAM ロールやアクセスキーを使う場合はこちら
- Bearer トークン認証: IAM のサービス固有認証情報で発行したトークンを使う方式。AWS の認証情報を持たない外部システムからの送信に便利
今回は Bearer トークン認証で試していきます。
Bearer トークン認証について
Bearer トークン認証は Authorization: Bearer <トークン> ヘッダーを付けるだけで認証できる方式です。
トークンは IAM ユーザーに紐付けて発行する仕組みで、IAM のサービス固有認証情報(Service-Specific Credential)として管理されます。
ただし、現在はプレビュー段階で、対応リージョンは us-east-1、us-east-2、us-west-1、us-west-2 の 4 つのみです。
プレビューのため、今後仕様が大きく変更される可能性があります。
やってみた
では動作確認していきます。
環境準備
リソースの作成
まずは、AWS Cloud Development Kit(CDK)で以下のリソースを us-east-1 に作成します。
- CloudWatch Logs ロググループ(
/http-log-collector/test) - ログストリーム(
test-stream) - IAM ユーザー(
CloudWatchLogsAPIKeyAccessマネージドポリシーをアタッチ)- Bearer トークンはこのユーザーに紐付けて発行します
CloudWatchLogsAPIKeyAccessにはlogs:PutLogEventsとlogs:CallWithBearerTokenの権限を含みます
ソースは以下で公開しています。
Bearerトークン認証の有効化
以下、put-bearer-token-authentication コマンドで、指定のロググループに対して、Bearer トークン認証の有効化を行います。
aws logs put-bearer-token-authentication \
--log-group-identifier /http-log-collector/test \
--bearer-token-authentication-enabled \
--region us-east-1
なお、AWS CLI のバージョンが古いとエラーが出ました。 私の環境では v2.33.30 で未対応だったので、v2.34.11 にアップグレードしました。
マネジメントコンソールで設定する場合は以下の手順で対応可能です。



Bearerトークンの発行
次に、IAM のサービス固有認証情報として Bearer トークンを発行します。
aws iam create-service-specific-credential \
--user-name cloudwatch-http-log-collector-user \
--service-name logs.amazonaws.com \
--credential-age-days 30
レスポンスの ServiceCredentialSecret が Bearer トークンになります。発行時にしか取得できないので注意が必要です。
{ "ServiceSpecificCredential": { "CreateDate": "2026-03-20T15:55:06+00:00", "ExpirationDate": "2026-04-19T15:55:06+00:00", "ServiceName": "logs.amazonaws.com", "ServiceCredentialAlias": "cloudwatch-http-log-collector-user-at-xxxxxxxxxxxx", "ServiceCredentialSecret": "ACWL****(Bearer トークン)", "ServiceSpecificCredentialId": "ACCA****", "UserName": "cloudwatch-http-log-collector-user", "Status": "Active" } }
先ほど紹介した下記ページでは、マネジメントコンソール上での作成を推奨しています。
ただし 2026/03 時点では作成したキーを削除できないなど動作が不安定な部分があったので、今回は CLI で作成しました。
ログ送信
準備ができたので、curl コマンドで実際の動作確認をしていきます。
なお、以降の curl コマンドでは、共通の変数を使います。
BEARER_TOKEN="<ServiceCredentialSecretの値>" LOG_GROUP="/http-log-collector/test" LOG_STREAM="test-stream" REGION="us-east-1" NOW_MS=$(date +%s)000 NOW_SEC=$(date +%s).0 NOW_NS=$(date +%s)000000000
1. Structured JSON (/ingest/json)
JSON 配列で複数のログを一度に送信できるエンドポイントです。
今回は以下の送信データを用意し、動作確認してみます。
[ {"timestamp": 1773862553000, "message": "log entry 1"}, {"timestamp": 1773862553000, "message": "log entry 2"} ]
curl で送ってみます。
curl -s -X POST \
"https://logs.${REGION}.amazonaws.com/ingest/json?logGroup=${LOG_GROUP}&logStream=${LOG_STREAM}" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "Content-Type: application/json" \
-d "[{\"timestamp\": ${NOW_MS}, \"message\": \"log entry 1\"}, {\"timestamp\": ${NOW_MS}, \"message\": \"log entry 2\"}]"
成功すると、空の JSON オブジェクトが返ってきます。 マネジメントコンソール上でロググループを確認してみると、2 つログが出ていることが確認できました。

2. ND-JSON (/ingest/bulk)
次に ND-JSON(Newline Delimited JSON)形式です。
ND-JSON は 1 行に 1 つの JSON オブジェクトを改行で区切って送信する形式です。
以下の送信データで確認してみます。
{"timestamp": 1773862553000, "message": "log line 1"} {"timestamp": 1773862553000, "message": "log line 2"} {"timestamp": 1773862553000, "message": "log line 3"}
なお、curl の -d オプションは改行を削除してしまうため、1 行目しか CloudWatch Logs に届きませんでした。
--data-binary を使う必要があリます。
printf '{"timestamp": %s, "message": "log line 1"}\n{"timestamp": %s, "message": "log line 2"}\n{"timestamp": %s, "message": "log line 3"}' \
"$NOW_MS" "$NOW_MS" "$NOW_MS" | \
curl -s -X POST \
"https://logs.${REGION}.amazonaws.com/ingest/bulk?logGroup=${LOG_GROUP}&logStream=${LOG_STREAM}" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "Content-Type: application/x-ndjson" \
--data-binary @-
送信後、3 行すべてが CloudWatch Logs に記録されていることを確認できました。

3. HLC (/services/collector/event)
次は HLC(HTTP Log Collector)形式で確認してみます。
送信データは以下で確認します。
[ {"event": "log event 1", "time": 1773862553.0}, {"event": {"level": "INFO", "message": "log event 2"}, "time": 1773862553.0, "host": "web-server-01"} ]
event フィールドが必須となっていますが、event の中身は文字列や JSON オブジェクトに対応しています。
上の例では 1 件目が文字列、2 件目が JSON オブジェクトです。また、2 件目の host のようにトップレベルにカスタムフィールドを追加することもできます。
curl -s -X POST \
"https://logs.${REGION}.amazonaws.com/services/collector/event?logGroup=${LOG_GROUP}&logStream=${LOG_STREAM}" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "Content-Type: application/json" \
-d "[{\"event\": \"log event 1\", \"time\": ${NOW_SEC}}, {\"event\": {\"level\": \"INFO\", \"message\": \"log event 2\"}, \"time\": ${NOW_SEC}, \"host\": \"web-server-01\"}]"

4. OpenTelemetry (/v1/logs)
最後に OpenTelemetry(OTel)はアプリケーションのログ・メトリクス・トレースを統一的に収集・送信するためのオープンソース標準規格です。
他の 3 つと違って、ロググループ/ストリームはヘッダーで指定します(クエリパラメータは使えません)。
データ構造が resourceLogs > scopeLogs > logRecords とネストが深く、「どのサービスの、どのライブラリが出したログか」というメタデータを階層的に持てるようになっています。
送信データは以下のとおりです。
{ "resourceLogs": [{ "resource": { "attributes": [ {"key": "service.name", "value": {"stringValue": "my-app"}} ] }, "scopeLogs": [{ "scope": {"name": "my-library", "version": "1.0.0"}, "logRecords": [{ "timeUnixNano": "1773862553000000000", "body": {"stringValue": "Hello from OpenTelemetry endpoint"} }] }] }] }
resourceLogs[].resource.attributes: どのサービスから送られたか(ここではmy-app)scopeLogs[].scope: どのライブラリが出したか(ここではmy-libraryv1.0.0)logRecords[]: 実際のログ本体
curl -s -X POST \
"https://logs.${REGION}.amazonaws.com/v1/logs" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "Content-Type: application/json" \
-H "x-aws-log-group: ${LOG_GROUP}" \
-H "x-aws-log-stream: ${LOG_STREAM}" \
-d '{"resourceLogs": [{"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "my-app"}}]}, "scopeLogs": [{"scope": {"name": "my-library", "version": "1.0.0"}, "logRecords": [{"timeUnixNano": "'"${NOW_NS}"'", "body": {"stringValue": "Hello from OpenTelemetry endpoint"}}]}]}]}'

タイムスタンプが現時刻からずれているとどうなる?
タイムスタンプに現時刻から大きくずれた時間を指定するとどうなるか気になったので、Structured JSON で試してみます。
今回は 1 日前のタイムスタンプで送ってみました。
# 1日前のタイムスタンプ(ミリ秒)
ONE_DAY_AGO=$(( $(date +%s) - 86400 ))000
curl -s -X POST \
"https://logs.${REGION}.amazonaws.com/ingest/json?logGroup=${LOG_GROUP}&logStream=${LOG_STREAM}" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "Content-Type: application/json" \
-d "[{\"timestamp\": ${ONE_DAY_AGO}, \"message\": \"1 day ago\"}]"
失敗時のみ、レスポンスボディに partialSuccess が付与され、失敗理由がわかるようになっていますね。
今回はロググループのリテンション期間を検証用に 1 日と短く設定しているので、expiredLogEventCount として弾かれました。
{ "partialSuccess": { "rejectedLogRecords": 1, "errorMessage": "{\"tooOldLogEventCount\": 0, \"tooNewLogEventCount\": 0, \"expiredLogEventCount\": 1}" } }
4種類の比較
最後に各エンドポイントの比較表を載せておきます。
| 項目 | Structured JSON | ND-JSON | HLC | OpenTelemetry |
|---|---|---|---|---|
| パス | /ingest/json |
/ingest/bulk |
/services/collector/event |
/v1/logs |
| タイムスタンプ単位 | ミリ秒 | ミリ秒 | 秒 | ナノ秒 |
| 必須フィールド | なし | なし | event |
OTLP 形式に準拠 |
| 部分的成功レスポンス | あり | あり | なし | あり |
| ログ先指定方法 | クエリパラメータ/ヘッダー | クエリパラメータ/ヘッダー | クエリパラメータ/ヘッダー | ヘッダーのみ |
| Content-Type | application/json |
application/x-ndjson |
application/json |
application/json |
まとめ
CloudWatch Logs HTTP エンドポイントを使うと、AWS SDK なしに HTTP POST だけでログを送信できます。
4 種類のエンドポイントはそれぞれ異なるユースケースに対応しており、既存のログ基盤に合わせて少ない変更で適用できそうですね。
Bearer トークン認証を使えば、AWS の認証情報(アクセスキー/シークレットキー)を持たない外部システムからでもログを送信できるのが大きなメリットです。
ただし、現時点では Bearer トークン認証はプレビュー段階で、対応リージョンも限定的(us-east-1/us-east-2/us-west-1/us-west-2)です。
東京リージョンでの利用が可能になるのが楽しみですね。