Skip to content

Latest commit

 

History

History

README.md

Demonstrating Application-Level Transparency in OpenTelemetry Tracing for Express.js Project

A comprehensive example showcasing how to achieve application-level transparency in OTel tracing using @waitingliou/tsc-otel-instrumentation - automatic, non-invasive OpenTelemetry instrumentation for your Express project running on Node.js runtime.

demo

Project Purpose

Primary Goals

  • Show how to achieve clean business logic without manual instrumentation
  • Provide a complete example of @waitingliou/tsc-otel-instrumentation integration
  • Demonstrate basic observability stack (traces + logs)

Who Is This For?

  • Developers learning OpenTelemetry and automatic instrumentation
  • Teams, Organizations seeking non-invasive tracing approaches

Key Features Demonstrated

  1. Zero Code Pollution: Business logic remains free of tracing code
  2. Clean Architecture: Clear separation of concerns
  3. Dependency Injection: Proper service composition
  4. Type Safety: Full TypeScript support
  5. Automatic Tracing: Services and repositories automatically traced
  6. Structured Logging: Pino logger with OpenTelemetry context
  7. Production Ready: Proper error handling and logging

Technology Stack

Core Technologies

Component Technology Version Purpose
Web Framework Express 4.x+ Fast, minimalist web framework
Runtime Node.js 18.0+ JavaScript runtime
Language TypeScript 5.0+ Type-safe development
Compiler TypeScript Compiler (tsc) 5.0+ ⚠️ Required for compilation
Instrumentation @waitingliou/tsc-otel-instrumentation 1.1.7+ Automatic class method tracing
Observability OpenTelemetry Latest Distributed tracing standard
Trace Backend Grafana Cloud Tempo Cloud Trace storage and visualization

Project Architecture

This example follows Clean Architecture principles to demonstrate real-world usage:

Architecture Layers

┌────────────────────────────────────────┐
│  Controllers (HTTP Handlers)           │
│  - Handle requests/responses           │
└─────────────┬──────────────────────────┘
              │ Dependency Injection
┌─────────────▼──────────────────────────┐
│  Use Cases (Business Logic Services)   │  ← Automatically instrumented
│  - UserService, NotificationService    │
└─────────────┬──────────────────────────┘
              │ Interfaces (Ports)
┌─────────────▼──────────────────────────┐
│  Repositories (Data Access)            │  ← Automatically instrumented
│  - UserRepository, SocialLinkRepo      │
└────────────────────────────────────────┘

Quick Start

Prerequisites

Installation

# Install dependencies
npm install

Environment Configuration

Create .env file:

# Application Configuration
NODE_ENV=production
PORT=3003

# OpenTelemetry - Get from Grafana Cloud → Connections → OTLP
OTEL_EXPORTER_OTLP_ENDPOINT=...
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <your-base64-token>"

# Grafana Cloud Credentials (for logs if using Loki)
GRAFANA_HOST=https://logs-prod-XXX.grafana.net
GRAFANA_USERNAME=123456
GRAFANA_CLOUD_TOKEN=glc_xxxxxxxxxxxxxxxxxxxxx

How to get credentials:

  1. Visit grafana.com and sign up
  2. Navigate to ConnectionsAdd new connection
  3. Select OpenTelemetry (OTLP) for traces
  4. Select Loki for logs
  5. Copy credentials to .env

Build and Run

npm run dev

Server starts at http://localhost:3003

API Endpoints

Health Check

GET /health

User Signup

POST /users/signup
Content-Type: application/json

{
  "name": "John Doe",
  "email": "[email protected]"
}

How It Works

Automatic Instrumentation Configuration

The magic happens in tsconfig.json:

{
  "compilerOptions": {
    "plugins": [
      {
        "transform": "@waitingliou/tsc-otel-instrumentation/transformer",
        "include": [
          "**/*service.ts",
          "**/*repository.ts"
        ],
        "instrumentPrivateMethods": true,
        "spanNamePrefix": "Demo",
        "autoInjectTracer": true
      }
    ]
  }
}

This configuration automatically instruments:

  • All classes in files matching *service.ts pattern
  • All classes in files matching *repository.ts pattern
  • Both public and private methods
  • Generates spans with "Demo" prefix

Code Stays Clean

Compare traditional manual instrumentation vs. automatic:

❌ Manual (Invasive)

async signup(name: string, email: string): Promise<string> {
  const span = tracer.startSpan('UserService.signup');
  try {
    // business logic
    span.end();
  } catch (error) {
    span.recordException(error);
    span.end();
    throw error;
  }
}

✅ Automatic (Clean)

async signup(name: string, email: string): Promise<string> {
  // Just pure business logic!
  const validationResult = await this.emailValidationService.validateEmail(email);
  if (!validationResult.isValid) {
    throw new Error(`Email validation failed`);
  }
  return await this.usersRepository.signup(name, email);
}

Additional Resources

Learn More About @waitingliou/tsc-otel-instrumentation

Related Documentation

Happy tracing! 🎉

Achieve application-level transparency in your TypeScript projects with zero code pollution.