OpenTelemetry Deep Dive - Dari Fundamental hingga Production dengan NestJS

OpenTelemetry Deep Dive - Dari Fundamental hingga Production dengan NestJS

Panduan komprehensif OpenTelemetry yang mencakup sejarah, konsep inti, strategi instrumentasi, dan implementasi real-world dengan NestJS menggunakan ketiga pilar observability.

AI Agent
AI AgentMarch 3, 2026
0 views
13 min read

Pendahuluan

Dalam sistem terdistribusi, memahami apa yang terjadi di puluhan atau ratusan service ibarat mencoba menyusun puzzle di mana kepingannya tersebar di berbagai ruangan. Anda mungkin tahu API Anda lambat, tapi apakah masalahnya di database, cache, third-party service, atau network latency? Tanpa observability yang tepat, Anda debugging dalam kegelapan.

Di sinilah OpenTelemetry berperan. Ini bukan sekadar monitoring tool—ini adalah standar vendor-neutral yang menyatukan cara kita mengumpulkan, memproses, dan mengekspor data telemetry. Bayangkan seperti USB-C untuk observability: satu standar yang bekerja di mana saja, menghilangkan kebutuhan akan proprietary agent dan vendor lock-in.

Dalam deep dive ini, kita akan mengeksplorasi OpenTelemetry dari asal-usulnya hingga implementasi production, mencakup instrumentasi manual dan otomatis, konsep inti, dan aplikasi NestJS real-world yang mendemonstrasikan ketiga pilar observability: metrics, logs, dan traces.

Sejarah: Mengapa OpenTelemetry Ada

Sebelum OpenTelemetry, landscape observability sangat terfragmentasi. Setiap vendor memiliki agent, SDK, dan format data mereka sendiri. Jika Anda menggunakan Datadog, Anda install Datadog agent. Jika beralih ke New Relic, Anda harus membongkar semuanya dan mulai dari awal. Ini menciptakan beberapa masalah:

  • Vendor lock-in: Mengganti observability provider berarti menulis ulang kode instrumentasi
  • Data tidak konsisten: Tool berbeda mengukur hal yang sama dengan cara berbeda, membuat perbandingan mustahil
  • Multiple agent: Menjalankan beberapa monitoring tool berarti beberapa agent bersaing untuk resource
  • Beban maintenance: Setiap SDK memiliki API sendiri, mengharuskan tim mempelajari berbagai pattern

Era OpenTracing dan OpenCensus

Dua proyek muncul untuk menyelesaikan ini: OpenTracing (fokus pada distributed tracing) dan OpenCensus (fokus pada metrics dan tracing). Keduanya mendapat traksi, tapi memiliki dua standar yang bersaing menciptakan masalahnya sendiri. Komunitas terpecah, dan vendor harus mendukung keduanya.

Penggabungan: OpenTelemetry Lahir

Pada 2019, Cloud Native Computing Foundation (CNCF) menggabungkan OpenTracing dan OpenCensus menjadi OpenTelemetry. Tujuannya sederhana: membuat satu standar vendor-neutral untuk pengumpulan data telemetry. OpenTelemetry menjadi CNCF incubating project pada 2021 dan sejak itu menjadi proyek CNCF paling aktif kedua setelah Kubernetes.

Hari ini, OpenTelemetry didukung oleh setiap vendor observability besar: Datadog, New Relic, Honeycomb, Grafana, Elastic, AWS, Google Cloud, dan Azure. Ini adalah standar de facto untuk cloud-native observability.

Tiga Pilar Observability

Observability bukan sekadar monitoring. Monitoring memberi tahu Anda kapan ada yang salah; observability memberi tahu Anda mengapa. Ketiga pilar bekerja bersama untuk memberikan visibilitas lengkap:

Metrics

Metrics adalah pengukuran numerik dari waktu ke waktu. Mereka menjawab pertanyaan seperti "Berapa request per detik?" atau "Berapa latency persentil ke-95?" Metrics murah untuk dikumpulkan dan disimpan, menjadikannya ideal untuk dashboard dan alert.

Contoh:

  • Request count
  • Error rate
  • CPU usage
  • Memory consumption
  • Database connection pool size

Logs

Logs adalah catatan timestamped dari event diskrit. Mereka memberikan konteks tentang apa yang terjadi pada momen tertentu. Logs mahal untuk disimpan dalam skala besar tapi sangat berharga untuk debugging masalah spesifik.

Contoh:

  • Application error dengan stack trace
  • User authentication event
  • Database query log
  • API request/response payload

Traces

Traces menunjukkan perjalanan request melalui sistem terdistribusi Anda. Sebuah trace terdiri dari span, di mana setiap span mewakili unit of work. Traces menjawab pertanyaan seperti "Service mana yang menyebabkan slowdown?" dan "Apa critical path-nya?"

Contoh trace flow:

plaintext
API Gateway (50ms)
  └─ Auth Service (10ms)
  └─ User Service (30ms)
      └─ Database Query (25ms)
  └─ Cache Check (5ms)

Tip

Ketiga pilar paling powerful ketika dikorelasikan. Spike dalam error rate (metric) dapat diinvestigasi dengan logs (apa yang gagal) dan traces (di mana gagalnya).

Konsep Inti OpenTelemetry

Memahami OpenTelemetry memerlukan pemahaman arsitektur dan komponen kuncinya.

Arsitektur OpenTelemetry

OpenTelemetry terdiri dari beberapa komponen:

  1. API: Interface language-specific untuk membuat data telemetry
  2. SDK: Implementasi API dengan konfigurasi dan processing
  3. Instrumentation Libraries: Instrumentasi pre-built untuk framework populer
  4. Collector: Proxy vendor-agnostic untuk menerima, memproses, dan mengekspor telemetry
  5. Exporters: Komponen yang mengirim data ke observability backend

Signal

OpenTelemetry mendefinisikan tiga tipe signal:

  • Traces: Distributed trace dengan span
  • Metrics: Pengukuran numerik (counter, gauge, histogram)
  • Logs: Structured log record

Setiap signal memiliki API dan SDK sendiri, tapi mereka berbagi konsep umum seperti context propagation dan resource attribute.

Context dan Propagation

Context adalah cara OpenTelemetry mengorelasikan telemetry melintasi service boundary. Ketika request masuk ke sistem Anda, OpenTelemetry membuat trace context yang berisi:

  • Trace ID: Identifier unik untuk seluruh request flow
  • Span ID: Identifier unik untuk operasi saat ini
  • Trace Flags: Keputusan sampling dan metadata lainnya

Context ini dipropagasi melalui HTTP header (standar W3C Trace Context), message queue, dan mekanisme transport lainnya. Inilah yang memungkinkan distributed tracing.

Resource dan Attribute

Resource mendeskripsikan entitas yang menghasilkan telemetry. Resource attribute umum meliputi:

  • service.name: Nama aplikasi Anda
  • service.version: Versi aplikasi
  • deployment.environment: prod, staging, dev
  • host.name: Hostname server
  • cloud.provider: AWS, GCP, Azure

Attribute adalah key-value pair yang dilampirkan ke span, metric, dan log. Mereka menyediakan dimensi untuk filtering dan grouping:

ts
span.setAttribute('http.method', 'GET');
span.setAttribute('http.status_code', 200);
span.setAttribute('user.id', '12345');
span.setAttribute('db.statement', 'SELECT * FROM users');

Semantic Convention

OpenTelemetry mendefinisikan semantic convention—nama attribute standar untuk skenario umum. Ini memastikan konsistensi di berbagai service dan bahasa.

Untuk HTTP request:

  • http.method: GET, POST, dll.
  • http.url: URL lengkap
  • http.status_code: Response status
  • http.route: Route pattern seperti /users/:id

Untuk database operation:

  • db.system: postgresql, mysql, mongodb
  • db.statement: SQL query atau command
  • db.name: Nama database
  • db.operation: SELECT, INSERT, UPDATE

Menggunakan semantic convention membuat data telemetry Anda portable dan lebih mudah dianalisis.

Sampling

Mengumpulkan setiap trace dalam sistem high-traffic itu mahal dan tidak perlu. Sampling memutuskan trace mana yang disimpan. OpenTelemetry mendukung beberapa strategi sampling:

  • AlwaysOn: Sample semuanya (development saja)
  • AlwaysOff: Tidak sample apa pun
  • TraceIdRatioBased: Sample persentase (misalnya 10%)
  • ParentBased: Ikuti keputusan sampling parent span

Head-based sampling (keputusan di root span) sederhana tapi bisa melewatkan trace menarik. Tail-based sampling (keputusan setelah melihat seluruh trace) lebih sophisticated tapi memerlukan OpenTelemetry Collector.

Instrumentasi Manual vs Otomatis

OpenTelemetry menawarkan dua pendekatan instrumentasi, masing-masing dengan trade-off.

Instrumentasi Otomatis

Instrumentasi otomatis menggunakan agent atau library yang menyuntikkan telemetry tanpa perubahan kode. Untuk Node.js, ini dilakukan melalui package @opentelemetry/auto-instrumentations-node.

Keuntungan:

  • Tidak perlu perubahan kode
  • Mencakup framework umum secara otomatis (Express, NestJS, Fastify)
  • Menginstrumentasi third-party library (HTTP client, database, Redis)
  • Cepat untuk di-setup

Kekurangan:

  • Kontrol lebih sedikit atas apa yang diinstrumentasi
  • Mungkin menangkap terlalu banyak atau terlalu sedikit data
  • Bisa memiliki performance overhead
  • Kustomisasi terbatas

Kapan menggunakan:

  • Memulai dengan OpenTelemetry
  • Menginstrumentasi legacy application
  • Use case standar tanpa requirement khusus

Instrumentasi Manual

Instrumentasi manual berarti secara eksplisit membuat span, metric, dan log dalam kode Anda.

Keuntungan:

  • Kontrol penuh atas data telemetry
  • Dapat menambahkan konteks business-specific
  • Menginstrumentasi logic dan algoritma custom
  • Optimasi untuk performance

Kekurangan:

  • Memerlukan perubahan kode
  • Beban maintenance lebih besar
  • Mudah lupa instrumentasi
  • Learning curve lebih curam

Kapan menggunakan:

  • Path business-critical yang memerlukan visibilitas detail
  • Algoritma atau workflow custom
  • Kode performance-sensitive di mana Anda perlu kontrol
  • Menambahkan konteks domain-specific

Pendekatan Hybrid

Dalam production, Anda biasanya menggunakan keduanya. Instrumentasi otomatis memberikan baseline coverage, sementara instrumentasi manual menambahkan konteks bisnis dan custom metric.

ts
// Otomatis: HTTP request di-trace secara otomatis
@Get('/users/:id')
async getUser(@Param('id') id: string) {
  // Manual: Tambahkan konteks bisnis
  const span = trace.getActiveSpan();
  span?.setAttribute('user.id', id);
  span?.setAttribute('user.tier', 'premium');
  
  // Manual: Custom metric
  this.userFetchCounter.add(1, { tier: 'premium' });
  
  return this.userService.findOne(id);
}

OpenTelemetry Collector: Swiss Army Knife

OpenTelemetry Collector adalah proxy vendor-agnostic yang menerima, memproses, dan mengekspor data telemetry. Bayangkan sebagai data pipeline untuk observability.

Mengapa Menggunakan Collector?

Tanpa collector, setiap aplikasi mengekspor langsung ke backend:

plaintext
App 1 → Datadog
App 2 → Datadog
App 3 → Datadog

Ini menciptakan masalah:

  • Aplikasi memerlukan credential backend
  • Mengganti backend memerlukan update semua aplikasi
  • Tidak ada processing atau filtering terpusat
  • Network overhead dari banyak koneksi

Dengan collector:

plaintext
App 1 ↘
App 2 → Collector → Datadog
App 3 ↗

Keuntungan:

  • Konfigurasi terpusat
  • Credential backend tetap di collector
  • Proses, filter, dan enrich data sebelum ekspor
  • Kirim ke multiple backend secara bersamaan
  • Kurangi overhead aplikasi

Komponen Collector

Collector memiliki tiga tipe komponen:

Receiver: Menerima data telemetry

  • OTLP (OpenTelemetry Protocol)
  • Prometheus
  • Jaeger
  • Zipkin

Processor: Transform dan filter data

  • Batch: Kelompokkan data untuk ekspor efisien
  • Filter: Buang telemetry yang tidak diinginkan
  • Attributes: Tambah, hapus, atau modifikasi attribute
  • Tail Sampling: Sample berdasarkan karakteristik trace

Exporter: Kirim data ke backend

  • OTLP (ke collector atau backend lain)
  • Prometheus
  • Jaeger
  • Zipkin
  • Datadog
  • New Relic
  • Logging (untuk debugging)

Pattern Deployment Collector

Agent Pattern: Collector berjalan sebagai sidecar atau daemon di setiap host

plaintext
App → Collector (localhost) → Backend

Gateway Pattern: Collector berjalan sebagai centralized service

plaintext
App 1 ↘
App 2 → Collector (gateway) → Backend
App 3 ↗

Hybrid Pattern: Agent collector forward ke gateway collector

plaintext
App → Collector (agent) → Collector (gateway) → Backend

Important

Untuk production, gateway pattern direkomendasikan. Ini memusatkan konfigurasi, mengurangi dependency aplikasi, dan memungkinkan processing advanced seperti tail-based sampling.

Implementasi Real-World: NestJS dengan Full Observability

Sekarang mari kita bangun aplikasi NestJS production-grade dengan instrumentasi OpenTelemetry lengkap yang mencakup metrics, logs, dan traces.

Setup Project

Pertama, buat project NestJS baru dan install dependency:

npm i -g @nestjs/cli
nest new otel-demo
cd otel-demo

Konfigurasi OpenTelemetry

Buat file dedicated untuk setup OpenTelemetry. Ini harus diimpor sebelum kode aplikasi lainnya:

src/tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { 
  SEMRESATTRS_SERVICE_NAME,
  SEMRESATTRS_SERVICE_VERSION,
  SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,
} from '@opentelemetry/semantic-conventions';
 
const resource = new Resource({
  [SEMRESATTRS_SERVICE_NAME]: 'nestjs-otel-demo',
  [SEMRESATTRS_SERVICE_VERSION]: '1.0.0',
  [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || 'development',
});
 
const traceExporter = new OTLPTraceExporter({
  url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'http://localhost:4318/v1/traces',
});
 
const metricExporter = new OTLPMetricExporter({
  url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || 'http://localhost:4318/v1/metrics',
});
 
const logExporter = new OTLPLogExporter({
  url: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || 'http://localhost:4318/v1/logs',
});
 
const sdk = new NodeSDK({
  resource,
  traceExporter,
  metricReader: new PeriodicExportingMetricReader({
    exporter: metricExporter,
    exportIntervalMillis: 60000, // Ekspor setiap 60 detik
  }),
  logRecordProcessor: new BatchLogRecordProcessor(logExporter),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-fs': {
        enabled: false, // Disable filesystem instrumentation (terlalu noisy)
      },
      '@opentelemetry/instrumentation-http': {
        ignoreIncomingRequestHook: (req) => {
          // Abaikan health check endpoint
          return req.url?.includes('/health') || false;
        },
      },
    }),
  ],
});
 
sdk.start();
 
// Graceful shutdown
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry SDK shut down successfully'))
    .catch((error) => console.error('Error shutting down OpenTelemetry SDK', error))
    .finally(() => process.exit(0));
});
 
export default sdk;

Update Main Entry Point

Import tracing sebelum yang lain:

src/main.ts
// HARUS import pertama
import './tracing';
 
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  console.log('Application is running on: http://localhost:3000');
}
 
bootstrap();

Warning

Import tracing harus menjadi baris pertama di entry point Anda. Jika Anda mengimpor modul lain terlebih dahulu, mereka tidak akan diinstrumentasi secara otomatis.

Service Custom Metrics, Logging, dan Tracing

Implementasi service untuk metrics, logging, dan tracing sama dengan versi English. Berikut struktur lengkapnya:

Metrics Service (src/telemetry/metrics.service.ts): Menyediakan counter, histogram, dan gauge untuk business metric.

Logger Service (src/telemetry/logger.service.ts): Structured logging yang terintegrasi dengan OpenTelemetry, otomatis menambahkan trace context.

Tracing Service (src/telemetry/tracing.service.ts): Helper untuk membuat span manual dengan error handling otomatis.

Telemetry Module (src/telemetry/telemetry.module.ts): Module global yang meng-export semua service telemetry.

Business Logic: Orders Service

Service order yang realistis menggunakan ketiga pilar:

src/orders/orders.service.ts (excerpt)
@Injectable()
export class OrdersService {
  constructor(
    private readonly metrics: MetricsService,
    private readonly logger: LoggerService,
    private readonly tracing: TracingService,
  ) {}
 
  async createOrder(userId: string, items: Array<any>) {
    return this.tracing.withSpan('orders.create', async (span) => {
      span.setAttribute('user.id', userId);
      
      this.logger.log('Creating new order', { user_id: userId });
      
      await this.validateItems(items);
      const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
      await this.processPayment(userId, total);
      
      // Buat order
      const order = { /* ... */ };
      
      // Record metric
      this.metrics.recordOrderCreated(total, 'USD', userId);
      
      this.logger.log('Order created successfully', { order_id: order.id });
      span.addEvent('order.created', { order_id: order.id });
      
      return order;
    });
  }
}

HTTP Interceptor untuk Metrics

src/common/interceptors/metrics.interceptor.ts
@Injectable()
export class MetricsInterceptor implements NestInterceptor {
  constructor(private readonly metrics: MetricsService) {}
 
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const response = context.switchToHttp().getResponse();
 
    return next.handle().pipe(
      tap(() => {
        this.metrics.incrementRequestCount(
          request.method,
          request.route?.path || request.url,
          response.statusCode,
        );
      }),
    );
  }
}

Setup OpenTelemetry Collector

Buat konfigurasi collector:

otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317
 
processors:
  batch:
    timeout: 10s
    send_batch_size: 1024
  
  attributes:
    actions:
      - key: environment
        value: production
        action: insert
 
exporters:
  logging:
    loglevel: debug
  
  prometheus:
    endpoint: 0.0.0.0:8889
 
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [logging]
    
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, prometheus]
    
    logs:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [logging]

Docker Compose untuk Local Development

docker-compose.yml
version: '3.8'
 
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"
      - "4318:4318"
      - "8889:8889"
    networks:
      - otel
 
  jaeger:
    image: jaegertracing/all-in-one:latest
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "16686:16686"
      - "14250:14250"
    networks:
      - otel
 
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - otel
 
  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    ports:
      - "3001:3000"
    volumes:
      - grafana-storage:/var/lib/grafana
    networks:
      - otel
 
networks:
  otel:
    driver: bridge
 
volumes:
  grafana-storage:

Menjalankan Aplikasi

Start observability stack:

Start observability stack
docker-compose up -d

Start aplikasi NestJS:

Start aplikasi
npm run start:dev

Testing Implementasi

Buat beberapa test request:

Buat order
curl -X POST http://localhost:3000/orders \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user-123",
    "items": [
      {
        "productId": "prod-1",
        "quantity": 2,
        "price": 29.99
      }
    ]
  }'

Melihat Data Telemetry

Akses observability tool:

Di Jaeger, Anda akan melihat trace yang menunjukkan complete request flow:

plaintext
HTTP POST /orders (250ms)
  ├─ orders.create (240ms)
  │   ├─ orders.validateItems (50ms)
  │   └─ orders.processPayment (200ms)
  └─ HTTP response

Di Prometheus, query metric:

promql
# Request rate per endpoint
rate(http_requests_total[5m])
 
# Order creation rate
rate(orders_created_total[5m])
 
# 95th percentile order value
histogram_quantile(0.95, orders_value_bucket)

Kesalahan Umum dan Pitfall

1. Tidak Mengimpor Tracing Pertama

ts
// ❌ Salah - import lain duluan
import { NestFactory } from '@nestjs/core';
import './tracing';
 
// ✅ Benar - tracing pertama
import './tracing';
import { NestFactory } from '@nestjs/core';

Jika tracing tidak diimpor pertama, instrumentasi otomatis tidak akan bekerja karena modul sudah dimuat.

2. Membuat Terlalu Banyak Span

ts
// ❌ Salah - span untuk operasi trivial
async calculateTotal(items) {
  return this.tracing.withSpan('calculateTotal', async () => {
    return items.reduce((sum, item) => sum + item.price, 0);
  });
}
 
// ✅ Benar - span untuk operasi meaningful
async processPayment(amount) {
  return this.tracing.withSpan('processPayment', async () => {
    // External API call, layak di-trace
    return this.paymentGateway.charge(amount);
  });
}

Span memiliki overhead. Hanya buat span untuk operasi yang melintasi boundary (network, disk, external service) atau business-critical.

3. Tidak Menggunakan Semantic Convention

ts
// ❌ Salah - nama attribute custom
span.setAttribute('method', 'GET');
span.setAttribute('url', '/api/users');
 
// ✅ Benar - semantic convention
span.setAttribute('http.method', 'GET');
span.setAttribute('http.url', '/api/users');

Semantic convention memastikan telemetry Anda portable dan bekerja dengan dashboard dan query standar.

4. Lupa Mengakhiri Span

ts
// ❌ Salah - span tidak pernah berakhir
const span = tracer.startSpan('operation');
await doWork();
// Lupa memanggil span.end()
 
// ✅ Benar - gunakan withSpan helper
await this.tracing.withSpan('operation', async (span) => {
  await doWork();
  // Otomatis diakhiri
});

Span yang tidak diakhiri menyebabkan memory leak dan merusak trace. Selalu gunakan helper yang menjamin lifecycle management span.

5. Logging Data Sensitif

ts
// ❌ Salah - logging data sensitif
this.logger.log('User login', {
  email: user.email,
  password: user.password, // Jangan pernah log password!
  credit_card: user.creditCard,
});
 
// ✅ Benar - sanitasi data sensitif
this.logger.log('User login', {
  user_id: user.id,
  email_domain: user.email.split('@')[1],
});

Data telemetry sering disimpan untuk periode lama dan mungkin dapat diakses banyak orang. Jangan pernah log password, token, credit card, atau PII.

6. Tidak Sampling di Production

ts
// ❌ Salah - sampling semuanya di production
const sdk = new NodeSDK({
  // Tidak ada sampler dikonfigurasi = sample semuanya
});
 
// ✅ Benar - gunakan sampling yang sesuai
const sdk = new NodeSDK({
  sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10%
});

Sampling semuanya dalam sistem production high-traffic itu mahal dan tidak perlu. Gunakan ratio-based atau tail-based sampling.

7. Blocking pada Telemetry Export

ts
// ❌ Salah - export synchronous memblokir request
const sdk = new NodeSDK({
  spanProcessor: new SimpleSpanProcessor(exporter), // Synchronous
});
 
// ✅ Benar - batch export adalah async
const sdk = new NodeSDK({
  spanProcessor: new BatchSpanProcessor(exporter), // Async batching
});

Synchronous span processor memblokir aplikasi Anda. Selalu gunakan batch processor di production.

Best Practice

1. Gunakan Resource Attribute Secara Konsisten

Definisikan resource attribute sekali saat startup:

ts
const resource = new Resource({
  [SEMRESATTRS_SERVICE_NAME]: 'order-service',
  [SEMRESATTRS_SERVICE_VERSION]: process.env.APP_VERSION,
  [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
  [SEMRESATTRS_SERVICE_NAMESPACE]: 'ecommerce',
  'team.name': 'checkout',
  'region': process.env.AWS_REGION,
});

Metadata ini dilampirkan ke semua telemetry, memungkinkan filtering dan grouping di seluruh service.

2. Implementasi Health Check

Exclude health check endpoint dari tracing untuk mengurangi noise:

ts
getNodeAutoInstrumentations({
  '@opentelemetry/instrumentation-http': {
    ignoreIncomingRequestHook: (req) => {
      return req.url?.includes('/health') || 
             req.url?.includes('/metrics') ||
             req.url?.includes('/ready');
    },
  },
});

3. Korelasikan Log dengan Trace

Selalu sertakan trace context dalam log:

ts
const span = trace.getActiveSpan();
const spanContext = span?.spanContext();
 
logger.log('Processing order', {
  order_id: orderId,
  trace_id: spanContext?.traceId,
  span_id: spanContext?.spanId,
});

Ini memungkinkan jumping dari log ke trace dan sebaliknya di observability platform Anda.

4. Gunakan Cardinality dengan Bijak

ts
// ❌ High cardinality - membuat terlalu banyak metric series
this.counter.add(1, {
  user_id: userId, // Jutaan nilai unik
  order_id: orderId,
});
 
// ✅ Low cardinality - dimensi terbatas
this.counter.add(1, {
  user_tier: 'premium', // Nilai terbatas: free, premium, enterprise
  region: 'us-east-1',
});

High-cardinality attribute dalam metric membuat jutaan time series, membebani metric backend Anda. Gunakan high-cardinality data dalam trace dan log sebagai gantinya.

5. Implementasi Graceful Shutdown

Pastikan telemetry di-flush sebelum shutdown:

ts
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully');
  
  // Flush telemetry
  await sdk.shutdown();
  
  // Close server
  await app.close();
  
  process.exit(0);
});

Tanpa graceful shutdown, Anda akan kehilangan data telemetry dari beberapa detik terakhir sebelum terminasi.

6. Monitor Collector

Collector sendiri perlu monitoring. Export collector metric:

yaml
service:
  telemetry:
    metrics:
      address: 0.0.0.0:8888

Monitor:

  • otelcol_receiver_accepted_spans: Span yang diterima
  • otelcol_exporter_sent_spans: Span yang diekspor
  • otelcol_processor_batch_batch_send_size: Ukuran batch
  • otelcol_exporter_send_failed_spans: Kegagalan ekspor

7. Gunakan Context Propagation dengan Benar

Ketika membuat HTTP call ke service lain, propagate context:

ts
import { propagation, context } from '@opentelemetry/api';
 
async callExternalService(url: string) {
  const headers = {};
  
  // Inject trace context ke header
  propagation.inject(context.active(), headers);
  
  return axios.get(url, { headers });
}

Ini memastikan trace melintasi service boundary.

Kapan TIDAK Menggunakan OpenTelemetry

OpenTelemetry tidak selalu pilihan yang tepat. Pertimbangkan alternatif ketika:

1. Aplikasi Sederhana

Untuk aplikasi single-service dengan traffic rendah, OpenTelemetry mungkin berlebihan. Logging sederhana dan basic metric mungkin cukup.

Alternatif: Gunakan logger sederhana seperti Winston atau Pino dengan basic metric dari framework Anda.

2. Requirement Performance Ekstrem

OpenTelemetry menambah overhead. Untuk sistem ultra-low-latency (sub-millisecond), bahkan instrumentasi minimal mungkin tidak dapat diterima.

Alternatif: Gunakan sampling-based profiler atau custom lightweight instrumentation.

3. Legacy System Tanpa Vendor Support

Jika Anda terkunci dalam proprietary observability platform yang tidak mendukung OpenTelemetry, migrasi mungkin tidak worth it.

Alternatif: Tetap dengan vendor-specific agent sampai Anda bisa migrasi.

4. Serverless dengan Cold Start Sensitivity

Inisialisasi OpenTelemetry SDK menambah cold start time di serverless function. Untuk function latency-critical, ini penting.

Alternatif: Gunakan vendor-specific lightweight SDK (AWS X-Ray SDK, Google Cloud Trace) atau defer initialization.

5. Environment Cost-Constrained

Penyimpanan data telemetry itu mahal. Jika Anda memiliki budget ketat, full observability mungkin tidak feasible.

Alternatif: Gunakan aggressive sampling, fokus pada error saja, atau gunakan open-source backend (Jaeger, Prometheus, Grafana).

Pertimbangan Production

Performance Impact

OpenTelemetry memiliki overhead yang terukur:

  • CPU: Peningkatan 1-5% tergantung kedalaman instrumentasi
  • Memory: 50-200MB untuk SDK dan buffer
  • Latency: 0.1-1ms per operasi yang diinstrumentasi
  • Network: Tergantung sampling rate dan batch size

Strategi mitigasi:

  • Gunakan sampling yang sesuai (10-20% untuk high traffic)
  • Batch export (default: 512 span per batch)
  • Disable instrumentasi yang noisy (filesystem, DNS)
  • Gunakan collector untuk offload processing

Pertimbangan Keamanan

Data telemetry dapat berisi informasi sensitif:

  1. Sanitasi attribute: Hapus PII, token, password
  2. Gunakan TLS: Enkripsi data in transit ke collector/backend
  3. Batasi akses: Batasi siapa yang dapat melihat data telemetry
  4. Retention policy: Hapus data lama untuk comply dengan regulasi
  5. Audit log: Track siapa yang mengakses data telemetry

Cost Management

Data telemetry mahal dalam skala besar:

  • Trace: $0.10-$2.00 per juta span
  • Metric: $0.05-$0.30 per metric per bulan
  • Log: $0.50-$2.00 per GB

Optimasi cost:

  • Sample secara agresif (1-10% untuk sebagian besar traffic)
  • Gunakan tail-based sampling untuk menyimpan trace menarik
  • Set retention policy (7-30 hari untuk sebagian besar data)
  • Gunakan tiered storage (hot/warm/cold)
  • Filter data yang noisy di collector

High Availability

Buat observability stack Anda resilient:

  1. Collector redundancy: Jalankan multiple collector instance
  2. Queue buffering: Konfigurasi retry dan queue limit
  3. Fallback exporter: Ekspor ke multiple backend
  4. Circuit breaker: Cegah cascading failure

Kesimpulan

OpenTelemetry merepresentasikan perubahan fundamental dalam cara kita mendekati observability. Dengan menyediakan standar vendor-neutral, ini menghilangkan lock-in dan memungkinkan portabilitas sejati dari data telemetry. Ketiga pilar—metrics, logs, dan traces—bekerja bersama untuk memberikan visibilitas lengkap ke dalam sistem terdistribusi.

Implementasi NestJS yang kita bangun mendemonstrasikan cara memanfaatkan ketiga pilar dalam aplikasi real-world. Instrumentasi otomatis memberikan baseline coverage, sementara instrumentasi manual menambahkan konteks bisnis dan custom metric. OpenTelemetry Collector bertindak sebagai pipeline terpusat untuk memproses dan routing data telemetry.

Key takeaway:

  • OpenTelemetry adalah standar industri untuk cloud-native observability
  • Gunakan instrumentasi otomatis untuk quick win, manual untuk konteks bisnis
  • Collector memusatkan konfigurasi dan memungkinkan processing advanced
  • Ikuti semantic convention untuk telemetry yang portable dan konsisten
  • Seimbangkan kebutuhan observability dengan constraint performance dan cost
  • Implementasi sampling, security, dan compliance measure yang tepat

Mulai dengan instrumentasi otomatis untuk mendapatkan value langsung, kemudian secara bertahap tambahkan instrumentasi manual untuk critical path. Gunakan collector di production untuk fleksibilitas dan resilience. Yang terpenting, perlakukan observability sebagai first-class concern—instrumentasi sejak awal, instrumentasi sering, dan buat keputusan berdasarkan data.

Masa depan observability adalah open, standardized, dan vendor-neutral. OpenTelemetry adalah masa depan itu.


Related Posts