Unified traces, metrics, and logs across a Node.js microservice fleet β demonstrating vendor-neutral observability with the CNCF standard.
Most teams bolt on monitoring as an afterthought: Prometheus for metrics, a separate tool for traces, another for logs. This creates vendor lock-in, data silos, and incomplete observability. You can see that a request is slow, but not why β because traces, metrics, and logs aren't correlated.
This project instruments a small Node.js service set with OpenTelemetry β the vendor-neutral CNCF standard β providing unified traces, metrics, and logs that flow through a single collector into open-source backends:
ββββββββββββββ ββββββββββββββ ββββββββββββββ
β API Gatewayββββ Order Svc ββββ Payment Svcβ
βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ
β β β
βββββββββββββββββΌββββββββββββββββ
β OTLP
βββββββββΌββββββββ
β OTel Collectorβ
βββββ¬ββββ¬ββββ¬ββββ
β β β
βββββββ β βββββββ
βΌ βΌ βΌ
ββββββββββ βββββββββ ββββββββββ
β Jaeger β βProm. β β Loki β
β(Traces)β β(Metr.)β β (Logs) β
ββββββββββ βββββββββ ββββββββββ
β
ββββββΌβββββ
β Grafana β
βDashboard β
ββββββββββββ
Adding prom-client to an Express app is a 10-minute tutorial. This project demonstrates the full OpenTelemetry pipeline: auto-instrumented traces that propagate context across services, custom metrics, structured logs with trace correlation, and a Collector that routes telemetry to multiple backends. That's what SRE interviews actually test.
- Instrumentation: OpenTelemetry Node.js SDK
- Collection: OTel Collector
- Traces: Jaeger
- Metrics: Prometheus
- Logs: Loki
- Dashboards: Grafana
- Orchestration: Docker Compose
βββ services/
β βββ api-gateway/ # Express gateway with OTel auto-instrumentation
β βββ order-service/ # Order processing service
β βββ payment-service/ # Payment processing service
βββ otel-collector/
β βββ config.yaml # OTel Collector pipeline configuration
βββ grafana/
β βββ dashboards/ # Pre-configured Grafana dashboards
βββ docker-compose.yaml # Full stack: services + collector + backends
βββ docs/ARCHITECTURE.md
βββ README.md
docker-compose up -d --build| UI | URL |
|---|---|
| API Gateway | http://localhost:3000 |
| Jaeger (Traces) | http://localhost:16686 |
| Prometheus (Metrics) | http://localhost:9090 |
| Grafana (Dashboards) | http://localhost:3001 |
π‘ Trace Sampling: This demo uses the default
AlwaysOnsampler for full visibility. In production, switch toParentBased(TraceIdRatioBased(0.1))for 10% head-based sampling, or use the OTel Collector's tail-based sampling processor to capture only error/slow traces.
π‘ Trace Sampling: This demo uses the default
AlwaysOnsampler for full visibility. In production, switch toParentBased(TraceIdRatioBased(0.1))for 10% head-based sampling, or use the OTel Collector's tail-based sampling processor to capture only error/slow traces.
| What | Status | Details |
|---|---|---|
| OTel Pipeline | Real | Full Jaeger, Prometheus, Loki, Grafana stack running via Docker Compose. |
| Microservices | Real | Node.js services auto-instrumented with OTel emitting real telemetry. |
| Traffic | Mocked | Simulated curl traffic replaces real user load. |
This project implements Progressive Credential Loading (PCL), allowing it to run immediately out-of-the-box without external dependencies while seamlessly upgrading as credentials are provided.
Ideal for CI, testing, and rapid local evaluation.
- Execution: Run with default configuration (
cp .env.example .env). Zero API keys, databases, or cloud credentials required. - Fallback Behavior: In-memory caches, deterministic offline heuristics, mock vector embeddings, and local mock storage are automatically used.
- Health Verification: Endpoints report
{"mode": "mock"}for unconfigured dependencies.
Connect specific external services while keeping others mocked.
- Execution: Provide credentials in
.envfor the services you want active (e.g.,OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, SERVICE_NAME). - Progressive Upgrade: The runtime inspects environment variables on boot and dynamically promotes only configured modules to live mode.
- Safe Fallback: Any missing credentials automatically remain on mock fallbacks with no crashes or missing service errors.
Enterprise deployment with production-grade backends, security scans, and container distributions.
- OCI Container Image: Pre-built, multi-arch image on GitHub Container Registry:
docker pull ghcr.io/sumitdalavi/opentelemetry-microservice-demo:v0.2.0
- Production Orchestration: Mount secrets via Kubernetes Secrets or cloud secret managers with atomic config reload support.
- Release Verification: Checksums and SPDX SBOM available on GitHub Releases.
- Architecture β System diagram and component details
- Runbook β Setup, commands, and expected outputs
- Decisions β ADRs for observability stack choices
- Changelog β Change history
- Failure Walkthrough β Guide to debugging a simulated failure using OTel traces.
| Decision | Rationale |
|---|---|
| OTel over vendor SDKs | Vendor-neutral; avoids Datadog/New Relic lock-in |
| OTel Collector over direct export | Collector acts as a gateway, enabling fan-out to multiple backends without code changes |
| Jaeger over Tempo | Jaeger has standalone mode ideal for PoC; Tempo requires object storage |
| 3 services (not 1) | Demonstrates distributed tracing context propagation across service boundaries |
| Tool | Version | Purpose |
|---|---|---|
| Docker | >= 24.x | Container runtime |
| Docker Compose | >= 2.x | Multi-container orchestration |
| curl or browser | Any | API testing & UI access |
# 1. Clone the repository
git clone https://github.com/SumitDalavi/opentelemetry-microservice-demo.git
cd opentelemetry-microservice-demo
# 2. Build and start all services (3 microservices + observability stack)
docker-compose up -d --build
# 3. Verify all containers are running
docker-compose ps| Service | URL | Purpose |
|---|---|---|
| API Gateway | http://localhost:3000 | Application entry point |
| Jaeger UI | http://localhost:16686 | Distributed tracing |
| Prometheus | http://localhost:9090 | Metrics collection |
| Grafana | http://localhost:3001 | Dashboards (admin/admin) |
| OTEL Collector | http://localhost:4318 | Telemetry pipeline |
# Hit the API Gateway multiple times to generate trace data
curl http://localhost:3000/api/orders
curl http://localhost:3000/api/orders
curl http://localhost:3000/api/orders- Open http://localhost:16686
- Select "api-gateway" from the Service dropdown
- Click "Find Traces"
- Click on a trace to see the full request flow across microservices (api-gateway Γ’β β order-service Γ’β β payment-service)
- Open http://localhost:9090
- Try queries like:
http_requests_totalΓ’β¬β Total HTTP requests per servicehttp_request_duration_seconds_bucketΓ’β¬β Request latency distribution
- Open http://localhost:3001 (login: admin / admin)
- Add Prometheus as a data source (URL:
http://prometheus:9090) - Add Jaeger as a data source (URL:
http://jaeger:16686)
| Check | Command / Action | Expected |
|---|---|---|
| All services up | docker-compose ps |
7 containers running |
| API responds | curl http://localhost:3000/api/orders |
Order JSON response |
| Traces visible | Open Jaeger UI β Find Traces | Multi-span traces |
| Metrics flowing | Prometheus β http_requests_total |
Incrementing counters |
# Stop all services
docker-compose downThe services in this repository are instrumented with the OpenTelemetry Node.js SDK, including custom spans within the order-service. Traces are exported via OTLP to the OpenTelemetry Collector and visualized in Jaeger.
You can verify the end-to-end trace propagation by running the traffic generation script:
make trafficAfter generating traffic, you can view the resulting traces by querying the Jaeger API:
curl -s "http://localhost:16686/api/traces?service=order-service&limit=1"Simulated Trace Output (Evidence):
[
{
"operationName": "GET /api/orders",
"startTime": 1788016728636000,
"duration": 6679
},
{
"operationName": "process-orders",
"startTime": 1788016728639000,
"duration": 3615
},
{
"operationName": "GET /payments/verify",
"startTime": 1788016728640000,
"duration": 716
}
]The project includes an automated DevSecOps release pipeline (.github/workflows/release.yml) triggered on semantic version tags (v*.*.*).
Each release automatically:
- Executes test suite gates.
- Builds the container image.
- Performs vulnerability scanning via Trivy.
- Generates an SPDX SBOM (
sbom.spdx.json) using Anchore Syft. - Publishes a GitHub Release with attached SBOM.
# Verify release via GitHub CLI
gh release view v0.2.0 --repo SumitDalavi/opentelemetry-microservice-demo
# Download and inspect SBOM
gh release download v0.2.0 --repo SumitDalavi/opentelemetry-microservice-demo --pattern "sbom.spdx.json"
cat sbom.spdx.json | jq .Sumit Dalavi β Senior DevSecOps / Platform Engineer GitHub | LinkedIn
Built with a focus on robust patterns, not toy demos.