@inh-lib/unified-telemetry-core
v0.3.4
Published
> Core telemetry abstractions - framework and layer agnostic
Readme
@inh-lib/unified-telemetry-core
Core telemetry abstractions - framework and layer agnostic
A comprehensive TypeScript library providing unified telemetry capabilities with structured logging, distributed tracing, and metrics collection. Built with type safety and simplicity in mind.
✨ Features
- 🎯 Framework Agnostic - Works with any TypeScript/JavaScript framework or runtime
- 📊 Three Pillars of Observability - Logging, Tracing, and Metrics in one unified API
- 🔒 Type Safe - No
anytypes, fully typed interfaces with excellent IntelliSense - 🚀 Zero Dependencies - No external HTTP or framework dependencies
- 🔧 Pluggable Providers - Easy to extend with custom implementations
- 📈 Production Ready - Includes both development (Console) and production (NoOp) providers
- 🎛️ Union Types - Uses modern union types instead of enums for better tree-shaking
📦 Installation
npm install @inh-lib/unified-telemetry-core🚀 Quick Start
Basic Setup
import {
ConsoleUnifiedTelemetryProvider,
UnifiedTelemetryProvider,
DEFAULT_TELEMETRY_CONFIG
} from '@inh-lib/unified-telemetry-core';
// Initialize provider
const provider: UnifiedTelemetryProvider = new ConsoleUnifiedTelemetryProvider({
...DEFAULT_TELEMETRY_CONFIG,
serviceName: 'my-service',
serviceVersion: '1.0.0',
environment: 'development'
});
// Get components
const { logger, tracer, metrics } = provider;Structured Logging with Tracing
// Start a span for tracing
const span = tracer.startSpan('user-registration', {
kind: UNIFIED_SPAN_KIND.SERVER,
attributes: { 'user.id': '12345' }
});
// Create logger with context
const telemetryLogger = logger.getLogger({
span,
options: {
operationType: 'business',
operationName: 'registerUser',
layer: 'service',
autoAddSpanEvents: true
}
});
// Log with automatic trace correlation
telemetryLogger.info('Starting user registration', {
email: '[email protected]'
});
// Handle errors with exception tracking
try {
// ... business logic
} catch (error) {
telemetryLogger.error('Registration failed', error, {
reason: 'validation_error'
});
span.setStatus({ code: UNIFIED_SPAN_STATUS_CODE.ERROR, message: error.message });
}
// Finish span
telemetryLogger.finishSpan();Metrics Collection
// Create metrics
const requestCounter = metrics.createCounter(
'http_requests_total',
'Total HTTP requests received'
);
const responseTime = metrics.createHistogram(
'http_request_duration_ms',
'HTTP request duration in milliseconds'
);
const activeConnections = metrics.createGauge(
'active_connections',
'Number of active connections'
);
// Record metrics
requestCounter.add(1, { method: 'POST', route: '/api/users' });
responseTime.record(150, { method: 'POST', status: '200' });
activeConnections.set(42);🏗️ Architecture
Core Components
The library is built around three main components:
- Provider (
UnifiedTelemetryProvider) - Factory for creating telemetry components - Logger (
UnifiedTelemetryLogger) - Structured logging with trace context - Tracer (
UnifiedTelemetryTracer) - Distributed tracing spans - Metrics (
UnifiedTelemetryMetrics) - Application metrics collection
Layer-based Architecture
The library supports a layered architecture approach:
- HTTP Layer - Web requests, API endpoints
- Service Layer - Business logic, domain services
- Data Layer - Database operations, data access
- Core Layer - Utilities, shared functionality
- Integration Layer - External service calls
- Custom Layer - Application-specific operations
Operation Types
Each telemetry operation can be categorized:
http- HTTP requests and responsesbusiness- Business logic operationsdatabase- Database queries and transactionsutility- Helper functions and utilitiesintegration- External API callsauth- Authentication and authorizationcustom- Application-specific operations
📚 API Reference
Providers
ConsoleUnifiedTelemetryProvider
Development provider that outputs all telemetry to console.
const provider = new ConsoleUnifiedTelemetryProvider({
serviceName: 'my-service',
serviceVersion: '1.0.0',
environment: 'development',
enableLogging: true,
enableTracing: true,
enableMetrics: true
});NoOpUnifiedTelemetryProvider
Production-ready provider that discards all telemetry (useful for testing or when telemetry is disabled).
const provider = new NoOpUnifiedTelemetryProvider({
serviceName: 'my-service',
serviceVersion: '1.0.0',
environment: 'production'
});Logger Context Options
interface UnifiedLoggerOptions {
operationType: 'http' | 'business' | 'database' | 'utility' | 'integration' | 'auth' | 'custom';
operationName: string;
layer: 'http' | 'service' | 'data' | 'core' | 'integration' | 'custom';
autoAddSpanEvents?: boolean; // Automatically add span events for all log levels
attributes?: Record<string, string | number | boolean>; // Additional attributes
}Span Options
interface UnifiedSpanOptions {
kind?: UnifiedSpanKind; // 'internal' | 'server' | 'client' | 'producer' | 'consumer'
attributes?: Record<string, string | number | boolean>;
startTime?: Date;
}🔌 Implementing Custom Providers
The library is designed to be easily extended with custom providers. All core interfaces follow industry standards and can be mapped to popular telemetry backends like OpenTelemetry, Jaeger, Prometheus, etc.
Core Interfaces Overview
UnifiedTelemetryProvider
Main factory interface that provides access to all telemetry components.
interface UnifiedTelemetryProvider {
readonly logger: UnifiedTelemetryLoggerService;
readonly tracer: UnifiedTelemetryTracer;
readonly metrics: UnifiedTelemetryMetrics;
shutdown(): Promise<void>;
}Implementation Requirements:
- Must provide instances of logger service, tracer, and metrics
shutdown()must cleanup resources and flush pending data- Should handle configuration through constructor
UnifiedTelemetryTracer
Distributed tracing interface compatible with OpenTelemetry spans.
interface UnifiedTelemetryTracer {
startSpan(name: string, options?: UnifiedSpanOptions): UnifiedTelemetrySpan;
getActiveSpan(): UnifiedTelemetrySpan | undefined;
}
interface UnifiedTelemetrySpan {
setTag(key: string, value: string | number | boolean): UnifiedTelemetrySpan;
setStatus(status: UnifiedSpanStatus): UnifiedTelemetrySpan;
recordException(exception: Error): UnifiedTelemetrySpan;
addEvent(name: string, attributes?: Record<string, string | number | boolean>): UnifiedTelemetrySpan;
finish(): void;
getTraceId(): string;
getSpanId(): string;
getStartTime(): Date;
}OpenTelemetry Mapping:
// OpenTelemetry Implementation Example
import { trace, Span } from '@opentelemetry/api';
class OTelUnifiedTracer implements UnifiedTelemetryTracer {
private tracer = trace.getTracer('my-service');
startSpan(name: string, options?: UnifiedSpanOptions): UnifiedTelemetrySpan {
const otelSpan = this.tracer.startSpan(name, {
kind: this.mapSpanKind(options?.kind),
attributes: options?.attributes,
startTime: options?.startTime
});
return new OTelUnifiedSpan(otelSpan);
}
getActiveSpan(): UnifiedTelemetrySpan | undefined {
const activeSpan = trace.getActiveSpan();
return activeSpan ? new OTelUnifiedSpan(activeSpan) : undefined;
}
private mapSpanKind(kind?: UnifiedSpanKind): SpanKind {
switch (kind) {
case 'server': return SpanKind.SERVER;
case 'client': return SpanKind.CLIENT;
case 'producer': return SpanKind.PRODUCER;
case 'consumer': return SpanKind.CONSUMER;
default: return SpanKind.INTERNAL;
}
}
}UnifiedTelemetryMetrics
Metrics interface compatible with Prometheus and OpenTelemetry metrics.
interface UnifiedTelemetryMetrics {
createCounter(name: string, description?: string): UnifiedTelemetryCounter;
createHistogram(name: string, description?: string): UnifiedTelemetryHistogram;
createGauge(name: string, description?: string): UnifiedTelemetryGauge;
}Prometheus Integration Example:
import * as prometheus from 'prom-client';
class PrometheusUnifiedMetrics implements UnifiedTelemetryMetrics {
private registry = new prometheus.Registry();
createCounter(name: string, description?: string): UnifiedTelemetryCounter {
const counter = new prometheus.Counter({
name,
help: description || name,
labelNames: ['method', 'status', 'route'] // common labels
});
this.registry.registerMetric(counter);
return new PrometheusUnifiedCounter(counter);
}
createHistogram(name: string, description?: string): UnifiedTelemetryHistogram {
const histogram = new prometheus.Histogram({
name,
help: description || name,
labelNames: ['method', 'status', 'route'],
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10] // default buckets
});
this.registry.registerMetric(histogram);
return new PrometheusUnifiedHistogram(histogram);
}
// ... similar for gauge
}UnifiedTelemetryLoggerService
Logger service factory for creating contextual loggers.
interface UnifiedTelemetryLoggerService {
getLogger(context: UnifiedLoggerContext): UnifiedTelemetryLogger;
getBaseLogger(): UnifiedBaseTelemetryLogger;
}
interface UnifiedLoggerContext {
options: UnifiedLoggerOptions;
span: UnifiedTelemetrySpan;
}Complete OpenTelemetry Provider Example
import {
UnifiedTelemetryProvider,
UnifiedTelemetryConfig,
UnifiedLoggerContext,
UnifiedTelemetryLogger,
UnifiedBaseTelemetryLogger
} from '@inh-lib/unified-telemetry-core';
import { NodeTracerProvider } from '@opentelemetry/sdk-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
export class OpenTelemetryUnifiedProvider implements UnifiedTelemetryProvider {
public readonly logger: UnifiedTelemetryLoggerService;
public readonly tracer: UnifiedTelemetryTracer;
public readonly metrics: UnifiedTelemetryMetrics;
private tracerProvider: NodeTracerProvider;
constructor(config: UnifiedTelemetryConfig) {
// Initialize OpenTelemetry
this.tracerProvider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: config.serviceName,
[SemanticResourceAttributes.SERVICE_VERSION]: config.serviceVersion,
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: config.environment,
}),
});
// Initialize components
this.tracer = new OTelUnifiedTracer(this.tracerProvider);
this.metrics = new OTelUnifiedMetrics(config);
this.logger = new OTelUnifiedLoggerService(config);
// Register the tracer provider
this.tracerProvider.register();
}
async shutdown(): Promise<void> {
await this.tracerProvider.shutdown();
}
}
class OTelUnifiedLoggerService implements UnifiedTelemetryLoggerService {
constructor(private config: UnifiedTelemetryConfig) {}
getLogger(context: UnifiedLoggerContext): UnifiedTelemetryLogger {
// Create structured logger that correlates with OpenTelemetry spans
return new OTelStructuredLogger(context, this.config);
}
getBaseLogger(): UnifiedBaseTelemetryLogger {
// Return simple logger without telemetry features
return new SimpleStructuredLogger(this.config);
}
}
class OTelStructuredLogger implements UnifiedTelemetryLogger {
constructor(
private context: UnifiedLoggerContext,
private config: UnifiedTelemetryConfig
) {}
info(message: string, attributes?: Record<string, unknown>): void {
// Structured logging with trace correlation
const logEntry = {
timestamp: new Date().toISOString(),
level: 'info',
message,
service: this.config.serviceName,
version: this.config.serviceVersion,
traceId: this.context.span.getTraceId(),
spanId: this.context.span.getSpanId(),
operation: this.context.options.operationName,
layer: this.context.options.layer,
...attributes
};
console.log(JSON.stringify(logEntry));
// Add event to OpenTelemetry span
if (this.context.options.autoAddSpanEvents) {
this.context.span.addEvent('log.info', { message, ...attributes });
}
}
// ... implement other methods
}Integration Patterns
1. Backend Integration Matrix
| Backend | Provider Class | Tracer | Metrics | Logger |
|---------|---------------|---------|---------|---------|
| OpenTelemetry | OpenTelemetryUnifiedProvider | ✅ Direct mapping | ✅ OTel Metrics API | ✅ Structured + Span correlation |
| Jaeger | JaegerUnifiedProvider | ✅ Jaeger Client | ➖ Prometheus sidecar | ✅ JSON structured |
| Datadog | DatadogUnifiedProvider | ✅ DD Trace | ✅ DD Metrics | ✅ DD Logs API |
| AWS X-Ray | XRayUnifiedProvider | ✅ X-Ray SDK | ✅ CloudWatch | ✅ CloudWatch Logs |
| Console/Local | ConsoleUnifiedProvider | ✅ Console output | ✅ Console output | ✅ Console output |
2. Configuration Mapping
// Generic config
const unifiedConfig: UnifiedTelemetryConfig = {
serviceName: 'my-service',
serviceVersion: '1.0.0',
environment: 'production',
enableTracing: true,
enableMetrics: true,
enableLogging: true,
sampleRate: 0.1
};
// OpenTelemetry specific
const otelProvider = new OpenTelemetryUnifiedProvider({
...unifiedConfig,
providerConfig: {
jaegerEndpoint: 'http://jaeger:14268/api/traces',
prometheusPort: 9090,
logFormat: 'json'
}
});
// Datadog specific
const datadogProvider = new DatadogUnifiedProvider({
...unifiedConfig,
providerConfig: {
datadogApiKey: process.env.DD_API_KEY,
datadogSite: 'datadoghq.com',
enableRuntimeMetrics: true
}
});3. Migration Strategy
// Step 1: Start with Console provider for development
const devProvider = new ConsoleUnifiedProvider(config);
// Step 2: Switch to production provider
const prodProvider = process.env.NODE_ENV === 'production'
? new OpenTelemetryUnifiedProvider(config)
: new ConsoleUnifiedProvider(config);
// Step 3: A/B test different providers
const provider = createProvider(config.telemetryBackend, config);
function createProvider(backend: string, config: UnifiedTelemetryConfig) {
switch (backend) {
case 'otel': return new OpenTelemetryUnifiedProvider(config);
case 'datadog': return new DatadogUnifiedProvider(config);
case 'jaeger': return new JaegerUnifiedProvider(config);
default: return new ConsoleUnifiedProvider(config);
}
}Implementation Checklist
When implementing a new provider, ensure:
- [ ] UnifiedTelemetryProvider - Factory creates all components
- [ ] UnifiedTelemetryTracer - Maps span lifecycle correctly
- [ ] UnifiedTelemetrySpan - Supports tags, events, status, exceptions
- [ ] UnifiedTelemetryMetrics - Counter, Histogram, Gauge implementations
- [ ] UnifiedTelemetryLogger - Structured logging with trace correlation
- [ ] Configuration - Handles provider-specific config in
providerConfig - [ ] Resource Management - Proper shutdown and cleanup
- [ ] Error Handling - Graceful degradation on backend failures
- [ ] Type Safety - No
anytypes, proper TypeScript support - [ ] Testing - Unit tests with mock backends
🔧 Advanced Usage
HTTP Request Tracing
import { UNIFIED_SPAN_KIND } from '@inh-lib/unified-telemetry-core';
function handleHttpRequest(req: Request) {
// Start HTTP span
const span = tracer.startSpan(`${req.method} ${req.path}`, {
kind: UNIFIED_SPAN_KIND.SERVER,
attributes: {
'http.method': req.method,
'http.url': req.url,
'http.user_agent': req.headers['user-agent']
}
});
// Create HTTP layer logger
const httpLogger = logger.getLogger({
span,
options: {
operationType: 'http',
operationName: `${req.method} ${req.path}`,
layer: 'http',
autoAddSpanEvents: true,
attributes: {
'request.id': req.id,
'user.id': req.user?.id
}
}
});
httpLogger.info('Processing HTTP request', {
method: req.method,
path: req.path
});
// ... handle request
httpLogger.finishSpan();
}Database Operations
async function getUserById(id: string) {
const span = tracer.startSpan('db.users.findById', {
kind: UNIFIED_SPAN_KIND.CLIENT,
attributes: {
'db.operation': 'SELECT',
'db.table': 'users',
'user.id': id
}
});
const dbLogger = logger.getLogger({
span,
options: {
operationType: 'database',
operationName: 'getUserById',
layer: 'data',
autoAddSpanEvents: true
}
});
try {
dbLogger.info('Querying user by ID', { userId: id });
const user = await db.users.findById(id);
span.setTag('db.rows_affected', user ? 1 : 0);
dbLogger.info('Query completed', { found: !!user });
return user;
} catch (error) {
dbLogger.error('Database query failed', error);
throw error;
} finally {
dbLogger.finishSpan();
}
}Custom Metrics Dashboard
class ServiceMetrics {
private requestCounter = metrics.createCounter(
'service_requests_total',
'Total requests processed by service'
);
private errorCounter = metrics.createCounter(
'service_errors_total',
'Total errors encountered by service'
);
private processingTime = metrics.createHistogram(
'service_processing_duration_ms',
'Time spent processing requests'
);
recordRequest(operation: string, success: boolean, duration: number) {
this.requestCounter.add(1, {
operation,
status: success ? 'success' : 'error'
});
if (!success) {
this.errorCounter.add(1, { operation });
}
this.processingTime.record(duration, { operation });
}
}🧪 Testing
The library provides a NoOp implementation perfect for testing:
import { NoOpUnifiedTelemetryProvider } from '@inh-lib/unified-telemetry-core';
describe('My Service', () => {
let provider: UnifiedTelemetryProvider;
beforeEach(() => {
// Use NoOp provider in tests
provider = new NoOpUnifiedTelemetryProvider();
});
afterEach(async () => {
await provider.shutdown();
});
it('should process requests', () => {
const span = provider.tracer.startSpan('test-operation');
const logger = provider.logger.getLogger({
span,
options: {
operationType: 'business',
operationName: 'testOperation',
layer: 'service'
}
});
// All telemetry calls are no-ops, won't affect test output
logger.info('Processing test data');
logger.finishSpan();
});
});🔌 Extending with Custom Providers
Create your own provider by implementing the interfaces:
import {
UnifiedTelemetryProvider,
UnifiedTelemetryTracer,
UnifiedTelemetryMetrics,
UnifiedTelemetryLoggerService,
UnifiedTelemetryConfig
} from '@inh-lib/unified-telemetry-core';
export class CustomTelemetryProvider implements UnifiedTelemetryProvider {
public readonly logger: UnifiedTelemetryLoggerService;
public readonly tracer: UnifiedTelemetryTracer;
public readonly metrics: UnifiedTelemetryMetrics;
constructor(config: UnifiedTelemetryConfig) {
this.tracer = new CustomTracer(config);
this.logger = new CustomLoggerService(config);
this.metrics = new CustomMetrics(config);
}
async shutdown(): Promise<void> {
// Implement cleanup logic
}
}🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/your-org/unified-telemetry-core.git
# Install dependencies
npm install
# Run tests
npm test
# Build the library
npm run buildCode Style
- ✅ No
anytypes allowed - ✅ Use union types instead of enums
- ✅ Prefer composition over inheritance
- ✅ Internal utilities are not exported
- ✅ All public APIs must be fully typed
📄 License
MIT License - see LICENSE file for details.
🗺️ Roadmap
- [ ] OpenTelemetry provider implementation
- [ ] Jaeger provider implementation
- [ ] Prometheus metrics provider
- [ ] Structured logging with JSON output
- [ ] Async context propagation
- [ ] Performance benchmarks
- [ ] More comprehensive examples
🐛 Issues & Support
Made with ❤️ by the INH Team
