CloudWatch Logs HTTPエンドポイントの4種類をBearerトークン認証で試してみた

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

はじめに

こんにちは。

アプリケーションサービス本部ディベロップメントサービス1課の森山です。

2026/03/16 に、Amazon CloudWatch Logs で HTTP ベースのログ取り込みがサポートされました。

aws.amazon.com

今回はこの機能を、プレビュー段階の Bearer トークン認証を使って試してみます。

この記事で学べること

  • CloudWatch Logs HTTP エンドポイントの概要と 4 種類の違い
  • Bearer トークン認証のセットアップ手順
  • 各エンドポイントへの curl での送信方法

前提知識・条件

  • AWS CLI がインストール済みであること(v2.34 以上が必要)

CloudWatch Logs HTTPエンドポイントとは

CloudWatch Logs が提供する HTTP エンドポイントで、HTTP POST リクエストでログを直接送信できる機能です。

AWS SDK の統合が不要なので、言語やプラットフォームを問わず利用できるのが特徴ですね。

docs.aws.amazon.com

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 <トークン> ヘッダーを付けるだけで認証できる方式です。

docs.aws.amazon.com

トークンは 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:PutLogEventslogs:CallWithBearerToken の権限を含みます

ソースは以下で公開しています。

github.com

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

docs.aws.amazon.com

なお、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

awscli.amazonaws.com

レスポンスの 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"
    }
}

先ほど紹介した下記ページでは、マネジメントコンソール上での作成を推奨しています。

docs.aws.amazon.com

ただし 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 配列で複数のログを一度に送信できるエンドポイントです。

docs.aws.amazon.com

今回は以下の送信データを用意し、動作確認してみます。

[
  {"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 オブジェクトを改行で区切って送信する形式です。

docs.aws.amazon.com

以下の送信データで確認してみます。

{"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)形式で確認してみます。

docs.aws.amazon.com

送信データは以下で確認します。

[
  {"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)はアプリケーションのログ・メトリクス・トレースを統一的に収集・送信するためのオープンソース標準規格です。

docs.aws.amazon.com

他の 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-library v1.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)です。

東京リージョンでの利用が可能になるのが楽しみですね。

森山 智史 (記事一覧)

アプリケーションサービス本部ディベロップメントサービス1課

2025年10月中途入社。

AWS Community Builders Serverless 2026