> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryprofound.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloudflare 連携(Worker)

> HTTP リクエストデータを取得して Profound Agent Analytics に転送する Cloudflare Worker を設定・デプロイします。

## 概要

この連携では、ミドルウェアとして動作する Cloudflare Worker を使用してリクエストのメタデータを収集し、Agent Analytics API に送信します。Worker は、実際のリクエスト処理に影響を与えることなく、IP アドレス、ユーザーエージェント、リファラーなどの重要なリクエスト情報を取得します。

## 前提条件

* Workers にアクセスできる Cloudflare アカウント

* 開発マシンにインストールされた Node.js

* ドメインの Cloudflare 設定へのアクセス権

* Agent Analytics 用の Profound Log Ingestion Token

<Tip>
  Cloudflare の Enterprise プランをご利用の場合は、<a href="https://docs.tryprofound.com/agent-analytics/cloudflare_logpush" target="_self">Cloudflare Logpush を使用した連携ガイド</a>をご覧ください。Logpush はアウトオブバンドで動作し、本番トラフィックに影響を与えることがないため、常に Worker よりも推奨されます。
</Tip>

<Warning>
  `*` ルート上の Worker は、そのホスト名への**すべての**リクエストのリクエストパス上に配置されるため、本番トラフィックに影響を与える可能性があります。以下のコードはそのまま使用し、特に次の点に注意してください。

  * Worker 内でレスポンスをクローンしたり、レスポンスボディを読み取ったりしないでください。ボディをバッファリングすると Worker の 128 MB の isolate メモリ制限を超える可能性があり、Cloudflare はそれを訪問者(またはクローラー)に [Error 1102 `Worker exceeded resource limits`](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1102/) として返します。

  * ログ送信の呼び出しは `ctx.waitUntil()` 内に置き、そのエラーは握りつぶしてください。これにより、遅延や失敗したログリクエストが 5xx になることを防げます。

  * 機密性の高いパスは Worker から完全に除外してください。以下のコードにあるパスリストはロギングをスキップするだけで、リクエストは引き続き Worker を通過します。チェックアウト、カート、管理画面のパスを Worker のパスから完全に外すには、ルートパターン自体を絞り込む(または Worker にバインドされていない、より優先度の高いルートを追加する)必要があります。

  これらのルールに違反した Worker の症状としては、サイズの大きいレスポンスやストリーミングレスポンスで断続的に 5xx が発生することや、クロール中に `AdsBot-Google` がこれらのエラーに遭遇した場合に Google Merchant Center / Google Ads で不承認(「リンク先が機能していません」「画像にアクセスできません」)になるといった二次的な影響が挙げられます。
</Warning>

<Note>
  Shopify ストアフロントを運用している場合は、その前段に Worker を配置しないでください。代わりに、ログをアウトオブバンドで収集する <a href="https://docs.tryprofound.com/agent-analytics/shopify" target="_self">Shopify ガイド</a>のログドレインオプションのいずれかを使用してください。
</Note>

<img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/worker_setup.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=03401e0694f16e2b2076d70eb045e534" alt="Logpush Detected" width="3680" height="2390" data-path="images/agent-analytics/cloudflare_worker/worker_setup.png" />

## 実装ガイド

<Steps>
  <Step title="開発環境をセットアップする">
    新しい Worker プロジェクトを作成し、依存関係をインストールします。

    ### 新しい Worker プロジェクトを作成する

    まず、Cloudflare Worker CLI を使用して新しい Worker プロジェクトを作成します。

    この例では、Cloudflare Worker CLI のバージョン `2.37.4` を使用しています。最新バージョンを使用しても構いませんが、手順に若干の違いがある場合があります。

    ```bash theme={null}
    npm create cloudflare@2.37.4 -- log-collector
    ```

    npm が起動すると、開始点を選択するよう求められます。開始カテゴリとして "Hello World" を選択します。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=49f53028fff87639f1bbe844d9591ade" alt="Cloudflare Worker Step 1 - Choose Starting Point" width="867" height="307" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-1.png" />

    "Hello World" Worker テンプレートを選択します。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-2.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=f225e20e3063a3c6dfd647c8d8835e09" alt="Cloudflare Worker Step 1 - Select Worker Template" width="526" height="232" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-2.png" />

    言語として "TypeScript" を選択します。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-3.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=96537f92fa73752faa35b98a68351484" alt="Cloudflare Worker Step 1 - Select Language" width="491" height="260" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step1-3.png" />

    CLI が `log-collector` という名前の新しいプロジェクトを作成し、必要な依存関係をインストールします。

    バージョン管理として Git を選択します。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step2-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=7d671bd7a44948a1f6ec8264a89c0a39" alt="Cloudflare Worker Step 2 - Select Version Control" width="604" height="255" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step2-1.png" />

    ここでアプリケーションをデプロイします。アプリケーションは Cloudflare の開発用ドメインにデプロイされるため、本番環境には影響しません。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/create-cloudflare-application-step3-1.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=96bd057ccbecc1d746170780e362b8b7" alt="Cloudflare Worker Step 3 - Deploy Application" width="347" height="73" data-path="images/agent-analytics/cloudflare_worker/create-cloudflare-application-step3-1.png" />

    Cloudflare CLI からログインと、アプリケーションをデプロイするアカウントの選択を求められます。ドメインが関連付けられているアカウントを選択してください。

    Cloudflare によって自動的にブラウザウィンドウが開き、"Hello World" が表示されます。アプリケーションのデプロイが完了したら、プロジェクトディレクトリに移動できます。

    ```bash theme={null}
    cd log-collector
    ```
  </Step>

  <Step title="Worker を設定する">
    `wrangler.json` ファイルを編集して、環境変数 `PROFOUND_API_URL` とルートバインディングを設定します。

    <Warning>
      パターン `example.com/*` を実際のドメインに置き換えてください。対象サイトの URL(通常はマーケティングサイト)を使用します。たとえば、マーケティングサイトが `https://www.example.com` の場合は、パターンとして `www.example.com/*` を使用します。
      `zone_name` には、www を除いた正規ドメインを指定してください。
    </Warning>

    <Tip>
      正しい設定がわからない場合は、[Profound サポート](mailto:support@tryprofound.com)にお問い合わせください。
    </Tip>

    ```json wrangler.json theme={null}
    {
      "$schema": "node_modules/wrangler/config-schema.json",
      "name": "log-collector",
      "main": "src/index.ts",
      "compatibility_date": "2025-01-29",
      "observability": {
        "enabled": true
      },
      "route": {
        "pattern": "example.com/*",
        "zone_name": "example.com"
      },
      "vars": { "PROFOUND_API_URL": "https://artemis.api.tryprofound.com/v1/logs/cloudflare_worker" }
    }
    ```

    次に、TypeScript コードを `src/index.ts` にコピーします。

    ```typescript src/index.ts theme={null}
    /**
     * Cloudflare Worker for Log Collection
     *
     * Forwards request metadata to Profound's log collection API. The origin
     * response body is never read or modified, and every error in the logging
     * path is swallowed, so logging cannot alter the response. Failures are
     * logged with enough detail to tell a rejected request apart from a
     * timeout or a connection error.
     */

    export interface Env {
        PROFOUND_API_URL: string;
        PROFOUND_LOG_INGESTION_TOKEN: string;
    }

    // Paths that are not logged. Matched on full path segments, so '/cart' does
    // not match '/cartography'.
    const EXCLUDED_PATHS = ['/checkout', '/cart', '/admin', '/api'];

    // Characters kept from a rejected response body when logging the failure.
    const DETAIL_LIMIT = 512;

    // Percent-decoded and lowercased so encoded variants such as '/%63heckout'
    // and '/checkout%2Fpayment' match the same way the origin routes them.
    function normalizePath(pathname: string): string {
        try {
            return decodeURIComponent(pathname).toLowerCase();
        } catch {
            return pathname.toLowerCase();
        }
    }

    function isExcluded(pathname: string): boolean {
        const path = normalizePath(pathname);
        return EXCLUDED_PATHS.some(
            (excluded) => path === excluded || path.startsWith(`${excluded}/`),
        );
    }

    export default {
        async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
            const response = await fetch(request);

            const skip = response.status === 101 || isExcluded(new URL(request.url).pathname);

            if (!skip) {
                ctx.waitUntil(
                    sendLog(request, response, env).catch((error: unknown) =>
                        console.error(
                            'Failed to send logs:',
                            error instanceof Error ? `${error.name}: ${error.message}` : error,
                        ),
                    ),
                );
            }

            return response;
        },
    } satisfies ExportedHandler<Env>;

    async function sendLog(request: Request, response: Response, env: Env) {
        const requestUrl = new URL(request.url);

        // +2 for ': ' and +2 for '\r\n' per header
        const headerSize = Array.from(response.headers.entries()).reduce(
            (total, [key, value]) => total + key.length + value.length + 4,
            0,
        );

        // Derived from content-length so the body is never buffered. Streamed or
        // chunked responses have no content-length and are reported as headers only.
        // HEAD and bodyless statuses advertise a content-length that is never sent,
        // so their body size is counted as zero.
        const contentLength = Number(response.headers.get('content-length'));
        const transmitsBody =
            request.method !== 'HEAD' && response.status !== 204 && response.status !== 304;
        const bodySize = transmitsBody && Number.isFinite(contentLength) ? contentLength : 0;
        const bytes = headerSize + bodySize;

        const logData = {
            timestamp: Date.now(),
            host: requestUrl.hostname,
            method: request.method,
            pathname: requestUrl.pathname,
            query_params: Object.fromEntries(requestUrl.searchParams),
            ip: request.headers.get('cf-connecting-ip'),
            userAgent: request.headers.get('user-agent'),
            referer: request.headers.get('referer'),
            bytes,
            status: response.status,
        };

        const logResponse = await fetch(env.PROFOUND_API_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-API-Key': env.PROFOUND_LOG_INGESTION_TOKEN,
            },
            body: JSON.stringify([logData]),
            signal: AbortSignal.timeout(5000),
        });

        // Only the first chunk of a rejected response is read, and only up to
        // DETAIL_LIMIT characters of it are kept, so a misconfigured URL that
        // returns a large body can never fill the isolate. The origin response
        // is still never read.
        if (!logResponse.ok) {
            const reader = logResponse.body?.getReader();
            const chunk = await reader?.read();
            await reader?.cancel();

            const detail = chunk?.value
                ? new TextDecoder().decode(chunk.value).slice(0, DETAIL_LIMIT)
                : '';

            console.error(`Log ingestion rejected the request: ${logResponse.status} ${detail}`);
            return;
        }

        await logResponse.body?.cancel();
    }
    ```

    <Note>
      Worker は 2 種類の失敗をログに記録します。`Log ingestion rejected the request: <status> <body>` は、Profound がリクエストを受信したものの拒否したことを意味します。これは通常、認証またはペイロードの問題であり、ステータスとボディからどちらかを特定できます。`Failed to send logs: <name>: <message>` は、リクエストが完了しなかったことを意味します。`TimeoutError` は 5 秒のタイムアウトです。それ以外の名前は、接続または設定の問題を示しています。どちらのメッセージも Worker のログに出力され、Workers & Pages > 対象の Worker > Logs で確認できます。
    </Note>

    <Note>
      `bytes` は、レスポンスボディではなく `content-length` レスポンスヘッダーから算出されます。ボディを読み取る(たとえば `response.clone()` と `blob()` を使用する)と、Cloudflare はペイロード全体を Worker の isolate 内にバッファリングする必要があり、サイズの大きいレスポンスやストリーミングレスポンスでは 5xx で失敗します。この Worker はレスポンスボディを読み取ることも変更することもありません。オリジンのレスポンスをそのままストリーミングしますが、リクエストパス上には存在するため、オリジンの fetch の失敗は Worker がない場合と同様に表面化します。そのため、`bytes` はヘッダーと `content-length` の合計であり、レスポンスが実際にボディを送信する場合(`HEAD`、`204`、`304` 以外)にのみカウントされます。
    </Note>
  </Step>

  <Step title="Worker をデプロイする">
    ### Cloudflare にログインする

    Wrangler CLI を使用して Worker をデプロイします。

    ```bash theme={null}
    # Login to Cloudflare
    npx wrangler login
    ```

    ### Profound Log Ingestion Token を設定する

    Secrets は Cloudflare Workers の機能で、Log Ingestion Token などの機密情報を安全な環境に保存できます。

    ```bash theme={null}
    # Configure the Log Ingestion Token secret
    npx wrangler secret put PROFOUND_LOG_INGESTION_TOKEN
    ```

    Log Ingestion Token の入力を求められます。トークンをコピーして貼り付け、Enter キーを押します。

    <img src="https://mintcdn.com/profound-37face47/dU1U8PbY91PjiXyk/images/agent-analytics/cloudflare_worker/configure-api-key.png?fit=max&auto=format&n=dU1U8PbY91PjiXyk&q=85&s=7ea6fb5162663140e753640037802093" alt="Configure Log Ingestion Token" width="195" height="62" data-path="images/agent-analytics/cloudflare_worker/configure-api-key.png" />

    ### Worker をデプロイする

    ```bash theme={null}
    # Deploy the Worker
    npx wrangler deploy
    ```
  </Step>

  <Step title="実装をテストする">
    Worker が正しく機能していることを確認します。

    Profound で **Agent Analytics** に移動し、**Logs** パネルにログが表示されることを確認します。AI ログフィルターはデフォルトで有効になっています。**Logs** パネル上部のフィルターを使用して無効にしてください。

    ログが表示されれば、設定は完了です。Analytics ダッシュボードにデータが表示されるはずです。
  </Step>
</Steps>

## トラブルシューティング

* ログが表示されない場合は、環境変数 `PROFOUND_API_URL` とシークレット `PROFOUND_LOG_INGESTION_TOKEN` が正しく設定されていることを確認してください

* Worker のログに `Log ingestion rejected the request: 401` または `403` が表示される場合: シークレット `PROFOUND_LOG_INGESTION_TOKEN` が存在しない、スペルが間違っている、またはこのサイト用に発行されたトークンではありません。`npx wrangler secret put PROFOUND_LOG_INGESTION_TOKEN` で再設定してください

* `Failed to send logs: TimeoutError`: ログリクエストが 5 秒のタイムアウトを超えました。タイムアウトによってログ送信の呼び出しが訪問者に影響しないようになっているため、少数の発生は想定内であり、影響を受けるのは該当するログ行のみです。継続的に発生する場合は、Worker のログにある `requestId` の値をいくつか添えて [Profound サポート](mailto:support@tryprofound.com)にご報告ください

* その他のエラー名で `Failed to send logs:` が表示される場合: `PROFOUND_API_URL` を上記の設定手順の値と照合してください。URL が自社の Cloudflare ゾーン上のホスト名に解決されると、Worker が自分自身を呼び出すことになり、すべてのリクエストで失敗します

* Cloudflare Workers > Analytics で実行エラーがないか確認してください

* ルートパターンがドメインの設定と一致していることを確認してください

* Cloudflare ダッシュボードのメトリクスを確認して、Worker がリクエストを受信していることを確認してください

* Worker のデプロイ後に断続的な 5xx が発生する場合: Metrics > Errors > Invocation Statuses で `Exceeded Memory`(Error 1102)または `Script threw exception`(Error 1101)を確認してください。どちらもオリジンではなく Worker 自体が失敗していることを意味します。上記のコード(`clone()` なし、ボディの読み取りなし)を実行していること、およびログ送信の呼び出しが `.catch()` 付きで `ctx.waitUntil()` にラップされていることを確認してください

* Worker のデプロイ後に Google Ads / Merchant Center で「リンク先が機能していません」や「画像にアクセスできません」などの不承認が発生する場合: これらは、`AdsBot-Google` または Merchant Center のフェッチャーが上記の Worker エラーのいずれかを受け取ったことが原因です。Worker のエラーを修正してから、再審査をリクエストしてください

* Worker が原因かどうかを完全に切り分けるには、ルートバインディングを削除し(`npx wrangler triggers delete` を実行するか、ダッシュボードでルートを削除)、エラーが止まることを確認してください

## その他のリソース

* [Cloudflare Workers のドキュメント](https://developers.cloudflare.com/workers/)

* [Wrangler CLI のドキュメント](https://developers.cloudflare.com/workers/wrangler/)

* API に関するご質問は [support@tryprofound.com](mailto:support@tryprofound.com) までお問い合わせください

## セキュリティに関する考慮事項

* 本番環境では、Log Ingestion Token をシークレットとして保存してください

* Log Ingestion Token は定期的にローテーションしてください

* Worker の使用状況とログに異常なパターンがないか監視してください
