@validatedid/opentelemetry-wrapper
v0.0.1
Published
A TypeScript wrapper for OpenTelemetry SDK with singleton pattern support
Readme
OpenTelemetry Wrapper
A TypeScript wrapper for OpenTelemetry SDK with singleton pattern support.
Installation
npm install @your-scope/opentelemetry-wrapperNote: Replace
@your-scope/opentelemetry-wrapperwith the actual package name when published.
Local Development
For testing this package locally before publishing, see LOCAL_TESTING.md.
Usage
Initialize OpenTelemetry
import { OpenTelemetryInitializer } from './src/OpenTelemetryInitializer';
// Initialize once at application startup
// build() returns the OpenTelemetry singleton instance directly
const otel = OpenTelemetryInitializer
.initialize({
baseUrl: 'http://localhost:4318',
resourceAttributes: {
serviceName: 'my-service',
version: '1.0.0',
product: 'my-product',
environment: 'production',
role: 'api',
entry: 'http',
costCenter: 'CC001',
}
})
.withTraceExporter()
.withLogExporter()
.withMetricsExporter()
.build();Adding Custom Instrumentations
If you need to add instrumentations that are not automatically detected, use the withInstrumentation() method:
import { OpenTelemetryInitializer } from './src/OpenTelemetryInitializer';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
const otel = OpenTelemetryInitializer
.initialize({
baseUrl: 'http://localhost:4318',
resourceAttributes: {
serviceName: 'my-service',
version: '1.0.0',
product: 'my-product',
environment: 'production',
role: 'api',
entry: 'http',
costCenter: 'CC001',
}
})
.withInstrumentation(new GraphQLInstrumentation())
.withInstrumentation(new MongoDBInstrumentation())
.withTraceExporter()
.withLogExporter()
.withMetricsExporter()
.build();Note: You can call withInstrumentation() multiple times to add multiple custom instrumentations. These will be added alongside the auto-detected instrumentations.
Configuration Validation
All configuration values are validated during initialization. Missing or empty values will throw descriptive errors:
// ❌ This will throw an error
OpenTelemetryInitializer.initialize({
baseUrl: '', // Error: "baseUrl is required in OpenTelemetry configuration"
resourceAttributes: {
serviceName: '', // Error: "resourceAttributes.serviceName is required..."
// ...
}
});Required configuration fields:
baseUrl- The OpenTelemetry collector endpointresourceAttributes.serviceName- Name of your serviceresourceAttributes.version- Version of your serviceresourceAttributes.product- Product nameresourceAttributes.environment- Environment (e.g., production, staging)resourceAttributes.role- Service role (e.g., api, worker)resourceAttributes.entry- Entry point type (e.g., http, grpc)resourceAttributes.costCenter- Cost center identifier
Get Logger with Parameter
import { LogLevel } from '@vididentity/opentelemetry-wrapper';
// Get logger for a specific controller/component
const logger = otel.getLogger('user-controller');
logger.emit({
severityNumber: LogLevel.INFO,
severityText: 'INFO',
body: 'User login successful',
attributes: { userId: '123' }
});
// Get logger for another component
const orderLogger = otel.getLogger('order-service');
orderLogger.emit({
severityNumber: LogLevel.ERROR,
severityText: 'ERROR',
body: 'Order processing failed',
attributes: { orderId: '456' }
});Available Log Levels
The LogLevel enum provides standard log severity levels without exposing OpenTelemetry internals:
LogLevel.TRACE // Most verbose - fine-grained debugging
LogLevel.DEBUG // Detailed debugging information
LogLevel.INFO // Informational messages
LogLevel.WARN // Warning messages
LogLevel.ERROR // Error messages
LogLevel.FATAL // Critical errors
// Each level also has variants (e.g., TRACE2, TRACE3, TRACE4, DEBUG2, etc.)
// for more granular controlExample usage:
import { OpenTelemetry, LogLevel } from '@vididentity/opentelemetry-wrapper';
const otel = OpenTelemetry.getInstance();
const logger = otel.getLogger('payment-service');
// Log at different levels
logger.emit({
severityNumber: LogLevel.DEBUG,
severityText: 'DEBUG',
body: 'Payment validation started',
attributes: { transactionId: 'tx-123' }
});
logger.emit({
severityNumber: LogLevel.WARN,
severityText: 'WARN',
body: 'Payment processing slow',
attributes: { duration: 5000 }
});
logger.emit({
severityNumber: LogLevel.ERROR,
severityText: 'ERROR',
body: 'Payment failed',
attributes: { error: 'Insufficient funds' }
});Get Tracer
// Get tracer for a specific service
const tracer = otel.getTracer('my-service', '1.0.0');
// Create spans
const span = tracer.startSpan('process-order');
try {
// Your code here
span.setStatus({ code: 0 }); // OK
} finally {
span.end();
}Get Meter
// Get meter for metrics
const meter = otel.getMeter('my-service', '1.0.0');
// Create metrics
const counter = meter.createCounter('requests.count');
counter.add(1, { endpoint: '/api/users' });Singleton Pattern
The OpenTelemetry class is a singleton, so you can get the instance anywhere in your application:
import { OpenTelemetry } from './src/OpenTelemetry';
// Get the singleton instance (must be initialized first)
const otel = OpenTelemetry.getInstance();
// Use it with different logger names for different parts of your application
const apiLogger = otel.getLogger('api-layer');
const dbLogger = otel.getLogger('database-layer');
const cacheLogger = otel.getLogger('cache-layer');Shutdown
// Gracefully shutdown OpenTelemetry when your application exits
await otel.shutdown();Architecture
OpenTelemetry - Singleton class that provides access to logger, tracer, and meter
getInstance()- Get the singleton instancegetLogger(name: string)- Get a logger with a specific namegetTracer(name: string, version?: string)- Get a tracergetMeter(name: string, version?: string)- Get a metershutdown()- Shutdown the SDK gracefully
OpenTelemetryInitializer - Builder pattern for configuring and initializing OpenTelemetry
initialize(config)- Create initializer with configuration and validate all required fieldswithTraceExporter()- Enable trace export to OTLP endpointwithLogExporter()- Enable log export to OTLP endpointwithMetricsExporter()- Enable metrics export to OTLP endpointwithInstrumentation(instrumentation)- Add custom instrumentation not auto-detected (can be called multiple times)build()- Build, start the SDK, and return the OpenTelemetry singleton instance
LogLevel - Enum for log severity levels without exposing OpenTelemetry internals
TRACE,DEBUG,INFO,WARN,ERROR,FATAL- Standard log levels- Each level includes variants (e.g.,
TRACE2,DEBUG3, etc.) for granular control
OpenTelemetryConfig - Configuration interface
baseUrl- OTLP collector endpointresourceAttributes- Service resource attributes (name, version, environment, etc.)
Design Patterns
Builder Pattern
The initializer uses the builder pattern for fluent configuration:
const otel = OpenTelemetryInitializer
.initialize(config)
.withInstrumentation(customInstrumentation) // Optional - add custom instrumentations
.withTraceExporter() // Optional
.withLogExporter() // Optional
.withMetricsExporter() // Optional
.build(); // Returns OpenTelemetry instanceSingleton Pattern
The OpenTelemetry class ensures only one instance exists throughout the application:
// After initialization, you can get the instance anywhere
const otel = OpenTelemetry.getInstance();No Temporal Coupling
The design eliminates temporal coupling - the OpenTelemetry singleton is properly initialized before being returned, so there's no risk of using it before it's ready.
Key Features
Singleton Pattern with Factory Methods
The library uses the singleton pattern with factory methods to provide a clean API:
- OpenTelemetry is a singleton - Only one instance exists throughout the application
- Factory methods -
getLogger(name),getTracer(name),getMeter(name)create instrumentation with different identifiers - Component-specific instrumentation - Each part of your application can get its own logger/tracer/meter with a unique name
Benefits:
- Initialize OpenTelemetry once with all resource attributes (service name, environment, etc.)
- Different parts of your application call
getLogger('component-name')with their specific component identifier - All loggers share the same underlying configuration but are identified by different names
- No temporal coupling - the SDK is fully initialized before being used
- Type-safe configuration with runtime validation
