# Node.js / TypeScript Integration Guide

> Learn how to integrate TraceKit with Node.js, Express, and NestJS. Automatic distributed tracing with dynamic logs for debugging production Node.js applications.

- Canonical: https://tracekit.dev/docs/languages/nodejs

Learn how to instrument your Node.js and TypeScript applications with TraceKit APM for zero-config distributed tracing.

**Let AI set up TraceKit for you** -- AI assistants can guide you through the entire setup process. [Try AI Setup](/docs/ai)

## Zero-Config Automatic Tracing

TraceKit Node APM package automatically traces HTTP requests, errors, and provides first-class TypeScript support. Just install, configure, and go!

## Prerequisites

- Node.js 16.x or higher
- Express 4.x/5.x or NestJS 10.x
- A TraceKit account ([create one free](https://app.tracekit.dev/register))
- A generated API key from the [API Keys](https://app.tracekit.dev/api-keys) page

## What Gets Traced Automatically

With TraceKit Node APM installed, these operations are traced automatically:

| Component | Span Type | Captured Attributes | Auto-Traced? |
|-----------|-----------|-------------------|--------------|
| Incoming HTTP | SERVER | route, method, status, duration, client_ip, user_agent | Yes |
| Outgoing HTTP | CLIENT | method, url, status_code, duration, peer.service | Yes |
| PostgreSQL | DB | db.system, db.statement, db.name, duration | Yes |
| MySQL | DB | db.system, db.statement, db.name, duration | Yes |
| MongoDB | DB | db.system (mongodb), db.operation, db.name, duration | Yes |
| Redis | DB | db.system (redis), db.statement, duration | Yes |
| LLM (OpenAI/Anthropic) | CLIENT | gen_ai.system, model, tokens, cost, finish_reason, latency | Yes |
| Exceptions | N/A | type, message, stack_trace, request_context | Yes |
| Custom Spans | Custom | user-defined attributes | Manual |

### Auto-Instrumented Libraries

The following libraries are automatically instrumented when detected. Import them after calling `tracekit.init()` for automatic span creation:

| Library | Span Type | Captured Attributes | Auto-Instrumented? |
|---------|-----------|-------------------|-------------------|
| pg (PostgreSQL) | DB | db.system, db.statement, db.name, duration | Yes |
| mysql / mysql2 | DB | db.system, db.statement, db.name, duration | Yes |
| mongodb | DB | db.system (mongodb), db.operation, db.name, duration | Yes |
| ioredis / redis | DB | db.system (redis), db.statement, duration | Yes |
| node:http / node:https | CLIENT | http.method, http.url, http.status_code, peer.service | Yes |
| undici (Fetch API) | CLIENT | http.method, http.url, http.status_code, peer.service | Yes |

Import database and HTTP client libraries **after** calling `tracekit.init()` to ensure automatic instrumentation is applied.

## Installation

Install the TraceKit Node APM package via npm or yarn:

```bash
npm install @tracekit/node-apm
# or
yarn add @tracekit/node-apm
```

## Express Setup

### JavaScript

```javascript
const express = require('express');
const tracekit = require('@tracekit/node-apm');

const app = express();

// Initialize TraceKit (before routes!)
tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY,
  serviceName: 'my-express-app',
  endpoint: 'https://app.tracekit.dev/v1/traces',
  enableCodeMonitoring: true, // Optional: Enable dynamic logs
});

// Add TraceKit middleware (before routes!)
app.use(tracekit.middleware());

// Your routes - automatically traced!
app.get('/api/users', (req, res) => {
  res.json({ users: ['alice', 'bob'] });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```

### TypeScript

```typescript
import express from 'express';
import * as tracekit from '@tracekit/node-apm';

const app = express();

// Initialize TraceKit (before routes!)
tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY!,
  serviceName: 'my-express-app',
  endpoint: 'https://app.tracekit.dev/v1/traces',
  enableCodeMonitoring: true, // Optional: Enable dynamic logs
});

// Add TraceKit middleware (before routes!)
app.use(tracekit.middleware());

// Your routes - automatically traced!
app.get('/api/users', (req, res) => {
  res.json({ users: ['alice', 'bob'] });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```

**Verify It Works** -- Start your application and make a few requests. Then open the [Traces page](https://app.tracekit.dev/traces) in your TraceKit dashboard. You should see:

- SERVER spans for each incoming HTTP request with route and status
- CLIENT spans for outgoing HTTP calls (auto-traced for http/https, fetch, axios)
- DB spans for database queries (auto-traced for PostgreSQL, MySQL, MongoDB, Redis)

Traces typically appear within 30 seconds. If you don't see them, check the [Troubleshooting](#troubleshooting) section.

## NestJS Setup

### 1. Import TracekitModule

Add the TracekitModule to your `app.module.ts`:

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { TracekitModule } from '@tracekit/node-apm/nestjs';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    TracekitModule.forRoot({
      apiKey: process.env.TRACEKIT_API_KEY!,
      serviceName: 'my-nestjs-app',
      endpoint: 'https://app.tracekit.dev/v1/traces',
      enableCodeMonitoring: true, // Optional: Enable dynamic logs
    }),
    UsersModule,
  ],
})
export class AppModule {}
```

### 2. Async Configuration

For environment-based configuration using ConfigModule:

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TracekitModule } from '@tracekit/node-apm/nestjs';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TracekitModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        apiKey: config.get('TRACEKIT_API_KEY')!,
        serviceName: config.get('APP_NAME', 'my-app'),
        endpoint: config.get('TRACEKIT_ENDPOINT'),
        enabled: config.get('NODE_ENV') === 'production',
        enableCodeMonitoring: true, // Optional: Enable dynamic logs
      }),
    }),
  ],
})
export class AppModule {}
```

**Automatic Interceptor** -- The TracekitModule automatically registers a global interceptor that traces all HTTP requests. No additional middleware needed!

### Middleware and Interceptor API

| Function / Class | Parameters | Returns | Description |
|-----------------|------------|---------|-------------|
| `createExpressMiddleware()` | client: TracekitClient, snapshot?: boolean | RequestHandler | Creates Express middleware that automatically traces all incoming HTTP requests. |
| `getCurrentSpan()` | req: Request | Span or undefined | Retrieves the current active span from an Express request. |
| `getClientIp()` | req: Request | string or undefined | Extracts client IP from request headers (X-Forwarded-For, X-Real-IP) with proxy support. |
| `TracekitInterceptor` | NestJS class | NestInterceptor | NestJS interceptor for automatic span creation on all controller methods. |

## Code Monitoring (Dynamic Logs)

**Production-Safe Dynamic Logs** -- Capture variable state, stack traces, and request context at any point in your code without redeployment. Perfect for debugging production issues! [Full Code Monitoring Documentation](/docs/code-monitoring)

### Setup

Enable code monitoring when initializing TraceKit:

```typescript
import * as tracekit from '@tracekit/node-apm';

const client = tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY!,
  serviceName: 'my-app',
  enableCodeMonitoring: true,  // Enable dynamic logs
});
```

### Usage: Add Debug Points

Use `client.captureSnapshot()` to capture variable state:

```typescript
app.post('/checkout', async (req, res) => {
  const { userId, amount } = req.body;

  // Capture snapshot at this point
  await client.captureSnapshot('checkout-validation', {
    userId,
    amount,
    timestamp: new Date().toISOString(),
  });

  // Your business logic...
  const result = await processPayment(amount, userId);

  // Another checkpoint
  await client.captureSnapshot('payment-complete', {
    userId,
    paymentId: result.paymentId,
    success: result.success,
  });

  res.json(result);
});
```

### Features

- **Auto-Registration** -- Breakpoints automatically created on first call
- **Variable Capture** -- Deep inspection of objects, arrays, and primitives
- **Stack Traces** -- Full call stack with file and line numbers
- **Request Context** -- HTTP method, route, headers, and query params

### Production Safety

v6.0 adds multiple layers of protection so code monitoring is safe for production workloads:

- **PII Scrubbing** -- 13 built-in sensitive-data patterns enabled by default. Matched values are replaced with typed `[REDACTED:type]` markers before data leaves the process.
- **Crash Isolation** -- Every capture entry point is wrapped in try/catch so a failure in the SDK never propagates to your application code.
- **Circuit Breaker** -- After 3 consecutive failures within 60 seconds the SDK enters a 5-minute cooldown, then auto-recovers. Fully configurable thresholds.
- **Remote Kill Switch** -- Toggle code monitoring on or off from the TraceKit dashboard. The change propagates to all connected SDK instances via SSE in real time.

**Real-Time Updates via SSE** -- The Node SDK opens a lightweight Server-Sent Events connection to the TraceKit server on startup. Breakpoint enables/disables, kill-switch toggles, and configuration changes are pushed instantly -- no polling delay.

### Exception Handling

Exceptions are automatically captured with full stack traces for code discovery:

```typescript
// Exceptions in spans are automatically captured
app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id);
    res.json(user);
  } catch (error) {
    // TraceKit automatically captures exception with stack trace
    next(error);
  }
});

// Express error handler
app.use((err, req, res, next) => {
  client.recordException(null, err);
  res.status(500).json({ error: 'Internal Server Error' });
});
```

### Code Monitoring API

| Method / Class | Parameters | Returns | Description |
|---------------|------------|---------|-------------|
| `captureSnapshot()` | label: string, vars: object | Promise\<void\> | Captures a snapshot of variable state at the call site. Auto-registers a breakpoint on first call. Rate-limited to 1 capture/sec per label. |
| `getSnapshotClient()` | none | SnapshotClient or undefined | Returns the snapshot client if code monitoring is enabled, or undefined if disabled. |
| `SnapshotClient` | class | -- | Manages breakpoint polling and snapshot uploads. Created automatically when `enableCodeMonitoring: true`. |
| `CaptureConfig` | interface | -- | Configuration for capture behaviour. Fields: `enablePiiScrubbing` (boolean), `piiPatterns` (PIIPatternEntry[]), `circuitBreaker` (CircuitBreakerConfig). |
| `CircuitBreakerConfig` | interface | -- | Configures circuit breaker thresholds. Fields: `maxFailures` (default 3), `windowMs` (default 60000), `cooldownMs` (default 300000). |
| `PIIPatternEntry` | interface | -- | Defines a sensitive-data pattern. Fields: `name` (string), `pattern` (RegExp), `redactedType` (string). |

### End-to-End Workflow

1. **Enable Code Monitoring** -- Set `enableCodeMonitoring: true` in your init config.
2. **Add Capture Points** -- Place `captureSnapshot` calls at code paths you want to debug. Note: `captureSnapshot` returns a Promise -- use `await`.
3. **Deploy and Verify Traces** -- Deploy your application and confirm traces are flowing at [/traces](https://app.tracekit.dev/traces).
4. **Navigate to Code Monitoring** -- Go to [/snapshots](https://app.tracekit.dev/snapshots) and click the "Browse Code" tab.
5. **Set Breakpoints** -- Breakpoints are auto-registered on the first `captureSnapshot` call. You can also manually add breakpoints via the UI.
6. **Trigger a Capture** -- Make a request that hits a code path with `captureSnapshot`.
7. **View Snapshot** -- Go to [/snapshots](https://app.tracekit.dev/snapshots) and click the captured snapshot to view variables, call stack, request context, and trace link.

### v25: Enhanced Code Monitoring

#### Log-Point Mode

When a breakpoint is set to **log-point mode** in the dashboard, the SDK captures only the specified expressions -- no local variables, stack traces, or request context. This dramatically reduces overhead for high-frequency code paths.

No code changes needed. The SDK reads the breakpoint's `mode` field from the server and adjusts capture behavior automatically.

#### SDK-Side Condition Evaluation

Breakpoint conditions (e.g., `status > 200`) are now evaluated **locally in the SDK** without a server round-trip. The server classifies each condition as `sdk-evaluable` or `server-only` at creation time. Simple expressions (comparisons, boolean logic, property access) are evaluated in-process.

No code changes needed. The SDK checks the `condition_eval` field and evaluates locally when possible.

#### Per-Breakpoint Capture Limits

New breakpoints default to:
- **max_depth: 5** -- variables nested deeper than 5 levels are truncated
- **max_payload_bytes: 128KB** -- snapshots larger than 128KB are truncated

These limits are configurable per breakpoint in the dashboard. The SDK respects per-breakpoint overrides automatically.

#### Auto-Expire

New breakpoints **auto-expire after 48 hours of inactivity**. Each capture resets the timer. Pin a breakpoint in the dashboard to prevent auto-expiry.

Existing breakpoints are not affected -- auto-expire only applies to breakpoints created after the v25 update.

## LLM Instrumentation

TraceKit automatically instruments OpenAI and Anthropic SDK calls when detected. LLM calls appear as spans with [OTel GenAI semantic convention](https://opentelemetry.io/docs/specs/semconv/gen-ai/) attributes.

**Zero-config auto-instrumentation** -- If `openai` or `@anthropic-ai/sdk` is installed, TraceKit patches them automatically at init. No code changes required.

### Captured Attributes

| Attribute | Description |
|-----------|-------------|
| `gen_ai.system` | Provider name (openai, anthropic) |
| `gen_ai.request.model` | Model name (gpt-4o, claude-sonnet-4-20250514, etc.) |
| `gen_ai.usage.input_tokens` | Prompt token count |
| `gen_ai.usage.output_tokens` | Completion token count |
| `gen_ai.response.finish_reasons` | Finish reason (stop, end_turn, tool_calls) |

### Configuration

```javascript
const client = init({
  apiKey: process.env.TRACEKIT_API_KEY,
  serviceName: 'my-service',
  endpoint: 'https://app.tracekit.dev/v1/traces',

  // LLM instrumentation (all enabled by default)
  instrumentLLM: {
    enabled: true,        // Master toggle
    openai: true,         // OpenAI instrumentation
    anthropic: true,      // Anthropic instrumentation
    captureContent: false, // Capture prompts/completions (off by default)
  },
});
```

### Environment Variable Override

Use `TRACEKIT_LLM_CAPTURE_CONTENT=true` to enable prompt/completion capture without code changes. Useful for enabling in staging but not production.

### Streaming Support

Streaming responses produce a single span that covers the entire stream. Token counts are accumulated from the final chunk. No special configuration needed.

### LLM Dashboard

View LLM cost, token usage, and latency metrics on the dedicated **LLM Observability** dashboard at `/ai/llm` in your TraceKit instance.

## Local UI (Development Mode)

**Debug Locally Without an Account** -- The Local UI lets you see traces in real-time on your local machine at `http://localhost:9999`. Perfect for development -- no account signup or API key required!

### Quick Start

**1. Install the Local UI:**

```bash
npm install -g @tracekit/local-ui
```

**2. Start the Local UI:**

```bash
tracekit-local
```

**3. Run your app in development mode:**

```bash
NODE_ENV=development node app.js
```

**4. Open your browser:**

```
http://localhost:9999
```

**Automatic Detection** -- The SDK automatically detects when Local UI is running and sends traces to both your local instance and the cloud (if you have an API key configured). No code changes needed!

### How It Works

- **Auto-Detection** -- SDK checks for Local UI at `localhost:9999` on startup
- **Real-Time Updates** -- See traces instantly with WebSocket live updates
- **Development Only** -- Only activates when `NODE_ENV=development`
- **Works Offline** -- No internet connection required -- everything runs locally

### Benefits

- **Zero friction onboarding** -- Try TraceKit without creating an account
- **Faster debugging** -- No context switching to web dashboard
- **Privacy first** -- Traces never leave your machine
- **Perfect for demos** -- Show TraceKit without cloud dependency

**Troubleshooting Local UI** -- If traces aren't appearing, check:

- Local UI is running (`curl http://localhost:9999/api/health`)
- `NODE_ENV=development` is set
- SDK version is v0.3.2 or higher
- Check console for "Local UI detected" message

## Service Discovery

TraceKit automatically instruments outgoing HTTP calls to create service dependency graphs. When your service makes an HTTP request to another service, TraceKit creates CLIENT spans and injects trace context headers.

### Supported HTTP Clients

- **http/https** -- Node.js built-in modules
- **fetch** -- Node 18+ native fetch API
- **axios** -- Works via http module
- **node-fetch, got, superagent** -- Work via http module

### Custom Service Name Mappings

For local development or when service names can't be inferred from hostnames, configure service name mappings:

```typescript
import * as tracekit from '@tracekit/node-apm';

tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY,
  serviceName: 'my-service',
  // Map localhost URLs to actual service names
  serviceNameMappings: {
    'localhost:8082': 'payment-service',
    'localhost:8083': 'user-service',
    'localhost:8084': 'inventory-service',
  },
});

// Now requests to localhost:8082 will show as "payment-service"
const response = await fetch('http://localhost:8082/charge');
// -> Creates CLIENT span with peer.service = "payment-service"
```

### Service Name Detection

TraceKit intelligently extracts service names from URLs:

| URL | Extracted Service Name |
|-----|----------------------|
| `http://payment-service:3000` | payment-service |
| `http://payment.internal` | payment |
| `http://payment.svc.cluster.local` | payment |
| `https://api.example.com` | api.example.com |

**Viewing Service Dependencies** -- Visit your TraceKit dashboard to see the [Service Map](https://app.tracekit.dev/services) -- a visual graph showing which services call which, with health metrics and latency data.

## Manual Instrumentation (Optional)

### Express - Manual Spans

```typescript
import { getClient } from '@tracekit/node-apm';

app.post('/api/process', async (req, res) => {
  const client = getClient();

  const span = client.startSpan('process-data', null, {
    'user.id': req.user?.id,
    'data.size': req.body.items.length,
  });

  try {
    const result = await processData(req.body);

    client.endSpan(span, {
      'result.count': result.length,
    });

    res.json(result);
  } catch (error) {
    client.recordException(span, error as Error);
    client.endSpan(span, {}, 'ERROR');
    throw error;
  }
});
```

### NestJS - Manual Spans

```typescript
import { Injectable, Inject } from '@nestjs/common';
import { TracekitClient } from '@tracekit/node-apm/nestjs';

@Injectable()
export class DataService {
  constructor(
    @Inject('TRACEKIT_CLIENT') private tracekit: TracekitClient
  ) {}

  async processLargeDataset(data: any[]) {
    const span = this.tracekit.startSpan('process-dataset', null, {
      'dataset.size': data.length,
    });

    try {
      const results = [];
      for (const item of data) {
        const result = await this.processItem(item);
        results.push(result);
      }

      this.tracekit.endSpan(span, {
        'results.count': results.length,
      });

      return results;
    } catch (error) {
      this.tracekit.recordException(span, error as Error);
      this.tracekit.endSpan(span, {}, 'ERROR');
      throw error;
    }
  }
}
```

### TracekitClient Tracing API

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `startTrace()` | op: string, attrs?: object | Span | Creates a new root trace for top-level operations not tied to an incoming HTTP request. |
| `startServerSpan()` | op: string, attrs?: object | Span | Creates a SERVER span for incoming requests. Extracts trace context from request headers. |
| `startSpan()` | op: string, parent?: Span, attrs?: object | Span | Creates a child span. If parent is null, automatically links to the current active span. |
| `endSpan()` | span: Span, attrs?: object, status?: string | void | Ends a span, recording its duration. Optionally sets final attributes and status code. |
| `addEvent()` | span: Span, name: string, attrs?: object | void | Records a timestamped event on a span. |
| `recordException()` | span: Span, error: Error | void | Records an exception on a span with message, type, and stack trace. Sets span status to ERROR. |

### Lifecycle Methods

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `flush()` | none | Promise\<void\> | Forces an immediate flush of all pending spans to the backend. |
| `shutdown()` | none | Promise\<void\> | Gracefully shuts down the SDK: flushes remaining data, releases resources, stops background tasks. |
| `isEnabled()` | none | boolean | Returns whether the SDK is currently enabled and actively tracing. |
| `shouldSample()` | none | boolean | Returns whether the current request should be sampled based on the configured sample rate. |
| `getTracer()` | none | Tracer | Returns the underlying OpenTelemetry Tracer instance for advanced use cases. |

### Complete Tracing Lifecycle Example

```typescript
const client = getClient();

// Start a root trace for a background job or non-HTTP operation
const span = client.startTrace('process-order', { 'order.id': orderId });

try {
  // Create a child span for a sub-operation
  const childSpan = client.startSpan('validate-payment', span);
  client.addEvent(childSpan, 'payment-validated', { amount: total });
  client.endSpan(childSpan);

  // End the root span with final attributes
  client.endSpan(span, { 'order.status': 'completed' });
} catch (err) {
  // Record the exception and mark the span as errored
  client.recordException(span, err);
  client.endSpan(span, {}, 'ERROR');
} finally {
  // Ensure all spans are exported before continuing
  await client.flush();
}
```

## Configuration Options

```typescript
tracekit.init({
  // Required: Your TraceKit API key
  apiKey: process.env.TRACEKIT_API_KEY,

  // Optional: Service name (default: 'node-app')
  serviceName: 'my-service',

  // Optional: TraceKit endpoint
  endpoint: 'https://app.tracekit.dev/v1/traces',

  // Optional: Enable/disable tracing (default: true)
  enabled: process.env.NODE_ENV !== 'development',

  // Optional: Sample rate 0.0-1.0 (default: 1.0 = 100%)
  sampleRate: 0.5, // Trace 50% of requests

  // Optional: Enable code monitoring / dynamic logs (default: false)
  enableCodeMonitoring: true,
});
```

## Environment Variables

Best practice: Store configuration in environment variables:

```bash
# .env
TRACEKIT_API_KEY=ctxio_your_generated_api_key_here
TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
TRACEKIT_SERVICE_NAME=my-nodejs-app
TRACEKIT_CODE_MONITORING_ENABLED=true
NODE_ENV=production
```

**Important Security Note** -- Never commit your API key to version control. Use environment variables and keep your `.env` file out of git.

### TracekitConfig Interface

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `apiKey` | string | Yes | -- | Your TraceKit API key for authenticating with the collector |
| `endpoint` | string | No | "app.tracekit.dev" | TraceKit collector endpoint URL |
| `tracesPath` | string | No | "/v1/traces" | HTTP path for trace data export |
| `metricsPath` | string | No | "/v1/metrics" | HTTP path for metrics data export |
| `serviceName` | string | No | "node-app" | Name of your service shown in the trace dashboard and service map |
| `enabled` | boolean | No | true | Enable or disable tracing globally |
| `sampleRate` | number | No | 1.0 | Trace sampling rate from 0.0 to 1.0 (1.0 = trace 100%) |
| `enableCodeMonitoring` | boolean | No | false | Enable live code debugging and snapshot capture |
| `autoInstrumentHttpClient` | boolean | No | true | Auto-instrument outgoing HTTP calls via http/https and fetch |
| `serviceNameMappings` | Record\<string, string\> | No | {} | Map host:port patterns to friendly service names for the service map |

### Configuration Reference

| Option | Type | Default | Env Variable | Description |
|--------|------|---------|-------------|-------------|
| `apiKey` | string | *required* | TRACEKIT_API_KEY | Your TraceKit API key for authentication |
| `endpoint` | string | "app.tracekit.dev" | TRACEKIT_ENDPOINT | TraceKit collector endpoint URL |
| `tracesPath` | string | "/v1/traces" | TRACEKIT_TRACES_PATH | HTTP path for trace data export |
| `metricsPath` | string | "/v1/metrics" | TRACEKIT_METRICS_PATH | HTTP path for metrics data export |
| `serviceName` | string | "node-app" | TRACEKIT_SERVICE_NAME | Name of your service in the trace dashboard |
| `enabled` | boolean | true | TRACEKIT_ENABLED | Enable or disable tracing globally |
| `sampleRate` | number | 1.0 | TRACEKIT_SAMPLE_RATE | Trace sampling rate (0.0 to 1.0) |
| `enableCodeMonitoring` | boolean | false | TRACEKIT_CODE_MONITORING_ENABLED | Enable live code debugging / snapshot capture |
| `autoInstrumentHttpClient` | boolean | true | -- | Auto-instrument outgoing HTTP calls |
| `serviceNameMappings` | Record\<string, string\> | {} | -- | Map host:port to service names for service discovery |

The Node.js SDK does not auto-read environment variables. Read them with `process.env` and pass to `init()`.

## Complete Example

Here's a complete Express + TypeScript example:

```typescript
import express, { Request, Response } from 'express';
import * as tracekit from '@tracekit/node-apm';

const app = express();
app.use(express.json());

// Initialize TraceKit
tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY!,
  serviceName: 'express-api',
  endpoint: 'https://app.tracekit.dev/v1/traces',
  enabled: process.env.NODE_ENV === 'production',
  enableCodeMonitoring: true, // Optional: Enable dynamic logs
});

// Add middleware
app.use(tracekit.middleware());

interface User {
  id: string;
  name: string;
  email: string;
}

// Routes - automatically traced!
app.get('/api/users', (req: Request, res: Response) => {
  const users: User[] = [
    { id: '1', name: 'Alice', email: 'alice@example.com' },
    { id: '2', name: 'Bob', email: 'bob@example.com' },
  ];
  res.json(users);
});

// Manual span example
app.post('/api/users', async (req: Request, res: Response) => {
  const client = tracekit.getClient();

  const span = client.startSpan('create-user', null, {
    'user.email': req.body.email,
  });

  try {
    // Simulate user creation
    const user: User = {
      id: Date.now().toString(),
      name: req.body.name,
      email: req.body.email,
    };

    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100));

    client.endSpan(span, {
      'user.id': user.id,
    });

    res.status(201).json(user);
  } catch (error) {
    client.recordException(span, error as Error);
    client.endSpan(span, {}, 'ERROR');
    res.status(500).json({ error: 'Failed to create user' });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log('Server running on port ' + PORT);
});
```

## Custom Metrics

Track custom metrics like request counts, queue sizes, and response times using the TraceKit metrics API.

### Counter

Track monotonically increasing values (requests, events):

```typescript
import * as tracekit from '@tracekit/node-apm';

const client = tracekit.init({
  apiKey: process.env.TRACEKIT_API_KEY,
  serviceName: 'my-service',
});

// Create a counter with optional tags
const counter = client.counter('http.requests.total', { service: 'api' });

// Increment by 1
counter.inc();

// Add a specific value
counter.add(5);
```

### Gauge

Track values that can go up or down (queue size, connections):

```typescript
// Create a gauge
const gauge = client.gauge('http.connections.active');

// Set to specific value
gauge.set(42);

// Increment/decrement
gauge.inc();
gauge.dec();
```

### Histogram

Track value distributions (latencies, sizes):

```typescript
// Create a histogram with tags
const histogram = client.histogram('http.request.duration', { unit: 'ms' });

// Record values
histogram.record(45.2);
histogram.record(123.5);
```

[View Custom Metrics Guide](/docs/metrics)

## Troubleshooting

### Traces not appearing?

**Cause:** The SDK is not initialized or is initialized after route handlers are registered.

**Fix:**
- Call `tracekit.init()` BEFORE registering any Express/Fastify routes or middleware
- Verify API key with `console.log(process.env.TRACEKIT_API_KEY)`
- Check that `enabled` is not set to `false`
- Look for errors in the console output

### Connection refused errors?

**Cause:** The TraceKit endpoint is unreachable from your Node.js process.

**Fix:**
- Test with `curl -X POST https://app.tracekit.dev/v1/traces` (expect 401)
- Check proxy/firewall settings
- Verify the endpoint URL does not have a trailing slash

### Code monitoring not working?

**Cause:** Code monitoring is disabled by default and requires explicit enablement.

**Fix:**
- Set `enableCodeMonitoring: true` in the init options
- Add `captureSnapshot()` calls where you want to inspect variables
- Check the dashboard for active breakpoints
- Allow up to 30 seconds for the SDK to poll for new breakpoints

### Authentication errors (401/403)?

**Cause:** API key is wrong, has extra whitespace, or was revoked.

**Fix:**
- Log the key to verify: `console.log(JSON.stringify(apiKey))` (catches invisible characters)
- Regenerate in dashboard if needed
- Ensure the correct key is used per environment

### Partial or missing spans?

**Cause:** Auto-instrumentation is not capturing all libraries, or async context is lost.

**Fix:**
- Verify `autoInstrumentHttpClient: true` (default) for outgoing HTTP
- For database spans, ensure the database library is imported AFTER `tracekit.init()`
- Check that async/await is used consistently (callbacks can lose trace context)

## Migrating from OpenTelemetry

If you are currently using OpenTelemetry directly, TraceKit wraps it with a simpler API. Replace your OTel setup with `tracekit.init()` and remove manual exporter configuration. TraceKit handles the OTel pipeline internally.

## Performance Overhead

TraceKit is built on OpenTelemetry's efficient batch processing pipeline. The SDK adds minimal overhead to your Node.js application.

| Metric | Impact | Details |
|--------|--------|---------|
| Request Tracing | < 1ms per request | Spans are batched and exported asynchronously |
| Code Monitoring (Idle) | Zero overhead | No performance impact when no active breakpoints |
| Code Monitoring (Capture) | < 5ms per snapshot | Includes variable serialization and security scanning |
| Memory Footprint | ~10-20 MB | SDK runtime and span buffer |
| SDK Initialization | < 200ms one-time | One-time cost at application startup |

Performance characteristics are typical for production workloads and may vary with application complexity, request volume, and number of instrumented libraries. Use [sampling configuration](#environment-variables) to reduce overhead in high-traffic services.

## Best Practices

- **DO: Initialize TraceKit BEFORE requiring routes/modules** -- The `tracekit.init()` call must be the first line in your entry file so auto-instrumentation patches `http`, `express`, and database drivers before they are loaded.
- **DO: Use environment variables for configuration** -- Keep API keys, endpoints, and service names in environment variables for easy per-environment configuration.
- **DO: Add custom spans for critical business logic** -- While auto-instrumentation covers HTTP and database calls, add manual spans for important operations like payment processing or order fulfillment.
- **DON'T: Initialize TraceKit inside a route handler** -- This will miss all requests that come in before that route is hit.
- **DON'T: Forget to call `flush()` before process exit** -- In serverless or short-lived processes, call `client.flush()` to ensure all spans are exported.

## Next Steps

- Explore your traces on the [Traces page](https://app.tracekit.dev/traces) to identify performance bottlenecks
- Set up [alert rules](/docs/alerts) to get notified when issues occur
- Add custom spans for specific operations you want to measure
- Configure sampling rates for high-traffic applications

**Pro Tip** -- Use the `getClient()` function to access the TraceKit client anywhere in your application for creating custom spans!
