Skip to content

Add Opentelemetry guide #23187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions _vale/config/vocabularies/Docker/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ deprovisioning
deserialization
deserialize
Dev
Dex
Dev Environments?
Dex
displayName
Expand Down
200 changes: 200 additions & 0 deletions content/guides/opentelemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
title: Instrumenting a JavaScript App with OpenTelemetry
description: &desc Learn how to instrument a JavaScript application using OpenTelemetry in a Dockerized environment.
keywords: OpenTelemetry, observability, tracing
linktitle: Instrumenting JS Apps with OpenTelemetry
summary: *desc
tags: [app-dev, observability]
languages: [JavaScript]
params:
time: 10 minutes
---

OpenTelemetry (OTEL) is an open-source observability framework that provides a set of APIs, SDKs, and tools for collecting telemetry data, such as metrics, logs, and traces, from applications. With OpenTelemetry, developers can obtain valuable insights into how their services perform in production or during local development.

Check failure on line 13 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'OTel' instead of 'OTEL'. Raw Output: {"message": "[Vale.Terms] Use 'OTel' instead of 'OTEL'.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 13, "column": 16}}}, "severity": "ERROR"}

Check warning on line 13 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Docker.Acronyms] 'OTEL' has no definition. Raw Output: {"message": "[Docker.Acronyms] 'OTEL' has no definition.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 13, "column": 16}}}, "severity": "WARNING"}

A key component of OpenTelemetry is the OpenTelemetry Protocol (OTLP) a general-purpose, vendor-agnostic protocol designed to transmit telemetry data efficiently and reliably. OTLP supports multiple data types (traces, metrics, logs) over HTTP or gRPC, making it the default and recommended protocol for communication between instrumented applications, the OpenTelemetry Collector, and backends such as Jaeger or Prometheus.

This guide walks you through how to instrument a simple Node.js application with OpenTelemetry and run both the app and a collector using Docker. This setup is ideal for local development and testing observability before integrating with external observability platforms like Prometheus, Jaeger, or Grafana.

In this guide, you'll learn how to:

- How to set up OpenTelemetry in a Node.js app.
- How to run an OpenTelemetry Collector in Docker.
- How to visualize traces with Jaeger.
- How to use Docker Compose to manage the full observability stack.

## Using OpenTelemetry with Docker

the official [docker image for opentelemetry](https://hub.docker.com/r/otel/opentelemetry-collector-contrib) provides a convenient way to deploy and manage dex instances. Opentelemetry is available for various cpu architectures, including amd64, armv7, and arm64, ensuring compatibility with different devices and platforms. Same for the [Jaeger docekr image](https://hub.docker.com/r/jaegertracing/jaeger).

Check failure on line 28 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'cpu'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'cpu'?", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 28, "column": 211}}}, "severity": "ERROR"}

Check failure on line 28 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'Opentelemetry'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'Opentelemetry'?", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 28, "column": 172}}}, "severity": "ERROR"}

Check failure on line 28 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'Dex' instead of 'dex'. Raw Output: {"message": "[Vale.Terms] Use 'Dex' instead of 'dex'.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 28, "column": 157}}}, "severity": "ERROR"}

## Prerequisites

[Docker Compose](/compose/): Recommended for managing multi-container Docker applications.

Basic knowledge of Node.js and Docker.

## Project Structure

Create the project directory:
```bash
mkdir otel-js-app
cd otel-js-app
```

```bash
otel-js-app/
├── docker-compose.yaml
├── collector-config.yaml
├── app/
│ ├── package.json
│ ├── app.js
│ └── tracer.js
```

## Create a Simple Node.js App

Initialize a basic Node.js app:

```bash
mkdir app && cd app
npm init -y
npm install express @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
```

Now, add the application logic:

```js
// app/app.js
const express = require('express');
require('./tracer'); // Initialize OpenTelemetry

const app = express();

app.get('/', (req, res) => {
res.send('Hello from OpenTelemetry demo app!');
});

const PORT = 3000;
app.listen(PORT, () => {
console.log(`App listening at http://localhost:${PORT}`);
});
```

## Configure OpenTelemetry Tracing

Check warning on line 85 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Docker.HeadingSentenceCase] Use sentence case for headings: 'Configure OpenTelemetry Tracing'. Raw Output: {"message": "[Docker.HeadingSentenceCase] Use sentence case for headings: 'Configure OpenTelemetry Tracing'.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 85, "column": 4}}}, "severity": "WARNING"}

Create the tracer configuration file:

```js
// app/tracer.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');

const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://collector:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
```

## Configure the OpenTelemetry Collector

Create a collector-config.yaml file at the root:

```yaml
# collector-config.yaml
receivers:
otlp:
protocols:
http:

exporters:
logging:
loglevel: debug
jaeger:
endpoint: jaeger:14250
tls:
insecure: true

service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging, jaeger]
```

## Add Docker Compose Configuration

Create the `docker-compose.yaml` file:

```yaml
version: '3.9'

services:
app:
build: ./app
ports:
- "3000:3000"
environment:
- NODE_ENV=development
depends_on:
- collector

collector:
image: otel/opentelemetry-collector:latest
volumes:
- ./collector-config.yaml:/etc/otelcol/config.yaml
command: ["--config=/etc/otelcol/config.yaml"]
ports:
- "4318:4318" # OTLP

jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # UI
- "14250:14250" # Collector gRPC
```

Now, add the `Dockerfile` inside the `app/` folder:

```dockerfile
# app/Dockerfile
FROM node:18

WORKDIR /usr/src/app
COPY . .
RUN npm install

CMD ["node", "app.js"]
```

## Start the Stack

Start all services with Docker Compose:

```bash
docker compose up --build
```

Once the services are running:

Visit your app at [http://localhost:3000](http://localhost:3000)

View traces at [http://localhost:16686](http://localhost:16686) in the Jaeger UI

Check failure on line 188 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'ui' instead of 'UI'. Raw Output: {"message": "[Vale.Terms] Use 'ui' instead of 'UI'.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 188, "column": 79}}}, "severity": "ERROR"}

## Verify Traces in Jaeger

After visiting your app's root endpoint, open Jaeger’s UI, search for the service (default is usually unknown_service unless explicitly named), and check the traces.

Check failure on line 192 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'unknown_service'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'unknown_service'?", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 192, "column": 103}}}, "severity": "ERROR"}

Check failure on line 192 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'ui' instead of 'UI'. Raw Output: {"message": "[Vale.Terms] Use 'ui' instead of 'UI'.", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 192, "column": 56}}}, "severity": "ERROR"}

You should see spans for the HTTP request, middleware, and auto-instrumented libraries.

Check failure on line 194 in content/guides/opentelemetry.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'middleware'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'middleware'?", "location": {"path": "content/guides/opentelemetry.md", "range": {"start": {"line": 194, "column": 44}}}, "severity": "ERROR"}

## Conclusion

You now have a fully functional OpenTelemetry setup using Docker Compose. You've instrumented a basic JavaScript app to export traces and visualized them using Jaeger. This architecture is extendable for more complex applications and observability pipelines using Prometheus, Grafana, or cloud-native exporters.

For advanced topics such as custom span creation, metrics, and logs, consult the OpenTelemetry JavaScript docs.
Loading