Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions clickstack/managed-onboarding/instrument-application.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
slug: /use-cases/observability/clickstack/instrument-application
title: 'Instrument an application in 5 mins with Managed ClickStack'
description: 'Instrument a Node.js application with OpenTelemetry and send its logs, metrics, and traces into Managed ClickStack'
doc_type: 'guide'
keywords: ['clickstack', 'instrumentation', 'opentelemetry', 'managed', 'observability', 'sdk', 'nodejs']
---

import { Image } from "/snippets/components/Image.jsx";

This guide shows how to instrument a small Node.js application with OpenTelemetry and send its logs, metrics, and traces into Managed ClickStack. The backend is instrumented with no changes to the application source code.

The [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer) is a Node.js app that queries the HackerNews dataset hosted in the public ClickHouse demo. Every chart, table, and search box is backed by a real ClickHouse query, so every interaction produces a trace whose main span is the HTTPS call from the backend out to ClickHouse.

This guide assumes you've completed setting up your OpenTelemetry Collector and have a ClickStack collector running and reachable from the machine you run this application on. **Ensure you have recorded its OTLP endpoint** and the `OTLP_AUTH_TOKEN` you set when deploying it.

## Prerequisites {#prerequisites}

- A ClickStack collector reachable from this machine. If you haven't deployed one yet, see the [OpenTelemetry collector guide](/clickstack/ingesting-data/collector) first.
- The OTLP endpoint of that collector and the `OTLP_AUTH_TOKEN` you set on it.
- Node 18+ and npm.

<Steps>
<Step title="Clone and run the application">

Clone the repository, install dependencies, and create your `.env` file:

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

The ClickHouse data source defaults to the public read-only demo cluster, so the app runs without any further configuration. Start it:

```bash
./run.sh
```

Open [http://localhost:5001](http://localhost:5001). You will see a year selector, summary statistics, an activity chart, top users and domains tables, and a search box. Click around: switch years, drill into stories.

<Image img="/images/clickstack/getting-started/hackernews_main.png" alt="The HackerNews Analyzer application running locally"/>

At this point the application is running but uninstrumented. ClickStack shows no data: it is waiting for telemetry. This is the "before" state.

</Step>
<Step title="Get the connection details">

The application needs two values to reach the collector:

- `OTEL_EXPORTER_OTLP_ENDPOINT`: the OTLP endpoint your collector exposes (commonly port `4318` for OTLP over HTTP).
- `OTEL_EXPORTER_OTLP_HEADERS`: the authorization header carrying your ingestion token, in the form `authorization=<token>`.

Open `.env` and set them:

```bash
OTEL_SERVICE_NAME=hn-analyzer-api
OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-collector-endpoint>:4318
OTEL_EXPORTER_OTLP_HEADERS=authorization=<your-ingestion-token>
```

The SDK uses `OTEL_EXPORTER_OTLP_HEADERS` to set the authorization header for all three signals: traces, metrics, and logs. If your collector runs locally and doesn't enforce auth, you can leave the value empty (`OTEL_EXPORTER_OTLP_HEADERS=authorization=`), but the variable must be present; the SDK skips initialization entirely if it's unset or fully empty.

</Step>
<Step title="Instrument the application">

Instrumentation has three parts: install the SDKs, switch the launch command, and enable the browser SDK. None of it changes the application's business logic.

### Install the SDKs {#install-sdks}

Install both the backend and browser OpenTelemetry SDKs:

```bash
npm install @hyperdx/node-opentelemetry @hyperdx/browser
```

### Use the opentelemetry-instrument CLI {#use-open-telemetry-cli}

The application is launched by `run.sh`, which has two `exec` lines at the bottom: one active, one commented. Switch which one is active so Node is wrapped by `opentelemetry-instrument`:

```diff
# BEFORE: plain node, no instrumentation, collector stays silent:
-exec node scripts/entrypoint.js
+# exec node scripts/entrypoint.js

# AFTER: same source, wrapped by opentelemetry-instrument CLI.
-# exec npx opentelemetry-instrument scripts/entrypoint.js
+exec npx opentelemetry-instrument scripts/entrypoint.js
```

That is the entire backend change. The auto-instrumentation is loaded by `opentelemetry-instrument` at process start.

### Enable the browser SDK {#enable-browser-sdk}

To capture distributed traces (browser to backend) and session replays, enable the browser SDK in `src/web/telemetry.ts`. Uncomment the import and the `HyperDX.init({...})` block:

```javascript
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,
});
}
```

No extra `.env` edits are required. `__OTLP_ENDPOINT__` and `__OTLP_AUTH_TOKEN__` are compile-time constants injected by `vite.config.ts`: the endpoint is `OTEL_EXPORTER_OTLP_ENDPOINT` and the token is parsed out of `OTEL_EXPORTER_OTLP_HEADERS`, the same values the backend uses.

<Warning>
The ingestion token is baked into the public browser bundle and is readable by anyone inspecting the network tab.
</Warning>

</Step>
<Step title="Generate traffic and view telemetry">

Restart the application so the new launch command and freshly built browser bundle take effect:

```bash
# Ctrl-C the previous run, then:
./run.sh
```

Reload the browser tab so Vite serves the updated bundle, then refresh the app a few times, switch years, and click into stories to generate traffic.

Open the ClickStack UI:

1. Go to **Search** and filter to the last 5 minutes. Logs for `hn-analyzer-api` stream in.

<Image img="/images/clickstack/getting-started/instrument_app_clickstack_logs.png" alt="ClickStack Logs"/>

2. Click into a request and walk up the trace. You will see the Express handler span, a child HTTP span pointing at the ClickHouse cluster with real network duration, and correlated `console.log` records on the same trace.

<Image img="/images/clickstack/getting-started/instrument_app_clickstack_traces.png" alt="ClickStack Traces"/>

3. Open **Session Replay** to play back a scrubbable video of a browser session, synced to the trace timeline.

<Image img="/images/clickstack/getting-started/instrument_app_clickstack_sessions.png" alt="ClickStack Sessions"/>

Logs, metrics, traces, and session replays all land in the same UI, share the same query language, and are correlated automatically.

</Step>
</Steps>

## Further reading {#further-reading}

- [OpenTelemetry collector](/clickstack/ingesting-data/collector) for configuring and deploying the collector.
- [Going to production](/clickstack/managing/production) for recommendations when going to production.
Loading