OpenTelemetry

Distributed tracing and observability with OpenTelemetry

The @justscale/feature-otel package provides automatic distributed tracing for JustScale applications using OpenTelemetry. It creates spans for HTTP requests, records errors, and propagates trace context across services.

Installation

Bash
npm install @justscale/feature-otel

Basic Setup

Add the otelFeature to your application to enable automatic tracing:

src/app.tsTypeScript
import { createCluster } from '@justscale/cluster';
import { otelFeature } from '@justscale/feature-otel';

const cluster = createCluster({
  features: [
    otelFeature({ serviceName: 'my-api' }),
  ],
  controllers: [MyController],
});

await cluster.serve({ http: 3000 });

What Gets Traced

  • Automatic span creation for every HTTP request
  • Error recording and exception tracking
  • Log-to-span-event conversion
  • Distributed tracing via W3C Trace Context headers

Production Setup with OTLP

For production, configure the OpenTelemetry SDK to export traces to your observability backend:

src/app.tsTypeScript
import { createCluster } from '@justscale/cluster';
import { otelFeature } from '@justscale/feature-otel';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

// 1. Initialize the OpenTelemetry SDK
const sdk = new NodeSDK({
  serviceName: 'my-api',
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  }),
});
sdk.start();

// 2. Create app with otel feature
const cluster = createCluster({
  features: [otelFeature({ serviceName: 'my-api' })],
  controllers: [],
});

await cluster.serve({ http: 3000 });

This sends traces to any OTLP-compatible backend like Jaeger, Tempo, Honeycomb, or Datadog.

Manual Span Creation

Create custom spans for specific operations within your services:

src/services/order-service.tsTypeScript
import { withSpan, withSpanSync } from '@justscale/feature-otel';

// Async operations
async function processOrder(orderId: string) {
  return withSpan('process-order', { orderId }, async () => {
    await validateOrder(orderId);
    await chargePayment(orderId);
    await sendConfirmation(orderId);
  });
}

// Sync operations
function calculateTax(amount: number) {
  return withSpanSync('calculate-tax', { amount }, () => {
    return amount * 0.1;
  });
}

async function validateOrder(orderId: string) { /* ... */ }
async function chargePayment(orderId: string) { /* ... */ }
async function sendConfirmation(orderId: string) { /* ... */ }

Accessing the Active Span

Get the current span to add attributes or events:

src/services/payment-service.tsTypeScript
import { getActiveSpan } from '@justscale/feature-otel';

function processPayment(amount: number) {
  const span = getActiveSpan();

  if (span) {
    span.setAttribute('payment.amount', amount);
    span.addEvent('payment.started');
  }

  // ... process payment logic

  span?.addEvent('payment.completed');
}

Distributed Tracing

Propagate trace context when calling external services:

src/services/api-client.tsTypeScript
import { createTracedFetch, getTraceHeaders } from '@justscale/feature-otel';

// Option 1: Use createTracedFetch for automatic propagation
const tracedFetch = createTracedFetch();
const response = await tracedFetch('https://api.example.com/data');

// Option 2: Manual header injection
const headers = getTraceHeaders();
const manualResponse = await fetch('https://api.example.com/data', {
  headers: {
    ...headers,
    'Content-Type': 'application/json',
  },
});

Configuration Options

config-interface.tsTypeScript
interface OtelFeatureConfig {
  // Service name for spans (required)
  serviceName: string;

  // Whether to record exceptions in spans (default: true)
  recordExceptions?: boolean;

  // Whether to record logs as span events (default: true)
  recordLogs?: boolean;

  // Attributes to add to all spans
  defaultAttributes?: Record<string, string | number | boolean>;
}
src/app.tsTypeScript
import { otelFeature } from '@justscale/feature-otel';

const feature = otelFeature({
  serviceName: 'my-api',
  recordExceptions: true,
  recordLogs: true,
  defaultAttributes: {
    'deployment.environment': 'production',
    'service.version': '1.0.0',
  },
});

Viewing Traces

Use an OTLP-compatible backend to view your traces:

  • Jaeger - Open source, self-hosted
  • Grafana Tempo - Scalable trace storage
  • Honeycomb - SaaS observability platform
  • Datadog - Full observability suite

Local Development with Jaeger

Bash
# Run Jaeger with Docker
docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4318:4318 \
  jaegertracing/all-in-one:latest

# View traces at http://localhost:16686