npm package: @glimt.dev/otel
Description: Glimt.dev wrapper around OpenTelemetry APIs
Author: Vercel Inc + Glimt.dev
Latest version: 2.1.1

# @glimt.dev/otel

OpenTelemetry for Node.js/Next.js/Edge. Works out of the box, highly configurable.

## Quick Start (3 lines)

```bash
npm install @glimt.dev/otel @opentelemetry/api @opentelemetry/api-logs
```

```ts
// instrumentation.ts (Next.js 13.4+)
import { registerOTel } from '@glimt.dev/otel'

export function register() {
  registerOTel({ serviceName: 'my-app' })
}
```

Done. Traces go to `https://ingest.glimt.dev/v1/traces` by default, logs to `/v1/logs`.

### Local Development

For local development with a collector on localhost, set the env var:

```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
```

### Custom Endpoint (programmatic)

To set the endpoint in code instead of env vars, use `exporterUrl`:

```ts
registerOTel({
  serviceName: 'my-app',
  exporterUrl: 'https://my-collector.example.com', // SDK appends /v1/traces and /v1/logs
  exporterHeaders: { 'x-api-key': 'your-key' },    // optional custom headers
})
```

---

## Full Example (all options)

```ts
import { registerOTel, OTLPHttpProtoTraceExporter, OTLPHttpJsonLogsExporter } from '@glimt.dev/otel'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'

export function register() {
  registerOTel({
    // Identity
    serviceName: 'my-app',
    attributes: {
      'app.version': process.env.npm_package_version,
    },

    // Release metadata (CRITICAL for source mapping)
    // Config takes precedence over env vars (COMMIT_SHA, COMMIT_REF, etc.)
    release: {
      commit: process.env.COMMIT_SHA,
      branch: process.env.COMMIT_REF,
      version: '1.0.0',
      environment: 'production',
    },

    // Glimt auth (or set GLIMT_ORG_ID + GLIMT_PUBLISHABLE_KEY env vars)
    organisationId: process.env.GLIMT_ORG_ID,
    publishableKey: process.env.GLIMT_PUBLISHABLE_KEY,

    // Extract request context
    attributesFromHeaders: {
      'enduser.id': 'x-user-id',
      'tenant.id': 'x-tenant-id',
      'client.ip': 'x-forwarded-for',
    },

    // Sampling (10% in production)
    traceSampler: 'parentbased_traceidratio',

    // Error handling
    captureUnhandledErrors: true,
    exitOnUnhandledException: true,
    flushTimeoutMs: 2000,

    // Custom endpoint (alternative to OTEL_EXPORTER_OTLP_ENDPOINT env var)
    exporterUrl: 'https://collector.example.com',
    exporterHeaders: { 'x-api-key': 'your-key' },

    // Debug logging (alternative to OTEL_LOG_LEVEL env var)
    logLevel: 'DEBUG',  // 'ALL' | 'VERBOSE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE'

    // Multi-backend fan-out (advanced - overrides exporterUrl)
    // spanProcessors: [
    //   new BatchSpanProcessor(new OTLPHttpProtoTraceExporter({
    //     url: 'https://collector-a.example.com/v1/traces',
    //   })),
    //   new BatchSpanProcessor(new OTLPHttpProtoTraceExporter({
    //     url: 'https://collector-b.example.com/v1/traces',
    //   })),
    // ],
  })
}
```

```bash
# Set sampling ratio
OTEL_TRACES_SAMPLER_ARG=0.1
```

---

## Environment Variables

Configure via env instead of code:

```bash
# Endpoints
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com  # Base URL
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://traces.example.com/v1/traces
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs.example.com/v1/logs

# Protocol
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf  # or http/json
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json

# Headers
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=secret,env=prod

# Sampling
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

# Service
OTEL_SERVICE_NAME=my-service
OTEL_SDK_DISABLED=true  # Kill switch

# Glimt auth
GLIMT_ORG_ID=org_xxx
GLIMT_PUBLISHABLE_KEY=pk_xxx
```

---

## Build Metadata (required for source mapping)

Set commit info for Glimt to map errors to code. Use either the `release` config option or environment variables:

**Via config (recommended):**
```ts
registerOTel({
  serviceName: 'my-app',
  release: {
    commit: process.env.COMMIT_SHA,
    branch: process.env.COMMIT_REF,
    version: '1.0.0',
    environment: 'production',
  }
})
```

**Via environment variables:**
```bash
COMMIT_SHA=$(git rev-parse HEAD)
COMMIT_REF=$(git rev-parse --abbrev-ref HEAD)
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
```

Also reads `VERCEL_GIT_COMMIT_SHA`, `NEXT_PUBLIC_COMMIT_SHA`, etc.

---

## Manual Spans & Logs

```ts
import { trace } from '@opentelemetry/api'
import { logs, SeverityNumber } from '@opentelemetry/api-logs'

// Spans
const tracer = trace.getTracer('my-component')
const span = tracer.startSpan('operation')
span.setAttribute('key', 'value')
span.end()

// Logs
const logger = logs.getLogger('my-component')
logger.emit({
  severityNumber: SeverityNumber.INFO,
  body: 'User signed up',
  attributes: { plan: 'pro' },
})
```

---

## Recording external-service failures on the active span

When integrating with third-party systems (GitHub, Slack, Stripe, Linear, email providers, etc),
prefer attaching the failure reason to the *current active span* so traces are self-explanatory.

```ts
import { recordExternalFailure, recordExternalFailureReason } from '@glimt.dev/otel'

// When you have an Error/exception:
try {
  await doExternalCall()
} catch (e) {
  recordExternalFailure({
    system: 'github',
    operation: 'listInstallationRepos',
    error: e,
    attributes: {
      'glimt.external.http.status_code': 404,
      'glimt.external.installation_id': 123,
    },
  })
  throw e
}

// When you only have a protocol-level error string (no exception thrown):
recordExternalFailureReason({
  system: 'slack',
  operation: 'chat.postMessage',
  reason: 'not_in_channel',
  attributes: { 'glimt.external.slack.channel': 'C123...' },
})
```

Notes:
- Both helpers set span status to **ERROR**, attach `glimt.external.*` attributes, and emit an `exception` event.
- `recordExternalFailureReason` **does not** synthesize an Error/stacktrace (to avoid misleading stacks).

---

## Defaults

| Feature | Default |
|---------|---------|
| Trace exporter | `http/protobuf` → `https://ingest.glimt.dev/v1/traces` |
| Log exporter | `http/json` → `https://ingest.glimt.dev/v1/logs` |
| Sampler | `always_on` |
| Propagators | W3C Trace Context + Baggage |
| Instrumentations | `fetch`, Node `http/https` |
| Error capture | `uncaughtException`, `unhandledRejection` → log + flush + exit |

---

## License

MIT