> ## 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.

# Démo HackerNews Analyzer

> Instrumenter une application Node.js avec un agent et envoyer les logs, traces, métriques et données de session replay à ClickStack

export const AgentPrompt = ({prompt, title = "Configuration assistée par agent", description, outline, outlineLabel = "Ce que l'agent va faire", 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 ? "Copié" : "Copier le prompt"}>
          {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"
  }}>Copier le prompt</span>
            <span style={{
    gridArea: "1 / 1",
    visibility: copied ? "visible" : "hidden"
  }}>Copié</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>
  **TL;DR**

  Clonez [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer), renseignez `.env` avec votre endpoint OTLP et votre jeton, puis collez le prompt de l’agent. Le backend ne nécessite aucun import OpenTelemetry ; l’agent connecte `@hyperdx/node-opentelemetry` au démarrage du processus.

  Temps nécessaire : environ 10 minutes
</Note>

HackerNews Analyzer est une application Node.js qui interroge le dataset HackerNews hébergé dans la démo publique de ClickHouse. Chaque graphique, table et champ de recherche correspond à une véritable requête ClickHouse, de sorte que chaque interaction produit une trace dont le span principal est l’appel HTTPS du backend vers ClickHouse.

Cette tâche est différente de la [démo de session replay](/fr/clickstack/example-datasets/session-replay), qui instrumente une application uniquement côté navigateur avec un ClickStack Docker local. Ici, vous bénéficiez de l’auto-instrumentation du backend, des spans de requêtes ClickHouse et du session replay dans la même application.

<h2 id="prerequisites">
  Prérequis
</h2>

* Node 18+ et npm
* Un endpoint OTLP/HTTP ClickStack et un jeton d’ingestion :
  * **ClickHouse Cloud :** ouvrez le service, puis **ClickStack** → **Configure your OpenTelemetry exporter** → **Env vars**. Le protocole est `http/protobuf`. Le header est `authorization=<ingestion token>`, sans préfixe `Bearer`.
  * **Collector local :** utilisez `http://localhost:4318`. Si le collector n’est pas sécurisé, laissez `authorization=` vide.

<h2 id="clone-the-repository">
  Clonez le dépôt
</h2>

Clonez [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer), installez les dépendances et copiez le modèle de fichier d’environnement :

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

Vous renseignerez le fichier `.env` à l’étape suivante, puis instrumenterez l’application depuis ce répertoire.

<h2 id="instrument-the-application">
  Instrumenter l’application
</h2>

<Steps>
  <Step title="Exécutez l’application" id="run-the-application">
    Depuis le répertoire `hn-news-analyzer` cloné, démarrez l’application. La source de données ClickHouse utilise par défaut le cluster de démonstration public en lecture seule. Aucune configuration supplémentaire n’est donc nécessaire :

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

    Ouvrez [http://localhost:5001](http://localhost:5001). Vous verrez un sélecteur d’année, des statistiques récapitulatives, un graphique d’activité, des tableaux des principaux utilisateurs et domaines, ainsi qu’un champ de recherche. Explorez l’interface : changez d’année, consultez les articles en détail.

    <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="L’application HackerNews Analyzer exécutée localement" width="2872" height="1474" data-path="images/clickstack/getting-started/hackernews_main.webp" />
    </Frame>

    À ce stade, l’application s’exécute, mais n’est pas instrumentée. ClickStack n’affiche aucune donnée : il attend la télémétrie.
  </Step>

  <Step title="Configurer l’environnement" id="configure-environment">
    Les SDKs utilisent les variables standard de l’exporter OpenTelemetry. Elles ne sont pas codées en dur dans le code source. Ouvrez `.env` et définissez :

    ```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` est l’endpoint OTLP/HTTP (port `4318`). `OTEL_EXPORTER_OTLP_HEADERS` est l’en-tête d’autorisation, au format `authorization=<token>`, sans préfixe `Bearer`.

    Si le collector n’applique pas d’authentification, laissez le token vide (`OTEL_EXPORTER_OTLP_HEADERS=authorization=`). La variable doit néanmoins être présente : le SDK ignore l’initialisation si elle n’est pas définie ou si elle est entièrement vide.

    Le SDK pour navigateur réutilise ces mêmes valeurs. `vite.config.ts` intègre l’endpoint et le token au bundle public lors du build ; utilisez donc un token d’ingestion temporaire, et non un token de production.
  </Step>

  <Step title="Instrumenter l’application" id="instrument">
    Choisissez une méthode. Toutes trois aboutissent à la même application instrumentée.

    <Tabs>
      <Tab title="Instrumentation avec un agent" id="instrument-with-an-agent">
        Une fois le dépôt cloné et le fichier `.env` renseigné, collez ce prompt dans un agent de code **depuis ce répertoire** afin d’instrumenter l’application.

        <AgentPrompt
          prompt="Utilisez curl pour télécharger, lire et suivre les instructions de : github.com/ClickHouse/hn-news-analyzer/blob/main/agent.md"
          description="Après avoir cloné hn-news-analyzer et renseigné .env, exécutez ce prompt depuis ce répertoire. Fonctionne avec Claude Code, Cursor, Codex et d’autres agents de code."
          outline={[
"Vérifiez que vous vous trouvez dans le répertoire cloné hn-news-analyzer et que .env contient déjà les valeurs OTEL_EXPORTER_OTLP_*. Arrêtez-vous si l’un des deux manque.",
"Installez @hyperdx/node-opentelemetry et remplacez la commande de lancement dans run.sh par opentelemetry-instrument.",
"Installez @hyperdx/browser et activez HyperDX.init ainsi que HyperDX.addAction.",
"Démarrez l’application, vérifiez que les contrôles d’intégrité OTLP réussissent, puis naviguez sur http://localhost:5001.",
]}
        />
      </Tab>

      <Tab title="Instrumentation manuelle" id="instrument-manually">
        L’instrumentation comporte trois étapes : installer les SDKs, modifier la commande de lancement et activer le SDK navigateur. Aucune ne modifie la logique métier de l'application.

        <h3 id="install-node-sdk">
          Installer le SDK Node
        </h3>

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

        <h3 id="enable-run-sh-wrapper">
          Activer le wrapper dans run.sh
        </h3>

        La fin de `run.sh` contient deux lignes `exec`. Commentez la ligne `node` standard et décommentez celle instrumentée :

        ```diff theme={null}
         # AVANT : node standard, sans instrumentation :
        -exec node scripts/entrypoint.js
        +# exec node scripts/entrypoint.js

         # APRÈS : même source, encapsulée par opentelemetry-instrument :
        -# exec npx opentelemetry-instrument scripts/entrypoint.js
        +exec npx opentelemetry-instrument scripts/entrypoint.js
        ```

        Continuez à lancer l’application via `scripts/entrypoint.js`. Ce shim appelle `require('console')` afin que la capture de console encapsule `console.log`. Si vous pointez directement `opentelemetry-instrument` vers `dist/server/index.js`, les traces sont bien envoyées, mais les logs sont silencieusement ignorés.

        <h3 id="enable-browser-sdk">
          Activer le SDK navigateur
        </h3>

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

        Dans `src/web/telemetry.ts`, décommentez l’import, le bloc `HyperDX.init({...})` et `HyperDX.addAction` dans `recordAction()` :

        ```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__` et `__OTLP_AUTH_TOKEN__` sont des constantes de compilation injectées par `vite.config.ts` à partir des mêmes valeurs `OTEL_EXPORTER_OTLP_*` que celles utilisées par le backend.

        <Warning>
          Le jeton d’ingestion est intégré au bundle navigateur public et peut être lu par toute personne inspectant l’onglet Network. Utilisez un jeton jetable.
        </Warning>
      </Tab>

      <Tab title="Utiliser une branche pré-instrumentée" id="use-the-instrumented-branch">
        Pour éviter l’instrumentation et démarrer avec une application déjà instrumentée, effectuez un checkout de la [branche `instrumented`](https://github.com/ClickHouse/hn-news-analyzer/tree/instrumented).

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

        N’exécutez pas `./reset.sh` sur cette branche, sauf si vous souhaitez supprimer les SDKs.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Générez du trafic et consultez les données de télémétrie" id="generate-traffic-and-view-telemetry">
    Redémarrez l’application pour que la nouvelle commande de démarrage et le bundle du navigateur fraîchement compilé prennent effet :

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

    Vérifiez que la bannière de démarrage affiche trois lignes « Health check passed » pour `/v1/traces`, `/v1/metrics` et `/v1/logs`. Rechargez l’onglet du navigateur pour que Vite serve le bundle mis à jour, puis changez d’année et cliquez sur des articles afin de générer du trafic.

    Ouvrez l’UI ClickStack :

    1. Accédez à **Search** et filtrez sur les 5 dernières minutes. Les logs de `hn-analyzer-api` affluent.

    <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="Recherche ClickStack affichant les logs de hn-analyzer-api des cinq dernières minutes" width="3018" height="1578" data-path="images/clickstack/getting-started/instrument_app_clickstack_logs.webp" />
    </Frame>

    2. Cliquez sur une requête et remontez la trace. Vous verrez le span du handler Express, un span HTTP enfant pointant vers `sql-clickhouse.clickhouse.com` avec une durée réseau réelle, ainsi que des enregistrements `console.log` corrélés dans la même trace.

    <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="Trace ClickStack avec un span de handler Express et un span HTTP enfant vers ClickHouse" width="2398" height="1590" data-path="images/clickstack/getting-started/instrument_app_clickstack_traces.webp" />
    </Frame>

    3. Ouvrez **Session Replay** pour lire une vidéo d’une session de navigateur, dont vous pouvez parcourir la chronologie, synchronisée avec celle de la trace.

    <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="Replay de session ClickStack synchronisé avec la chronologie de la trace" width="2408" height="1580" data-path="images/clickstack/getting-started/instrument_app_clickstack_sessions.webp" />
    </Frame>

    Les logs, métriques, traces et replays de session sont regroupés dans la même UI, utilisent le même langage de requête et sont automatiquement corrélés.
  </Step>
</Steps>

<h2 id="learn-more">
  En savoir plus
</h2>

* [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer) : le dépôt de démonstration instrumenté par ce guide.

- [Session Replay](/fr/clickstack/features/session-replay) : vue d’ensemble de la fonctionnalité, options du SDK et paramètres de confidentialité.
- [Session Replay Demo](/fr/clickstack/example-datasets/session-replay) : une démo autonome avec une instance locale de ClickStack.
- [ClickStack Prise en main](/fr/clickstack/getting-started/index) : déployez ClickStack et ingérez vos premières données.
- [Tous les jeux de données d’exemple](/fr/clickstack/example-datasets/index) : d’autres jeux de données d’exemple et guides.
