otel-apollo-plugin
v0.0.1
Published
OpenTelemetry integration for Apollo Server - replaces the deprecated `@alphasense/jaeger-apollo-plugin`.
Readme
@alphasense/otel-apollo-plugin
OpenTelemetry integration for Apollo Server - replaces the deprecated @alphasense/jaeger-apollo-plugin.
This package provides a modern, standards-based approach to distributed tracing in Apollo Server using OpenTelemetry (OTEL), replacing the deprecated Jaeger/OpenTracing implementation.
Why Migrate?
- Deprecated Dependencies:
jaeger-client-nodeis officially deprecated and unmaintained - Industry Standard: OpenTelemetry is a CNCF graduated project, the future of observability
- Better Performance: OTLP protocol is more efficient than Jaeger's HTTP/Thrift
- Vendor Flexibility: Easy to switch backends (Datadog, Honeycomb, AWS X-Ray, etc.)
- Active Maintenance: Regular releases and security updates
Installation
bun add @alphasense/otel-apollo-plugin
# or
npm install @alphasense/otel-apollo-plugin
# or
pnpm add @alphasense/otel-apollo-pluginUsage
1. Initialize OpenTelemetry (Required)
CRITICAL: Initialize OpenTelemetry at the very top of your entry file, before importing Apollo Server or Express!
// index.ts - MUST be the first import!
import { initializeOpenTelemetry } from '@alphasense/otel-apollo-plugin';
// Initialize OTEL first
const sdk = initializeOpenTelemetry({
serviceName: 'graphql-search',
serviceVersion: '1.0.0',
});
// Now import Apollo Server and other modules
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';2. Add Plugin to Apollo Server (Optional)
The plugin enriches traces with additional context like operation names, user IDs, and error information.
import { ApolloServer } from '@apollo/server';
import { OpenTelemetryPlugin, enrichContextWithOtel } from '@alphasense/otel-apollo-plugin';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
OpenTelemetryPlugin(),
// ... other plugins
],
});3. Enrich Context with OpenTelemetry Data
import { enrichContextWithOtel } from '@alphasense/otel-apollo-plugin';
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
const baseContext = {
user: await getUserFromToken(req.headers.authorization),
// ... other context fields
};
// Adds requestId from trace ID
return enrichContextWithOtel(baseContext);
},
});4. Use Utility Functions in Resolvers
import { getActiveSpan, getBaggageItem, BAGGAGE_KEYS, SpanStatusCode } from '@alphasense/otel-apollo-plugin';
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
// Get active span to add custom attributes
const span = getActiveSpan();
span?.setAttribute('user.id', id);
// Get baggage values (e.g., clientId from gateway)
const clientId = getBaggageItem(BAGGAGE_KEYS.CLIENT_ID);
try {
const user = await fetchUser(id);
span?.setStatus({ code: SpanStatusCode.OK });
return user;
} catch (error) {
span?.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span?.recordException(error);
throw error;
}
},
},
};5. Create Custom Spans
import { getTracer, SpanStatusCode } from '@alphasense/otel-apollo-plugin';
async function fetchUserData(userId: number) {
const tracer = getTracer('graphql-search');
const span = tracer.startSpan('fetch-user-data');
try {
const data = await database.query('SELECT * FROM users WHERE id = ?', [userId]);
span.setAttribute('user.id', userId);
span.setAttribute('db.rows_returned', data.length);
span.setStatus({ code: SpanStatusCode.OK });
return data;
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
throw error;
} finally {
span.end();
}
}Configuration
Environment Variables
# Enable/disable OpenTelemetry (default: true)
OTEL_ENABLED=true
# OTLP endpoint (default: http://grafana-agent-traces.tracing:4317)
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://grafana-agent-traces.tracing:4317
# Service version (optional, default: 1.0.0)
OTEL_SERVICE_VERSION=2.3.0
# Sampling configuration (optional)
OTEL_TRACES_SAMPLER=always_on # always_on, always_off, traceidratio
OTEL_TRACES_SAMPLER_ARG=1.0 # For traceidratio: 0.0 to 1.0Programmatic Configuration
initializeOpenTelemetry({
serviceName: 'graphql-search',
serviceVersion: '1.0.0',
otlpEndpoint: 'http://grafana-agent-traces.tracing:4317',
enabled: true,
graphqlConfig: {
ignoreTrivialResolveSpans: true, // Skip trivial resolvers (default: true)
depth: 10, // Maximum span depth (default: 10)
mergeItems: true, // Merge list items (default: true)
},
});Migration from jaeger-apollo-plugin
Breaking Changes
1. Context Structure
Before (OpenTracing):
interface BaseContext extends JaegerPluginContext {
tracer: Tracer;
rootSpan: Span;
// ...
}After (OpenTelemetry):
import { getActiveSpan, getTracer } from '@alphasense/otel-apollo-plugin';
interface BaseContext {
requestId: string; // From enrichContextWithOtel()
// No tracer or rootSpan needed
// ...
}
// In resolvers:
const span = getActiveSpan();
const tracer = getTracer('my-service');2. Baggage Access
Before:
const clientId = context.rootSpan?.getBaggageItem('CLIENT_ID');After:
import { getBaggageItem, BAGGAGE_KEYS } from '@alphasense/otel-apollo-plugin';
const clientId = getBaggageItem(BAGGAGE_KEYS.CLIENT_ID);3. Span Operations
Before:
span.setTag('http.status_code', 200);
span.log({ event: 'error', message: 'Failed' });
span.finish();After:
span.setAttribute('http.status_code', 200);
span.addEvent('error', { message: 'Failed' });
span.end();4. Initialization Order
Critical: OpenTelemetry MUST be initialized before importing Apollo Server:
// ✅ Correct
import { initializeOpenTelemetry } from '@alphasense/otel-apollo-plugin';
const sdk = initializeOpenTelemetry({ serviceName: 'my-service' });
import { ApolloServer } from '@apollo/server';
// ❌ Wrong - traces will be incomplete!
import { ApolloServer } from '@apollo/server';
import { initializeOpenTelemetry } from '@alphasense/otel-apollo-plugin';API Reference
Initialization
initializeOpenTelemetry(config: OtelConfig)
Initialize the OpenTelemetry SDK with OTLP exporter.
Plugin
OpenTelemetryPlugin<TContext>()
Apollo Server plugin that enriches traces with GraphQL operation data.
enrichContextWithOtel<TContext>(context: TContext)
Adds OpenTelemetry data (requestId) to your Apollo Server context.
Utility Functions
getActiveSpan(): Span | undefined
Get the currently active span from the OpenTelemetry context.
getTracer(name: string, version?: string): Tracer
Get a tracer instance for creating custom spans.
getBaggageItem(key: BaggageKey): string | undefined
Get a baggage value from the current context.
setBaggageItem(key: BaggageKey, value: string): Context
Set a baggage value in the current context.
getAllBaggage(): Record<string, string>
Get all baggage items as an object.
getTraceId(): string | undefined
Get the current trace ID.
getSpanId(): string | undefined
Get the current span ID.
Constants
BAGGAGE_KEYS
Standard baggage keys:
BAGGAGE_KEYS.CLIENT_ID- Client ID making the requestBAGGAGE_KEYS.OPERATION_NAME- Root operation name
Development
Build
bun run buildTest
bun testLint
bun run lintLicense
MIT
Contributing
Issues and pull requests are welcome!
