<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>カスタムメトリクス - CayTech Lab</title>
	<atom:link href="https://caymezon.com/tag/%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9/feed/" rel="self" type="application/rss+xml" />
	<link>https://caymezon.com</link>
	<description></description>
	<lastBuildDate>Sun, 08 Mar 2026 05:02:41 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://caymezon.com/wp-content/uploads/2026/01/cropped-CayTechLab-32x32.jpg</url>
	<title>カスタムメトリクス - CayTech Lab</title>
	<link>https://caymezon.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<atom:link rel='hub' href='https://caymezon.com/?pushpress=hub'/>
	<item>
		<title>AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-console-chatbot-log-api/</link>
					<comments>https://caymezon.com/aws-handson-console-chatbot-log-api/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 05:02:41 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[APIGateway]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWSコンソール]]></category>
		<category><![CDATA[CloudWatchLogs]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[MetricFilter]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[カスタムメトリクス]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[チャットボット]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20257</guid>

					<description><![CDATA[<p>目次 はじめにキーワード解説使用するAWSサービス全体の作業順序① CloudWatch Log Group を作成する② CloudWatch Metric Filter を作成するフィルターパターンの設定メトリクスの [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-console-chatbot-log-api/">AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-2" checked><label class="toc-title" for="toc-checkbox-2">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">キーワード解説</a></li><li><a href="#toc3" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc4" tabindex="0">全体の作業順序</a></li><li><a href="#toc5" tabindex="0">① CloudWatch Log Group を作成する</a></li><li><a href="#toc6" tabindex="0">② CloudWatch Metric Filter を作成する</a><ol><li><a href="#toc7" tabindex="0">フィルターパターンの設定</a></li><li><a href="#toc8" tabindex="0">メトリクスの設定</a></li></ol></li><li><a href="#toc9" tabindex="0">③ Lambda 関数を作成する</a><ol><li><a href="#toc10" tabindex="0">タイムアウトの設定</a></li><li><a href="#toc11" tabindex="0">環境変数の設定</a></li><li><a href="#toc12" tabindex="0">コードの入力</a></li></ol></li><li><a href="#toc13" tabindex="0">④ Lambda の実行ロールに権限を追加する</a><ol><li><a href="#toc14" tabindex="0">CloudWatch Logs と CloudWatch の権限をまとめて追加する</a></li></ol></li><li><a href="#toc15" tabindex="0">⑤ API Gateway REST API を作成する</a><ol><li><a href="#toc16" tabindex="0">5-1. API の作成</a></li><li><a href="#toc17" tabindex="0">5-2. /logs リソースを作成する</a></li><li><a href="#toc18" tabindex="0">5-3. POST メソッドを作成する（ログ書き込み）</a></li><li><a href="#toc19" tabindex="0">5-4. GET メソッドを作成する（ログ取得）</a></li><li><a href="#toc20" tabindex="0">5-5. API をデプロイする</a></li></ol></li><li><a href="#toc21" tabindex="0">⑥ 動作テスト</a><ol><li><a href="#toc22" tabindex="0">事前準備: API URL を変数に設定する</a></li><li><a href="#toc23" tabindex="0">テスト 1: ログを記録する（POST /logs）</a></li><li><a href="#toc24" tabindex="0">テスト 2: エラーログを記録する（Metric Filter のテスト）</a></li><li><a href="#toc25" tabindex="0">テスト 3: ログを取得する（GET /logs）</a></li><li><a href="#toc26" tabindex="0">CloudWatch でログを確認する</a></li><li><a href="#toc27" tabindex="0">CloudWatch Logs Insights でクエリを実行する（任意）</a></li><li><a href="#toc28" tabindex="0">カスタムメトリクスを確認する（任意）</a></li></ol></li><li><a href="#toc29" tabindex="0">⑦ リソースの削除</a><ol><li><a href="#toc30" tabindex="0">1. API Gateway を削除する</a></li><li><a href="#toc31" tabindex="0">2. Lambda 関数を削除する</a></li><li><a href="#toc32" tabindex="0">3. CloudWatch Log Group を削除する</a></li><li><a href="#toc33" tabindex="0">4. IAM ロールを削除する（任意）</a></li><li><a href="#toc34" tabindex="0">5. CloudWatch Lambda ロググループを削除する（任意）</a></li></ol></li><li><a href="#toc35" tabindex="0">SAM との対比</a></li><li><a href="#toc36" tabindex="0">トラブルシューティング</a></li><li><a href="#toc37" tabindex="0">まとめ</a><ol><li><a href="#toc38" tabindex="0">コンソール版で実感できたポイント</a></li></ol></li><li><a href="#toc39" tabindex="0">コンソール版と SAM 版を比較してみる</a></li><li><a href="#toc40" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「チャットボットの会話ログをAPIで受け取り、AWSに蓄積したい」——そんな要件を実現する構成が <strong>API Gateway + Lambda + CloudWatch Logs</strong> の組み合わせです。</p>
<p>この記事では、<strong>AWSコンソールのみ</strong>を使って、チャットボットの会話ログを記録・取得するREST APIをゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">クライアント
  ↓ POST /logs（会話ログを送信）
API Gateway（REST API）
  ↓ Lambda プロキシ統合
Lambda（ChatLogFunction / Python 3.12）
  ↓ boto3 logs.put_log_events()
CloudWatch Log Group（/chatbot/chatbot-logs）
  ↓ Metric Filter（level = "error" のログを検出）
CloudWatch カスタムメトリクス（ChatErrorCount）</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li>API Gateway REST API のリソース・メソッド作成と Lambda プロキシ統合</li>
<li>CloudWatch Logs カスタムロググループへのログ書き込み（<code>put_log_events</code>）</li>
<li>Metric Filter によるエラーログの自動メトリクス変換</li>
<li>CloudWatch Logs Insights でのログクエリ実行</li>
</ul>
<hr>
<blockquote>
<p><strong>この記事は <a href="#">SAM版ハンズオン</a> の比較記事です。</strong><br />コンソール操作でAPI Gateway・Lambda・CloudWatch Logsの連携を視覚的に学び、SAM と何が違うのかを比較したい方向けです。</p>
</blockquote>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CloudWatch Log Group</strong></td>
<td>ログの論理的なまとまり。今回はチャットログ専用グループを作成する</td>
</tr>
<tr>
<td><strong>Log Stream</strong></td>
<td>Log Group の中の個別のログストリーム。今回はセッションIDごとに作成する</td>
</tr>
<tr>
<td><strong>put_log_events</strong></td>
<td>boto3 でカスタムロググループにログを書き込む API</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td>ロググループに届いたログを JSON フィルターでマッチさせ、カスタムメトリクスを自動生成する仕組み</td>
</tr>
<tr>
<td><strong>カスタムメトリクス</strong></td>
<td>CloudWatch に独自のメトリクスを記録する機能。アラーム設定やダッシュボードに使える</td>
</tr>
<tr>
<td><strong>Lambda プロキシ統合</strong></td>
<td>API Gateway が HTTP リクエスト全体を Lambda に渡す最もシンプルな統合方式</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway</strong></td>
<td>REST APIのエンドポイント（POST /logs, GET /logs）</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>ログの書き込み・取得処理（Python 3.12）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>チャットログの保存・クエリ</td>
<td>月5GBまで無料</td>
</tr>
<tr>
<td><strong>CloudWatch メトリクス</strong></td>
<td>エラーログのカスタムメトリクス化</td>
<td>月10件まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">全体の作業順序</span></h2>
<pre><code class="language-plaintext">① CloudWatch Log Group を作成する
      ↓
② CloudWatch Metric Filter を作成する（error ログ → メトリクス）
      ↓
③ Lambda 関数を作成する（コード・環境変数・タイムアウト設定）
      ↓
④ Lambda の実行ロールに権限を追加する
  （CloudWatch Logs: 書き込み・読み取り / CloudWatch: PutMetricData）
      ↓
⑤ API Gateway REST API を作成する（POST /logs, GET /logs）
      ↓
⑥ 動作テスト（curl でログ送信 → コンソールで確認）
      ↓
⑦ リソースの削除</code></pre>
<blockquote>
<p><strong>Log Group を先に作る理由：</strong><br />Metric Filter の作成にはロググループが必要なため、最初に作成します。</p>
</blockquote>
<hr>
<h2><span id="toc5">① CloudWatch Log Group を作成する</span></h2>
<p><strong>AWSコンソール → CloudWatch → ロググループ → 「ロググループの作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ロググループ名</td>
<td><code>/chatbot/chatbot-logs</code></td>
</tr>
<tr>
<td>保持期間</td>
<td><strong>7日間</strong></td>
</tr>
</tbody>
</table>
<p>「作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>ロググループ名</strong>: <code>/chatbot/chatbot-logs</code></li>
</ul>
<hr>
<h2><span id="toc6">② CloudWatch Metric Filter を作成する</span></h2>
<p>エラーログを自動的にカスタムメトリクスに変換するフィルターを設定します。</p>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → 「メトリクスフィルター」タブ → 「メトリクスフィルターを作成」</strong></p>
<h3><span id="toc7">フィルターパターンの設定</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>フィルターパターン</td>
<td><code>{ $.level = &quot;error&quot; }</code></td>
</tr>
</tbody>
</table>
<p>「パターンのテスト」でテストデータを入力して確認できます（任意）。「次へ」をクリック。</p>
<h3><span id="toc8">メトリクスの設定</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>フィルター名</td>
<td><code>ChatErrorFilter</code></td>
</tr>
<tr>
<td>メトリクスの名前空間</td>
<td><code>ChatBot/chatbot-logs</code></td>
</tr>
<tr>
<td>メトリクス名</td>
<td><code>ChatErrorCount</code></td>
</tr>
<tr>
<td>メトリクス値</td>
<td><code>1</code></td>
</tr>
<tr>
<td>デフォルト値</td>
<td><code>0</code></td>
</tr>
</tbody>
</table>
<p>「次へ」→「メトリクスフィルターを作成」。</p>
<blockquote>
<p><strong>Metric Filter の仕組み:</strong><br />CloudWatch Logs に届いたログが <code>{ $.level = &quot;error&quot; }</code> にマッチするたびに、<br /><code>ChatBot/chatbot-logs</code> 名前空間の <code>ChatErrorCount</code> に <code>1</code> が加算されます。<br />ログに <code>{&quot;level&quot;: &quot;error&quot;, ...}</code> という JSON が含まれていればマッチします。</p>
</blockquote>
<hr>
<h2><span id="toc9">③ Lambda 関数を作成する</span></h2>
<p><strong>AWSコンソール → Lambda → 「関数の作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>作成方法</td>
<td>一から作成</td>
</tr>
<tr>
<td>関数名</td>
<td><code>ChatLogFunction</code></td>
</tr>
<tr>
<td>ランタイム</td>
<td><strong>Python 3.12</strong></td>
</tr>
<tr>
<td>アーキテクチャ</td>
<td>x86_64</td>
</tr>
<tr>
<td>実行ロール</td>
<td>「基本的な Lambda アクセス権限で新しいロールを作成」（デフォルト）</td>
</tr>
</tbody>
</table>
<p>「関数の作成」をクリック。</p>
<h3><span id="toc10">タイムアウトの設定</span></h3>
<p><strong>「設定」タブ → 「一般設定」→「編集」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイムアウト</td>
<td><strong>10</strong> 秒</td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<h3><span id="toc11">環境変数の設定</span></h3>
<p><strong>「設定」タブ → 「環境変数」→「編集」→「環境変数を追加」</strong></p>
<table>
<thead>
<tr>
<th>キー</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LOG_GROUP_NAME</code></td>
<td><code>/chatbot/chatbot-logs</code></td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<h3><span id="toc12">コードの入力</span></h3>
<p>「コード」タブ → <code>lambda_function.py</code> を開いて既存の内容を全て置き換えます。</p>
<pre><code class="language-python">import json
import os
import time
import uuid

import boto3
from botocore.exceptions import ClientError

logs_client = boto3.client("logs")
cloudwatch = boto3.client("cloudwatch")

LOG_GROUP_NAME = os.environ["LOG_GROUP_NAME"]
METRIC_NAMESPACE = "ChatBot/" + LOG_GROUP_NAME.split("/")[-1]  # "ChatBot/chatbot-logs"


def lambda_handler(event, context):
    http_method = event.get("httpMethod", "")
    resource = event.get("resource", "/")

    if http_method == "POST" and resource == "/logs":
        return post_log(event)
    elif http_method == "GET" and resource == "/logs":
        return get_logs(event)
    else:
        return _response(404, {"error": "Not Found"})


def post_log(event):
    try:
        body = json.loads(event.get("body") or "{}")
    except json.JSONDecodeError:
        return _response(400, {"error": "Invalid JSON"})

    session_id = body.get("session_id") or str(uuid.uuid4())
    user_id = body.get("user_id", "anonymous")
    message = body.get("message", "")
    response_text = body.get("response", "")
    level = body.get("level", "info")

    log_entry = {
        "session_id": session_id,
        "user_id": user_id,
        "message": message,
        "response": response_text,
        "level": level,
    }

    log_stream_name = f"session/{session_id}"
    _ensure_log_stream(log_stream_name)  # ストリームを作成（既存なら無視）
    logs_client.put_log_events(
        logGroupName=LOG_GROUP_NAME,
        logStreamName=log_stream_name,
        logEvents=[
            {
                "timestamp": int(time.time() * 1000),  # ミリ秒単位の Unix 時間
                "message": json.dumps(log_entry, ensure_ascii=False),
            }
        ],
    )

    cloudwatch.put_metric_data(
        Namespace=METRIC_NAMESPACE,
        MetricData=[
            {
                "MetricName": "MessageCount",
                "Dimensions": [{"Name": "Level", "Value": level}],
                "Value": 1,
                "Unit": "Count",
            }
        ],
    )

    return _response(200, {
        "session_id": session_id,
        "log_stream": log_stream_name,
        "message": "ログを記録した",
    })


def get_logs(event):
    params = event.get("queryStringParameters") or {}
    session_id = params.get("session_id")
    limit = min(int(params.get("limit", "20")), 100)

    if not session_id:
        return _response(400, {"error": "session_id クエリパラメーターは必須"})

    log_stream_name = f"session/{session_id}"

    try:
        resp = logs_client.get_log_events(
            logGroupName=LOG_GROUP_NAME,
            logStreamName=log_stream_name,
            limit=limit,
            startFromHead=False,  # 最新のログから取得
        )
        events = []
        for e in resp["events"]:
            try:
                msg = json.loads(e["message"])
            except json.JSONDecodeError:
                msg = e["message"]
            events.append({"timestamp_ms": e["timestamp"], "log": msg})
    except ClientError as e:
        if e.response["Error"]["Code"] == "ResourceNotFoundException":
            events = []  # ログストリームが存在しない場合は空リストを返す
        else:
            raise

    return _response(200, {
        "session_id": session_id,
        "log_count": len(events),
        "logs": events,
    })


def _ensure_log_stream(log_stream_name: str) -&gt; None:
    try:
        logs_client.create_log_stream(
            logGroupName=LOG_GROUP_NAME,
            logStreamName=log_stream_name,
        )
    except ClientError as e:
        if e.response["Error"]["Code"] != "ResourceAlreadyExistsException":
            raise  # 既存ストリームは無視、それ以外のエラーは再送出


def _response(status_code: int, body: dict) -&gt; dict:
    return {
        "statusCode": status_code,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(body, ensure_ascii=False),
    }</code></pre>
<p>「Deploy」ボタンをクリックしてコードを保存します。</p>
<hr>
<h2><span id="toc13">④ Lambda の実行ロールに権限を追加する</span></h2>
<p>デフォルトの実行ロールは CloudWatch Logs への書き込み（Lambdaの実行ログ出力）のみです。<br />今回はカスタムロググループへの書き込み・読み取りと、カスタムメトリクスへの書き込み権限を追加します。</p>
<p><strong>Lambda → <code>ChatLogFunction</code> → 「設定」タブ → 「アクセス権限」→ 実行ロール名のリンクをクリック</strong></p>
<p>（例: <code>ChatLogFunction-role-XXXX</code>）→ IAM コンソールのロール画面が開きます。</p>
<h3><span id="toc14">CloudWatch Logs と CloudWatch の権限をまとめて追加する</span></h3>
<p>「許可を追加」→「インラインポリシーを作成」→「JSON」タブを選択して以下を入力します。</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:GetLogEvents",
        "logs:DescribeLogStreams"
      ],
      "Resource": [
        "arn:aws:logs:ap-northeast-1:YOUR_ACCOUNT_ID:log-group:/chatbot/chatbot-logs",
        "arn:aws:logs:ap-northeast-1:YOUR_ACCOUNT_ID:log-group:/chatbot/chatbot-logs:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "cloudwatch:PutMetricData",
      "Resource": "*"
    }
  ]
}</code></pre>
<blockquote>
<p><code>YOUR_ACCOUNT_ID</code> を自分の AWS アカウント ID（12桁）に置き換えてください。<br />アカウント ID は <code>aws sts get-caller-identity</code> で確認できます。</p>
</blockquote>
<p>「次へ」→ ポリシー名: <code>ChatLogPolicy</code> → 「ポリシーを作成」。</p>
<blockquote>
<p><strong>なぜ 2 種類の権限が必要か:</strong></p>
<ul>
<li><code>logs:PutLogEvents</code> — <code>put_log_events()</code> でカスタムロググループにログを書き込む</li>
<li><code>logs:GetLogEvents</code> — <code>get_log_events()</code> でログを読み取る</li>
<li><code>cloudwatch:PutMetricData</code> — <code>put_metric_data()</code> でカスタムメトリクスを記録する</li>
</ul>
</blockquote>
<hr>
<h2><span id="toc15">⑤ API Gateway REST API を作成する</span></h2>
<h3><span id="toc16">5-1. API の作成</span></h3>
<p><strong>AWSコンソール → API Gateway → 「APIを作成」→ 「REST API」→「構築」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>API タイプ</td>
<td>REST API（WebSocket ではない方）</td>
</tr>
<tr>
<td>API 名</td>
<td><code>ChatLogAPI</code></td>
</tr>
<tr>
<td>エンドポイントタイプ</td>
<td>リージョン</td>
</tr>
</tbody>
</table>
<p>「API を作成」をクリック。</p>
<h3><span id="toc17">5-2. /logs リソースを作成する</span></h3>
<p><strong>「リソース」→「リソースを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>リソース名</td>
<td><code>logs</code></td>
</tr>
<tr>
<td>リソースパス</td>
<td><code>/logs</code></td>
</tr>
</tbody>
</table>
<p>「リソースを作成」をクリック。</p>
<h3><span id="toc18">5-3. POST メソッドを作成する（ログ書き込み）</span></h3>
<p><code>/logs</code> リソースを選択した状態で「メソッドを作成」。</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>メソッドタイプ</td>
<td><strong>POST</strong></td>
</tr>
<tr>
<td>統合タイプ</td>
<td><strong>Lambda 関数</strong></td>
</tr>
<tr>
<td>Lambda プロキシ統合</td>
<td><strong>有効（チェックを入れる）</strong></td>
</tr>
<tr>
<td>Lambda 関数</td>
<td><code>ChatLogFunction</code></td>
</tr>
</tbody>
</table>
<p>「メソッドを作成」をクリック。</p>
<h3><span id="toc19">5-4. GET メソッドを作成する（ログ取得）</span></h3>
<p>同様に <code>GET</code> メソッドを追加します。設定は POST と同じ（Lambda 関数: <code>ChatLogFunction</code>）。</p>
<h3><span id="toc20">5-5. API をデプロイする</span></h3>
<p><strong>「API をデプロイ」→</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ステージ</td>
<td>「新しいステージ」</td>
</tr>
<tr>
<td>ステージ名</td>
<td><code>Prod</code></td>
</tr>
</tbody>
</table>
<p>「デプロイ」をクリック。</p>
<p><strong>デプロイ後に表示される URL を控えておきます:</strong></p>
<pre><code class="language-plaintext">https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code></pre>
<hr>
<h2><span id="toc21">⑥ 動作テスト</span></h2>
<h3><span id="toc22">事前準備: API URL を変数に設定する</span></h3>
<pre><code class="language-cmd">set API_URL=https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code></pre>
<blockquote>
<p><code>XXXXXXXXXX</code> を ⑤ でデプロイ後に表示された URL の値に置き換えてください。</p>
</blockquote>
<hr>
<h3><span id="toc23">テスト 1: ログを記録する（POST /logs）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"今日の天気は？\", \"response\": \"東京は晴れです\", \"level\": \"info\"}"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_stream": "session/session-001",
  "message": "ログを記録した"
}</code></pre>
<p><!-- ![POST /logs のレスポンス確認](images/post-logs-response.jpg) --></p>
<hr>
<h3><span id="toc24">テスト 2: エラーログを記録する（Metric Filter のテスト）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"注文履歴を見せて\", \"response\": \"エラーが発生しました\", \"level\": \"error\"}"</code></pre>
<blockquote>
<p>このリクエスト後、CloudWatch メトリクス <code>ChatBot/chatbot-logs &gt; ChatErrorCount</code> が +1 されます。<br />反映まで数分かかることがあります。</p>
</blockquote>
<hr>
<h3><span id="toc25">テスト 3: ログを取得する（GET /logs）</span></h3>
<pre><code class="language-cmd">curl "%API_URL%/logs?session_id=session-001"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_count": 2,
  "logs": [
    {
      "timestamp_ms": 1700000000000,
      "log": {
        "session_id": "session-001",
        "user_id": "user001",
        "message": "今日の天気は？",
        "response": "東京は晴れです",
        "level": "info"
      }
    }
  ]
}</code></pre>
<p><!-- ![GET /logs のレスポンス確認](images/get-logs-response.jpg) --></p>
<hr>
<h3><span id="toc26">CloudWatch でログを確認する</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → ログストリーム</strong></p>
<ul>
<li><code>session/session-001</code> ストリームをクリック</li>
<li>送信した会話ログが JSON 形式で記録されていることを確認します</li>
</ul>
<p><!-- ![CloudWatch Logs のセッションストリーム](images/cloudwatch-log-stream.jpg) --></p>
<h3><span id="toc27">CloudWatch Logs Insights でクエリを実行する（任意）</span></h3>
<p><strong>CloudWatch → Logs Insights</strong></p>
<p>ロググループに <code>/chatbot/chatbot-logs</code> を選択し、<strong>時間範囲を「直近1時間」など幅のある範囲に設定</strong>してから以下のクエリを実行します。</p>
<blockquote>
<p><strong>注意</strong>: 開始時刻と終了時刻が同じだとエラー <code>Query&#39;s end date and time is either before the log groups creation time...</code> が出ます。カレンダーアイコンで時間幅を確保してください。</p>
</blockquote>
<pre><code class="language-plaintext">fields @timestamp, session_id, user_id, level, message
| filter level = "error"
| sort @timestamp desc
| limit 20</code></pre>
<p>エラーログだけを抽出して確認できます。</p>
<h3><span id="toc28">カスタムメトリクスを確認する（任意）</span></h3>
<p><strong>CloudWatch → メトリクス → すべてのメトリクス → カスタム名前空間 → <code>ChatBot/chatbot-logs</code></strong></p>
<table>
<thead>
<tr>
<th>メトリクス名</th>
<th>説明</th>
<th>発生元</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChatErrorCount</code></td>
<td>error ログが書き込まれるたびに +1</td>
<td>Metric Filter（自動）</td>
</tr>
<tr>
<td><code>MessageCount</code></td>
<td>POST /logs が呼ばれるたびに +1</td>
<td>Lambda の put_metric_data</td>
</tr>
</tbody>
</table>
<p><!-- ![CloudWatch カスタムメトリクスの確認画面](images/cloudwatch-custom-metrics.jpg) --></p>
<hr>
<h2><span id="toc29">⑦ リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ず削除してください。</strong></p>
<h3><span id="toc30">1. API Gateway を削除する</span></h3>
<p><strong>API Gateway → API → <code>ChatLogAPI</code> → 「削除」→ API 名を入力 → 「削除」</strong></p>
<h3><span id="toc31">2. Lambda 関数を削除する</span></h3>
<p><strong>Lambda → 関数 → <code>ChatLogFunction</code> → 「アクション」→「削除」</strong></p>
<h3><span id="toc32">3. CloudWatch Log Group を削除する</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → 「アクション」→「ロググループを削除」</strong></p>
<blockquote>
<p>Metric Filter はロググループを削除すると自動的に削除されます。</p>
</blockquote>
<h3><span id="toc33">4. IAM ロールを削除する（任意）</span></h3>
<p><strong>IAM → ロール → <code>ChatLogFunction-role-XXXX</code> を削除</strong></p>
<h3><span id="toc34">5. CloudWatch Lambda ロググループを削除する（任意）</span></h3>
<p><strong>CloudWatch → ロগগループ → <code>/aws/lambda/ChatLogFunction</code> → 削除</strong></p>
<hr>
<h2><span id="toc35">SAM との対比</span></h2>
<table>
<thead>
<tr>
<th>SAMの記述</th>
<th>コンソールでやること</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::Logs::LogGroup</code></td>
<td>CloudWatch → ロググループを作成</td>
</tr>
<tr>
<td><code>AWS::Logs::MetricFilter</code></td>
<td>ロググループ → メトリクスフィルターを作成</td>
</tr>
<tr>
<td><code>Statement: logs:PutLogEvents</code></td>
<td>IAM → ロールにインラインポリシーを追加</td>
</tr>
<tr>
<td><code>Statement: cloudwatch:PutMetricData</code></td>
<td>同上（同じポリシーに含める）</td>
</tr>
<tr>
<td><code>Events: Type: Api</code>（POST + GET）</td>
<td>API Gateway → リソース・メソッドを作成してデプロイ</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版のつまずきポイント:</strong><br />IAM のインラインポリシーに <code>YOUR_ACCOUNT_ID</code> を自分のアカウントIDに書き換えるのを忘れると <code>AccessDeniedException</code> が発生します。<br />SAM版では <code>!GetAtt ChatLogGroup.Arn</code> で自動解決されるため、この手作業が不要です。</p>
</blockquote>
<hr>
<h2><span id="toc36">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AccessDeniedException: logs:PutLogEvents</code></td>
<td>Lambda ロールに権限がない</td>
<td>④ の手順でインラインポリシーを追加する</td>
</tr>
<tr>
<td><code>ResourceNotFoundException</code> (Log Group)</td>
<td>環境変数のロググループ名が誤っている</td>
<td><code>LOG_GROUP_NAME</code> 環境変数と ① で作成したグループ名を確認する</td>
</tr>
<tr>
<td>POST で 500 エラー</td>
<td>Lambda 実行エラー</td>
<td>Lambda → 「モニタリング」→「CloudWatch Logs を表示」でエラー詳細を確認する</td>
</tr>
<tr>
<td>GET で <code>{&quot;log_count&quot;: 0}</code></td>
<td>ログがない or session_id が異なる</td>
<td>POST で使った session_id と一致しているか確認する</td>
</tr>
<tr>
<td>Metric Filter が反映されない</td>
<td>数分かかる</td>
<td>5〜10 分後に CloudWatch メトリクスを再確認する</td>
</tr>
<tr>
<td><code>InvalidParameterException</code> (put_log_events)</td>
<td>タイムスタンプが古すぎる or 未来すぎる</td>
<td><code>int(time.time() * 1000)</code> でミリ秒タイムスタンプを生成しているか確認する</td>
</tr>
<tr>
<td>API Gateway から 403 が返る</td>
<td>ステージのデプロイ忘れ</td>
<td>API Gateway → 「API をデプロイ」を再実行する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc37">まとめ</span></h2>
<p>今回のハンズオンで体験できたこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway + Lambda 連携</strong></td>
<td>Lambda プロキシ統合で REST API の POST / GET を1関数で処理</td>
</tr>
<tr>
<td><strong>カスタムロググループへの書き込み</strong></td>
<td><code>put_log_events()</code> でセッション単位のログストリームに記録</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td><code>{ $.level = &quot;error&quot; }</code> でエラーログを自動的にメトリクスへ変換</td>
</tr>
<tr>
<td><strong>CloudWatch Logs Insights</strong></td>
<td>クエリでエラーログだけを抽出・確認</td>
</tr>
</tbody>
</table>
<h3><span id="toc38">コンソール版で実感できたポイント</span></h3>
<ul>
<li>API Gateway のリソース・メソッド・デプロイという3段階の設定が視覚的に理解できる</li>
<li>IAM ポリシーのリソース ARN を手動で記述することで、権限スコープの意味が身につく</li>
<li>Metric Filter の仕組みを GUI で設定することで、JSON フィルター構文の動作が理解できる</li>
</ul>
<hr>
<h2><span id="toc39">コンソール版と SAM 版を比較してみる</span></h2>
<p>コンソールで API Gateway + Lambda + CloudWatch Logs の連携を理解したら、SAM で同じ構成をコードで定義することで「SAM が何を自動化しているか」が明確になります。IAM ポリシーの ARN 解決や Log Group の保持期間設定など、コンソールでの手動操作がコードに対応しています。</p>
<p><!-- TODO: SAM版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc40">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-console-chatbot-log-api/">AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-console-chatbot-log-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
