> ## 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 演示

> 使用 agent 为 Node.js 应用添加插桩，并将日志、链路追踪、指标和会话回放发送至 ClickStack

export const AgentPrompt = ({prompt, title = "Agent 辅助设置", description, outline, outlineLabel = "Agent 将执行的操作", 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 是一个 Node.js 应用，用于查询托管在公网 ClickHouse 演示环境中的 HackerNews 数据集。每个图表、表和搜索框都会执行真实的 ClickHouse 查询，因此每次交互都会生成一个 trace，其主 span 是后端向 ClickHouse 发起的 HTTPS 调用。

这与[会话回放演示](/zh/clickstack/example-datasets/session-replay)不同，后者为本地 Docker ClickStack 上仅在浏览器中运行的应用添加插桩。这里，您可以从同一应用获得后端自动插桩、ClickHouse 查询 span 和会话回放。

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

* Node 18+ 和 npm
* ClickStack OTLP/HTTP 端点和摄取令牌：
  * \*\*ClickHouse Cloud：\*\*打开服务，然后依次选择 **ClickStack** → **配置 OpenTelemetry exporter** → **环境变量**。协议为 `http/protobuf`。请求头为 `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 exporter 变量，而非在源代码中硬编码。打开 `.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` 是授权请求头，格式为 `authorization=<token>`，不带 `Bearer` 前缀。

    如果 collector 未强制实施身份验证，请将标记留空 (`OTEL_EXPORTER_OTLP_HEADERS=authorization=`) 。该变量仍必须存在；如果未设置或完全为空，SDK 将跳过初始化。

    Browser SDK 会复用这些值。`vite.config.ts` 会在构建时将端点和标记写入公开 bundle，因此请使用一次性的摄取标记，而不要使用生产环境标记。
  </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。这些操作均不会改变应用程序的业务逻辑。

        <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`。注释掉普通的 `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__` 是编译时常量，`vite.config.ts` 会使用后端所用的相同 `OTEL_EXPORTER_OTLP_*` 值将其注入。

        <Warning>
          摄取令牌会被嵌入公开的浏览器包中，任何查看 Network 选项卡的人都可以读取它。请使用一次性令牌。
        </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"。重新加载浏览器选项卡，让 Vite 提供更新后的 bundle，然后切换年份并点击文章以生成流量。

    打开 ClickStack UI：

    1. 前往 **搜索**，筛选最近 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="ClickStack 搜索显示最近五分钟内的 hn-analyzer-api 日志" width="3018" height="1578" data-path="images/clickstack/getting-started/instrument_app_clickstack_logs.webp" />
    </Frame>

    2. 点击某个 request，然后沿 trace 向上查看。你将看到 Express handler span、一个指向 `sql-clickhouse.clickhouse.com` 且包含实际网络耗时的子 HTTP span，以及同一 trace 上关联的 `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="ClickStack trace，包含 Express handler span 和指向 ClickHouse 的子 HTTP span" width="2398" height="1590" data-path="images/clickstack/getting-started/instrument_app_clickstack_traces.webp" />
    </Frame>

    3. 打开 **Session Replay**，播放与 trace 时间线同步、可拖动浏览的浏览器 session 视频。

    <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="与 trace 时间线同步的 ClickStack session replay" width="2408" height="1580" data-path="images/clickstack/getting-started/instrument_app_clickstack_sessions.webp" />
    </Frame>

    日志、指标、链路追踪和 session replay 都会进入同一 UI，使用相同的 query language，并自动关联。
  </Step>
</Steps>

<h2 id="learn-more">
  了解更多
</h2>

* [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer)：本指南中进行插桩的演示仓库。
* [会话回放](/zh/clickstack/features/session-replay)：功能概览、SDK 选项和隐私控制。
* [会话回放演示](/zh/clickstack/example-datasets/session-replay)：一个使用本地 ClickStack 实例的自包含演示。
* [ClickStack 入门](/zh/clickstack/getting-started/index)：部署 ClickStack 并摄取你的第一批数据。
* [所有示例数据集](/zh/clickstack/example-datasets/index)：其他示例数据集和相关指南。
