> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# HackerNews Analyzer デモ

> Node.js アプリをエージェントでインストルメントし、ログ、トレース、メトリクス、セッションリプレイを ClickStack に送信する

export const AgentPrompt = ({prompt, title = "エージェント支援セットアップ", description, outline, outlineLabel = "エージェントが行うこと", repositoryUrl, repositoryLabel = "ClickHouse/agent-skills"}) => {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    const copyWithTextArea = () => {
      const textArea = document.createElement("textarea");
      textArea.value = prompt;
      textArea.style.position = "fixed";
      textArea.style.opacity = "0";
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
    };
    try {
      if (navigator?.clipboard?.writeText) {
        try {
          await navigator.clipboard.writeText(prompt);
        } catch {
          copyWithTextArea();
        }
      } else {
        copyWithTextArea();
      }
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="ch-agent-prompt-wrapper" data-mdast="ignore">
      <div className="ch-agent-prompt-main-row">
        <div className="ch-agent-prompt-left">
          <span className="ch-agent-prompt-title">{title}</span>
        </div>
        <div className="ch-agent-prompt-prompt-area" style={{
    overflow: "hidden"
  }}>
          <code className="ch-agent-prompt-prompt-text" style={{
    overflowX: "auto"
  }}>
            {prompt}
          </code>
        </div>
        <button type="button" className="ch-agent-prompt-copy-button" style={{
    boxSizing: "border-box",
    justifyContent: "center",
    minWidth: "8.25rem",
    whiteSpace: "nowrap"
  }} onClick={handleCopy} aria-label={copied ? "コピーしました" : "プロンプトをコピー"}>
          {copied ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <polyline points="20 6 9 17 4 12" />
            </svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
            </svg>}
          <span style={{
    display: "grid",
    justifyItems: "center"
  }}>
            <span style={{
    gridArea: "1 / 1",
    visibility: copied ? "hidden" : "visible"
  }}>プロンプトをコピー</span>
            <span style={{
    gridArea: "1 / 1",
    visibility: copied ? "visible" : "hidden"
  }}>コピーしました</span>
          </span>
        </button>
      </div>
      {(description || repositoryUrl) && <div className="ch-agent-prompt-sub-row">
          {description && <span className="ch-agent-prompt-description">{description}</span>}
          {repositoryUrl && <a className="ch-agent-prompt-repository-link" href={repositoryUrl} target="_blank" rel="noopener noreferrer">
              {repositoryLabel}
            </a>}
        </div>}
      {outline?.length > 0 && <details className="ch-agent-prompt-outline">
          <summary className="ch-agent-prompt-outline-summary">
            <svg width="12" height="12" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="ch-agent-prompt-outline-chevron" aria-hidden="true">
              <path d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd" />
            </svg>
            <span>{outlineLabel}</span>
          </summary>
          <ol className="ch-agent-prompt-outline-list">
            {outline.map((item, index) => <li key={index}>{item}</li>)}
          </ol>
        </details>}
    </div>;
};

<Note>
  **要約**

  [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer)をクローンし、`.env` にOTLPエンドポイントとトークンを設定してから、agentのプロンプトを貼り付けます。バックエンドにOpenTelemetryのインポートは必要ありません。agentがプロセスの起動時に `@hyperdx/node-opentelemetry` を組み込みます。

  所要時間：約10分
</Note>

HackerNews Analyzerは、公開されているClickHouseデモでホストされるHackerNewsデータセットをクエリするNode.jsアプリです。すべてのチャート、テーブル、検索ボックスは実際のClickHouseクエリであるため、操作のたびにトレースが生成されます。そのメインspanは、バックエンドからClickHouseへのHTTPS呼び出しです。

これは、ローカルのDocker ClickStackに対してブラウザ専用アプリをインストルメントする[セッションリプレイ demo](/ja/clickstack/example-datasets/session-replay)とは異なるものです。ここでは、同じアプリでバックエンドのauto-instrumentation、ClickHouseクエリspan、セッションリプレイを利用できます。

<h2 id="prerequisites">
  前提条件
</h2>

* Node 18 以降と npm
* ClickStack の OTLP/HTTP endpoint とインジェストトークン:
  * **ClickHouse Cloud:** service を開き、**ClickStack** → **OpenTelemetry exporter を設定** → **環境変数**の順に選択します。プロトコルは `http/protobuf` です。headers には `authorization=<ingestion token>` を指定し、`Bearer` プレフィックスは付けません。
  * **ローカル collector:** `http://localhost:4318` を使用します。collector が保護されていない場合は、`authorization=` を空のままにします。

<h2 id="clone-the-repository">
  リポジトリをクローンする
</h2>

[HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer)をクローンし、依存関係をインストールして、環境変数テンプレートをコピーします。

```bash theme={null}
git clone https://github.com/ClickHouse/hn-news-analyzer.git
cd hn-news-analyzer
npm install
cp .env.example .env
```

次の手順で `.env` を設定した後、このディレクトリからインストルメントします。

<h2 id="instrument-the-application">
  アプリケーションをインストルメントする
</h2>

<Steps>
  <Step title="アプリケーションを実行する" id="run-the-application">
    クローンした `hn-news-analyzer` ディレクトリで、アプリを起動します。ClickHouse データソースはデフォルトでパブリックな読み取り専用デモクラスターを使用するため、追加の設定なしで実行できます。

    ```bash theme={null}
    ./run.sh
    ```

    [http://localhost:5001](http://localhost:5001) を開きます。年セレクタ、サマリー統計、アクティビティチャート、上位ユーザーとドメインのテーブル、検索ボックスが表示されます。年を切り替えたり、ストーリーの詳細を確認したりしてみましょう。

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/hackernews_main.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=8893dcb341dbb8cffdf3d78821ce949c" alt="ローカルで実行中の HackerNews Analyzer アプリケーション" width="2872" height="1474" data-path="images/clickstack/getting-started/hackernews_main.webp" />
    </Frame>

    この時点ではアプリケーションは実行されていますが、インストルメントされていません。ClickStack にはまだデータが表示されません。テレメトリーの受信を待機しています。
  </Step>

  <Step title="環境を構成する" id="configure-environment">
    SDK は標準の OpenTelemetry エクスポーター変数を読み取ります。これらはソースコードにハードコードされていません。`.env` を開き、次のように設定します。

    ```bash theme={null}
    OTEL_SERVICE_NAME=hn-analyzer-api
    OTEL_EXPORTER_OTLP_ENDPOINT=<your-otlp-http-endpoint>
    OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
    OTEL_EXPORTER_OTLP_HEADERS=authorization=<your-ingestion-token>
    OTEL_TRACES_EXPORTER=otlp
    OTEL_METRICS_EXPORTER=otlp
    OTEL_LOGS_EXPORTER=otlp
    ```

    `OTEL_EXPORTER_OTLP_ENDPOINT` は OTLP/HTTP エンドポイント (ポート `4318`) です。`OTEL_EXPORTER_OTLP_HEADERS` は認証 header で、`Bearer` プレフィックスなしの `authorization=<token>` 形式です。

    collector で認証を強制していない場合は、token を空のままにします (`OTEL_EXPORTER_OTLP_HEADERS=authorization=`) 。変数自体は設定しておく必要があります。未設定または完全に空の場合、SDK は初期化をスキップします。

    Browser SDK ではこれらと同じ値を再利用します。`vite.config.ts` はビルド時にエンドポイントと token を Public bundle に埋め込むため、production 用ではなく、使い捨てのインジェスト token を使用してください。
  </Step>

  <Step title="アプリケーションをインストルメントする" id="instrument">
    いずれかの方法を選んでください。どの方法でも、最終的には同じインストルメント済みアプリケーションになります。

    <Tabs>
      <Tab title="エージェントによるインストルメンテーション" id="instrument-with-an-agent">
        リポジトリをクローンし、`.env`を設定したら、アプリケーションをインストルメントするため、**そのディレクトリで**このプロンプトをコーディングエージェントに貼り付けてください。

        <AgentPrompt
          prompt="curl を使用して github.com/ClickHouse/hn-news-analyzer/blob/main/agent.md をダウンロードし、内容を読んで指示に従ってください"
          description="hn-news-analyzer をクローンして .env を設定した後、そのディレクトリでこのプロンプトを実行してください。Claude Code、Cursor、Codex、その他のコーディングエージェントで使用できます。"
          outline={[
"クローンした hn-news-analyzer ディレクトリにいること、および .env に OTEL_EXPORTER_OTLP_* の値がすでに設定されていることを確認します。どちらかが不足している場合は停止します。",
"@hyperdx/node-opentelemetry をインストールし、run.sh を opentelemetry-instrument に切り替えます。",
"@hyperdx/browser をインストールし、HyperDX.init と HyperDX.addAction を有効にします。",
"アプリを起動し、OTLP ヘルスチェックが成功することを確認して、http://localhost:5001 を操作するよう案内します。",
]}
        />
      </Tab>

      <Tab title="手動インストルメンテーション" id="instrument-manually">
        インストルメンテーションは、SDK のインストール、起動コマンドの切り替え、ブラウザ SDK の有効化の3つで構成されます。いずれもアプリケーションのビジネスロジックを変更しません。

        <h3 id="install-node-sdk">
          Node SDK をインストールする
        </h3>

        ```bash theme={null}
        npm install @hyperdx/node-opentelemetry
        ```

        <h3 id="enable-run-sh-wrapper">
          run.sh でラッパーを有効にする
        </h3>

        `run.sh`の末尾には`exec`行が2つあります。通常の`node`行をコメントアウトし、インストルメント済みの行をコメント解除します。

        ```diff theme={null}
         # 変更前: 通常の node、インストルメンテーションなし:
        -exec node scripts/entrypoint.js
        +# exec node scripts/entrypoint.js

         # 変更後: 同じソースを opentelemetry-instrument でラップ:
        -# exec npx opentelemetry-instrument scripts/entrypoint.js
        +exec npx opentelemetry-instrument scripts/entrypoint.js
        ```

        引き続き`scripts/entrypoint.js`を介して起動してください。このシムは`require('console')`を呼び出すため、コンソールキャプチャによって`console.log`がラップされます。`opentelemetry-instrument`で`dist/server/index.js`を直接指定するとトレースは送信されますが、ログは通知なく失われます。

        <h3 id="enable-browser-sdk">
          ブラウザ SDK を有効にする
        </h3>

        ```bash theme={null}
        npm install @hyperdx/browser
        ```

        `src/web/telemetry.ts`で、インポート、`HyperDX.init({...})`ブロック、および`recordAction()`内の`HyperDX.addAction`をコメント解除します。

        ```diff theme={null}
        -// import HyperDX from '@hyperdx/browser';
        +import HyperDX from '@hyperdx/browser';

         export function initTelemetry(): void {
        -  // HyperDX.init({
        -  //   url: __OTLP_ENDPOINT__,
        -  //   apiKey: __OTLP_AUTH_TOKEN__,
        -  //   service: 'hn-analyzer-web',
        -  //   tracePropagationTargets: [/localhost:5001/i, /\/api\//i],
        -  //   consoleCapture: true,
        -  //   advancedNetworkCapture: true,
        -  // });
        +  HyperDX.init({
        +    url: __OTLP_ENDPOINT__,
        +    apiKey: __OTLP_AUTH_TOKEN__,
        +    service: 'hn-analyzer-web',
        +    tracePropagationTargets: [/localhost:5001/i, /\/api\//i],
        +    consoleCapture: true,
        +    advancedNetworkCapture: true,
        +  });
         }
        ```

        `__OTLP_ENDPOINT__`と`__OTLP_AUTH_TOKEN__`は、バックエンドで使用するものと同じ`OTEL_EXPORTER_OTLP_*`値から、`vite.config.ts`によって注入されるコンパイル時定数です。

        <Warning>
          インジェストトークンは公開ブラウザバンドルに埋め込まれるため、ネットワークタブを確認すれば誰でも読み取れます。使い捨てのトークンを使用してください。
        </Warning>
      </Tab>

      <Tab title="事前インストルメント済みブランチを使用する" id="use-the-instrumented-branch">
        インストルメンテーションを省略し、すでにインストルメント済みのアプリケーションから始めるには、[`instrumented`ブランチ](https://github.com/ClickHouse/hn-news-analyzer/tree/instrumented)をチェックアウトします。

        ```bash theme={null}
        git checkout instrumented
        npm install
        ```

        SDK を削除する場合を除き、このブランチで`./reset.sh`を実行しないでください。
      </Tab>
    </Tabs>
  </Step>

  <Step title="トラフィックを生成してテレメトリーを表示する" id="generate-traffic-and-view-telemetry">
    新しい起動コマンドとビルドし直したブラウザーバンドルを反映するには、アプリケーションを再起動します。

    ```bash theme={null}
    # Ctrl-C the previous run, then:
    ./run.sh
    ```

    起動バナーに、`/v1/traces`、`/v1/metrics`、`/v1/logs` について「Health check passed」が3行表示されることを確認します。ブラウザのタブを再読み込みしてViteが更新済みのバンドルを配信するようにし、年を切り替えてストーリーをクリックし、トラフィックを生成します。

    ClickStack UIを開きます。

    1. **Search** に移動し、過去5分間に絞り込みます。`hn-analyzer-api` のログがストリーミング表示されます。

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_logs.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=e93104dd5b9ee8d297a451510c1273b3" alt="過去5分間のhn-analyzer-apiログを表示するClickStack検索画面" width="3018" height="1578" data-path="images/clickstack/getting-started/instrument_app_clickstack_logs.webp" />
    </Frame>

    2. リクエストをクリックして、トレースを上流にたどります。Expressハンドラーのスパン、実際のネットワーク所要時間を示す `sql-clickhouse.clickhouse.com` 向けの子HTTPスパン、および同じトレース上で相関付けられた `console.log` レコードが表示されます。

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_traces.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=a421c9aebd0d2e5966c5193f70c89667" alt="ExpressハンドラーのスパンとClickHouseへの子HTTPスパンを含むClickStackトレース" width="2398" height="1590" data-path="images/clickstack/getting-started/instrument_app_clickstack_traces.webp" />
    </Frame>

    3. **Session Replay** を開くと、トレースタイムラインと同期したブラウザセッションの動画をシークしながら再生できます。

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_sessions.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=6d9af2c26c59ee42f2df33f40fd40f62" alt="トレースタイムラインと同期したClickStackセッションリプレイ" width="2408" height="1580" data-path="images/clickstack/getting-started/instrument_app_clickstack_sessions.webp" />
    </Frame>

    ログ、メトリクス、トレース、セッションリプレイはすべて同じUIに集約され、同じクエリ言語を使用し、自動的に相関付けられます。
  </Step>
</Steps>

<h2 id="learn-more">
  詳しくはこちら
</h2>

* [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer): このガイドでインストルメントするデモリポジトリ。
* [セッションリプレイ](/ja/clickstack/features/session-replay): 機能の概要、SDK オプション、プライバシー制御。
* [セッションリプレイ Demo](/ja/clickstack/example-datasets/session-replay): ローカルの ClickStack インスタンスで試せる自己完結型デモ。
* [ClickStack 入門](/ja/clickstack/getting-started/index): ClickStack をデプロイし、最初のデータを取り込むためのガイド。
* [サンプルデータセット一覧](/ja/clickstack/example-datasets/index): そのほかのサンプルデータセットとガイド。
