<!-- Markdown mirror of https://justscale.sh/docs/features/otel -->

# 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

```bash
npm install @justscale/feature-otel
```

## Basic Setup

Add the `otelFeature` to your application to enable automatic tracing:

src/app.tsTypeScript

```typescript
import JustScale, { defineApp } from '@justscale/core';
import type { AppEnv } from './env-contract';
import { otelFeature } from '@justscale/feature-otel';
import { MyController } from './controllers';

export default defineApp(import.meta, (env: AppEnv) =>
  JustScale()
    .add(env)
    .add(otelFeature({ serviceName: 'my-api' }))
    .add(MyController)
);
```

### 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

```typescript
import JustScale, { defineApp } from '@justscale/core';
import type { AppEnv } from './env-contract';
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 at module top-level so it's
// running before defineApp's composition starts emitting spans.
const sdk = new NodeSDK({
  serviceName: 'my-api',
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  }),
});
sdk.start();

// 2. Compose the app
export default defineApp(import.meta, (env: AppEnv) =>
  JustScale()
    .add(env)
    .add(otelFeature({ serviceName: 'my-api' }))
);
```

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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```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
```

## Next Steps

- Features Overview
- Error Handling
- Debugging
