Skip to main content
The @arizeai/phoenix-otel package provides OpenTelemetry-based instrumentation for automatic tracing of LLM applications in Node.js.

Installation

npm install @arizeai/phoenix-otel

Quick Start

import { register } from '@arizeai/phoenix-otel';

// Simplest setup - auto-discover Phoenix locally
const provider = register({
  projectName: 'my-llm-app'
});

// Your LLM code is now automatically traced!
import OpenAI from 'openai';

const client = new OpenAI();
const response = await client.chat.completions.create({
  model: 'gpt-4',
  messages: [{ role: 'user', content: 'Hello!' }]
});

register()

The main function for setting up Phoenix tracing.

Signature

import { register } from '@arizeai/phoenix-otel';
import type { RegisterParams } from '@arizeai/phoenix-otel';

const provider = register(params: RegisterParams): NodeTracerProvider

Parameters

projectName
string
The project name that spans will be associated with in Phoenix. This helps organize and filter traces in the Phoenix UI.Default: "default"
url
string
The URL to the Phoenix server. Can be postfixed with the tracing path. If not provided, the system will check the PHOENIX_COLLECTOR_URL environment variable.The URL will be automatically normalized to include the /v1/traces endpoint if not present.Default: http://localhost:6006
apiKey
string
The API key for authenticating with the Phoenix instance. If not provided, the system will check the PHOENIX_API_KEY environment variable.The API key will be automatically added to the Authorization header as a Bearer token.
headers
Record<string, string>
Additional headers to be included when communicating with the OTLP collector. These headers will be merged with any automatically generated headers (like Authorization).
batch
boolean
Whether to use batching for span processing.
  • true (default): Uses OpenInferenceBatchSpanProcessor for better performance in production
  • false: Uses OpenInferenceSimpleSpanProcessor for immediate span export (useful for debugging)
Batching is recommended for production environments as it reduces network overhead and improves performance.Default: true
instrumentations
Instrumentation[]
A list of OpenTelemetry instrumentations to automatically register.Note: This feature may only work with CommonJS projects. ESM projects may require manual instrumentation registration.
spanProcessors
SpanProcessor[]
Custom span processors to add to the tracer provider.Important: When provided, this will override the default span processor created from the url, apiKey, headers, and batch parameters.
global
boolean
Whether to register the tracer provider as the global provider.When true (default), the provider will be registered globally and can be accessed throughout the application. Set to false if you want to manage the provider lifecycle manually or use multiple providers.Default: true
diagLogLevel
DiagLogLevel
The diagnostic log level for the built-in DiagConsoleLogger.This controls the verbosity of OpenTelemetry’s internal logging. Omit this parameter to disable built-in logging entirely.Available levels: DiagLogLevel.DEBUG, DiagLogLevel.INFO, DiagLogLevel.WARN, DiagLogLevel.ERROR

Returns

provider
NodeTracerProvider
The configured NodeTracerProvider instance that can be used to create traces.

Examples

import { register } from '@arizeai/phoenix-otel';

const provider = register({
  projectName: 'my-application'
});

Advanced Components

For fine-grained control, you can use lower-level functions and components.

getDefaultSpanProcessor()

Creates a default span processor configured for Phoenix.
import { getDefaultSpanProcessor } from '@arizeai/phoenix-otel';

const processor = getDefaultSpanProcessor({
  url: 'https://app.phoenix.arize.com',
  apiKey: 'your-api-key',
  batch: true,
  headers: {
    'x-custom-header': 'value'
  }
});

ensureCollectorEndpoint()

Normalizes a Phoenix server URL to ensure it includes the correct OTLP traces endpoint.
import { ensureCollectorEndpoint } from '@arizeai/phoenix-otel';

const url = ensureCollectorEndpoint('https://app.phoenix.arize.com');
// Returns: 'https://app.phoenix.arize.com/v1/traces'

const urlWithPath = ensureCollectorEndpoint('https://app.phoenix.arize.com/v1/traces');
// Returns: 'https://app.phoenix.arize.com/v1/traces' (no change)

createNoOpProvider()

Creates a no-op tracer provider for testing or when tracing is disabled.
import { createNoOpProvider } from '@arizeai/phoenix-otel';

const provider = createNoOpProvider();
// All tracing calls become no-ops

Manual Tracing

Create custom traces and spans using the OpenTelemetry API:
import { register, trace } from '@arizeai/phoenix-otel';

const provider = register({
  projectName: 'my-app'
});

const tracer = trace.getTracer('my-instrumentation');

// Create a span
const span = tracer.startSpan('my-operation');

try {
  // Your code here
  span.setAttribute('user.id', '123');
  span.setAttribute('request.url', 'https://example.com');
  
  // Do work...
  
  span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
  span.setStatus({
    code: SpanStatusCode.ERROR,
    message: error.message
  });
  throw error;
} finally {
  span.end();
}

Using Context API

import { register, trace, context } from '@arizeai/phoenix-otel';

const provider = register({
  projectName: 'my-app'
});

const tracer = trace.getTracer('my-instrumentation');

// Run code within a span context
await tracer.startActiveSpan('my-operation', async (span) => {
  try {
    span.setAttribute('operation.type', 'llm-call');
    
    // Your async code here
    const result = await someAsyncOperation();
    
    span.setAttribute('result.length', result.length);
    span.setStatus({ code: SpanStatusCode.OK });
    
    return result;
  } catch (error) {
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: error.message
    });
    throw error;
  } finally {
    span.end();
  }
});

Framework Integration

Express.js

import express from 'express';
import { register } from '@arizeai/phoenix-otel';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';

// Set up tracing before importing your app
register({
  projectName: 'my-express-app',
  instrumentations: [
    new HttpInstrumentation(),
    new ExpressInstrumentation()
  ]
});

const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000);

Next.js

Create an instrumentation file:
// instrumentation.ts (or instrumentation.js)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { register: phoenixRegister } = await import('@arizeai/phoenix-otel');
    
    phoenixRegister({
      projectName: 'my-nextjs-app',
      batch: true
    });
  }
}
Then enable in next.config.js:
// next.config.js
module.exports = {
  experimental: {
    instrumentationHook: true,
  },
};

Vercel AI SDK

import { register } from '@arizeai/phoenix-otel';
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

// Set up Phoenix tracing
register({
  projectName: 'vercel-ai-app'
});

// Use Vercel AI SDK - automatically traced
const { text } = await generateText({
  model: openai('gpt-4'),
  prompt: 'What is Phoenix?'
});

Environment Variables

PHOENIX_COLLECTOR_URL
string
Phoenix server URL for trace collectionDefault: http://localhost:6006
PHOENIX_API_KEY
string
API key for authentication with Phoenix Cloud
NODE_ENV
string
Node environment (development, production, etc.)

Configuration Examples

Development

import { register, DiagLogLevel } from '@arizeai/phoenix-otel';

const provider = register({
  projectName: 'dev-project',
  url: 'http://localhost:6006',
  batch: false,
  diagLogLevel: DiagLogLevel.DEBUG
});

Production

import { register } from '@arizeai/phoenix-otel';

const provider = register({
  projectName: 'prod-app',
  url: process.env.PHOENIX_COLLECTOR_URL,
  apiKey: process.env.PHOENIX_API_KEY,
  batch: true,
  global: true
});

Testing

import { createNoOpProvider } from '@arizeai/phoenix-otel';

const provider = process.env.NODE_ENV === 'test'
  ? createNoOpProvider()
  : register({
      projectName: 'my-app',
      batch: true
    });

Troubleshooting

Traces Not Appearing

  1. Verify Phoenix is running: http://localhost:6006
  2. Check the URL is correct
  3. Enable debug logging:
    import { DiagLogLevel } from '@arizeai/phoenix-otel';
    
    register({
      projectName: 'my-app',
      diagLogLevel: DiagLogLevel.DEBUG
    });
    

ESM Issues

For ESM projects, you may need to use dynamic imports:
// instrumentation.ts
export async function register() {
  const { register: phoenixRegister } = await import('@arizeai/phoenix-otel');
  
  phoenixRegister({
    projectName: 'my-app'
  });
}

Performance Issues

  • Always use batch: true in production
  • Adjust batch size if needed via custom span processors
  • Consider sampling for high-volume applications

See Also

Build docs developers (and LLMs) love