{"_id":"@apdev/express-otel-observability","name":"@apdev/express-otel-observability","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.2":{"name":"@apdev/express-otel-observability","version":"1.0.2","description":"A comprehensive OpenTelemetry observability module for Express applications with automatic tracing, structured logging, metrics, and error handling","author":{"name":"Anfitrião Prime"},"license":"MIT","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js","default":"./dist/index.js"}},"keywords":["express","opentelemetry","otel","observability","tracing","metrics","logging","distributed-tracing","rabbitmq","amqplib","socket.io","websocket","loki","tempo","prometheus","grafana","apm","monitoring"],"repository":{"type":"git","url":"git+https://github.com/pafrtds/express-otel-observability.git"},"bugs":{"url":"https://github.com/pafrtds/express-otel-observability/issues"},"homepage":"https://github.com/pafrtds/express-otel-observability#readme","scripts":{"build":"tsc -p tsconfig.build.json","build:watch":"tsc -p tsconfig.build.json --watch","clean":"rm -rf dist tsconfig.build.tsbuildinfo","verify:dist":"node -e \"const fs=require('fs'); const p=(f)=>fs.existsSync(f); if(!p('dist/index.js')||!p('dist/index.d.ts')){ console.error('ERRO: dist/index.js ou dist/index.d.ts não encontrados. Rode npm run build.'); process.exit(1); }\"","prepack":"npm run clean && npm run build && npm run verify:dist","prepublishOnly":"npm run prepack","lint":"eslint \"src/**/*.ts\"","lint:fix":"eslint \"src/**/*.ts\" --fix","test":"jest","test:watch":"jest --watch","test:cov":"jest --coverage"},"peerDependencies":{"express":">=4.0.0"},"peerDependenciesMeta":{"amqplib":{"optional":true},"amqp-connection-manager":{"optional":true},"socket.io":{"optional":true},"winston":{"optional":true}},"dependencies":{"@opentelemetry/api":"^1.9.0","@opentelemetry/api-logs":"^0.211.0","@opentelemetry/auto-instrumentations-node":"^0.69.0","@opentelemetry/exporter-logs-otlp-http":"^0.211.0","@opentelemetry/exporter-metrics-otlp-http":"^0.211.0","@opentelemetry/exporter-trace-otlp-http":"^0.211.0","@opentelemetry/resources":"^2.5.0","@opentelemetry/sdk-logs":"^0.211.0","@opentelemetry/sdk-metrics":"^2.5.0","@opentelemetry/sdk-node":"^0.211.0","@opentelemetry/sdk-trace-base":"^2.5.0","@opentelemetry/semantic-conventions":"^1.39.0","triple-beam":"^1.4.1","winston-transport":"^4.7.0"},"devDependencies":{"@types/express":"^4.17.21","@types/jest":"^29.5.2","@types/node":"^20.3.1","@types/triple-beam":"^1.3.5","@typescript-eslint/eslint-plugin":"^7.8.0","@typescript-eslint/parser":"^7.8.0","amqp-connection-manager":"^4.1.14","amqplib":"^0.10.3","eslint":"^8.56.0","express":"^4.18.3","jest":"^29.7.0","socket.io":"^4.7.5","ts-jest":"^29.1.0","typescript":"^5.1.3","winston":"^3.12.0"},"engines":{"node":">=18.0.0"},"_id":"@apdev/express-otel-observability@1.0.2","gitHead":"672dfb0d522dceab17d9b649f6446cc1e94ee48b","_nodeVersion":"20.18.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-Pl9dWhINoADxcAJ1koRswxZWMXM3kWwPIp0jC5i3QWP/3nkM815GXTtByjUPqyFqtxgqC7Ik8F1R6CnsiOacXg==","shasum":"0de1bd9beaefbc670cec2a0cacd4477a3d937921","tarball":"https://registry.npmjs.org/@apdev/express-otel-observability/-/express-otel-observability-1.0.2.tgz","fileCount":57,"unpackedSize":143432,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCPNkQufLWCq8a0mB69NHkBJHFwIBg6GFatbmjiYfK3LQIhAI8F9F+OfnXvd6wEN4czJOmqC3MMkL+hsj/l14UuNWtQ"}]},"_npmUser":{"name":"apdev","email":"cereso.rodrigues@anfitriaoprime.com.br"},"directories":{},"maintainers":[{"name":"apdev","email":"cereso.rodrigues@anfitriaoprime.com.br"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/express-otel-observability_1.0.2_1770313913419_0.2400582470873336"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-05T17:51:53.330Z","1.0.2":"2026-02-05T17:51:53.566Z","modified":"2026-02-05T17:51:53.757Z"},"maintainers":[{"name":"apdev","email":"cereso.rodrigues@anfitriaoprime.com.br"}],"description":"A comprehensive OpenTelemetry observability module for Express applications with automatic tracing, structured logging, metrics, and error handling","homepage":"https://github.com/pafrtds/express-otel-observability#readme","keywords":["express","opentelemetry","otel","observability","tracing","metrics","logging","distributed-tracing","rabbitmq","amqplib","socket.io","websocket","loki","tempo","prometheus","grafana","apm","monitoring"],"repository":{"type":"git","url":"git+https://github.com/pafrtds/express-otel-observability.git"},"author":{"name":"Anfitrião Prime"},"bugs":{"url":"https://github.com/pafrtds/express-otel-observability/issues"},"license":"MIT","readme":"# Express OpenTelemetry Observability\n\nA comprehensive OpenTelemetry observability module for Express applications with automatic tracing, structured logging, metrics, and error handling.\n\n## Features\n\n- **Automatic Tracing**: HTTP, Express, Axios auto-instrumentation via OpenTelemetry\n- **Distributed Tracing**: Context propagation across HTTP, RabbitMQ, and WebSocket\n- **Structured Logging**: JSON logs with automatic trace_id/span_id enrichment, compatible with Loki\n- **Winston Transport**: Drop-in Winston transport for existing codebases (zero code changes!)\n- **Metrics Collection**: HTTP requests, RabbitMQ messages, WebSocket events, and errors\n- **Error Handling**: Global error middleware with structured logging and span recording\n- **RabbitMQ Support**: Trace context injection/extraction for message queues (amqplib/amqp-connection-manager)\n- **WebSocket Support**: Socket.IO event tracing with context propagation\n- **OTLP Export**: Traces, metrics, and logs exported to OpenTelemetry Collector\n- **Resilience**: Application won't crash if Collector is unavailable\n\n## Requirements\n\n- Node.js >= 18.0.0\n- Express >= 4.0.0\n\n## Installation\n\n```bash\nnpm install @pafrtds/express-otel-observability\n```\n\n## Quick Start\n\n### 1. Create Tracing Bootstrap File\n\nCreate a file `src/tracing.bootstrap.ts`:\n\n```typescript\nimport { initObservability } from '@pafrtds/express-otel-observability'\n\ninitObservability({\n  serviceName: process.env.SERVICE_NAME || 'my-express-service',\n  serviceVersion: process.env.SERVICE_VERSION || '1.0.0',\n  environment: process.env.NODE_ENV || 'development',\n})\n```\n\n### 2. Import Bootstrap FIRST in Server\n\n```typescript\n// server.ts\nimport './tracing.bootstrap' // MUST be first!\n\nimport http from 'http'\nimport express from 'express'\nimport {\n  httpMetricsMiddleware,\n  observabilityErrorMiddleware,\n  logger,\n} from '@pafrtds/express-otel-observability'\n\nconst app = express()\n\n// Add metrics middleware BEFORE routes\napp.use(httpMetricsMiddleware)\n\napp.use(express.json())\n\n// Your routes\napp.get('/users', (req, res) => {\n  logger.info('Fetching users')\n  res.json([{ id: 1, name: 'John' }])\n})\n\n// Add error middleware AFTER routes\napp.use(observabilityErrorMiddleware)\n\nconst server = http.createServer(app)\nserver.listen(3000, () => {\n  logger.info('Server started on port 3000')\n})\n```\n\n## Configuration Options\n\n```typescript\ninterface ObservabilityOptions {\n  // Required\n  serviceName: string\n\n  // Optional (with defaults)\n  serviceVersion?: string           // Default: '1.0.0'\n  environment?: string              // Default: 'development'\n  \n  // OTLP Endpoints\n  otlpTraceEndpoint?: string        // Default: 'http://localhost:4318/v1/traces'\n  otlpMetricsEndpoint?: string      // Default: 'http://localhost:4318/v1/metrics'\n  otlpLogsEndpoint?: string         // Default: 'http://localhost:4318/v1/logs'\n  \n  // Feature Toggles\n  enableMetrics?: boolean           // Default: true\n  enableOtlpLogs?: boolean          // Default: true\n  enableConsoleLogs?: boolean       // Default: true\n  \n  // Other Options\n  logLevel?: 'debug' | 'info' | 'warn' | 'error'  // Default: 'info'\n  sensitiveFields?: string[]        // Fields to mask in logs\n  maxBodyLogSize?: number           // Default: 10000 bytes\n  metricsExportIntervalMs?: number  // Default: 15000\n  debug?: boolean                   // Default: false\n}\n```\n\n## Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `SERVICE_NAME` | Service name for telemetry | - |\n| `SERVICE_VERSION` | Service version | `1.0.0` |\n| `NODE_ENV` | Environment (development/production) | `development` |\n| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | OTLP traces endpoint | `http://localhost:4318/v1/traces` |\n| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | OTLP metrics endpoint | `http://localhost:4318/v1/metrics` |\n| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | OTLP logs endpoint | `http://localhost:4318/v1/logs` |\n| `OTEL_METRICS_ENABLED` | Enable metrics | `true` |\n| `OTEL_LOGS_ENABLED` | Enable OTLP logs | `true` |\n| `OTEL_CONSOLE_LOGS_ENABLED` | Enable console logs | `true` |\n| `OTEL_METRICS_EXPORT_INTERVAL_MS` | Metrics export interval | `15000` |\n| `OTEL_DEBUG` | Enable debug mode | `false` |\n| `LOG_LEVEL` | Log level | `info` |\n\n## Structured Logging\n\n### Using the Default Logger\n\n```typescript\nimport { logger } from '@pafrtds/express-otel-observability'\n\nlogger.info('User created', { userId: '123' })\nlogger.error('Failed to process request', { error: err.message })\n```\n\n### Creating Context-Specific Loggers\n\n```typescript\nimport { createLogger } from '@pafrtds/express-otel-observability'\n\nconst userLogger = createLogger('UserService')\nuserLogger.info('Processing user') // Logs with context: \"UserService\"\n```\n\n### Log Output\n\n**Development (readable format):**\n```\n2024-01-15T10:30:00.000Z INFO  [UserService] User created (trace: a1b2c3d4...)\n```\n\n**Production (JSON for Loki):**\n```json\n{\n  \"timestamp\": \"2024-01-15T10:30:00.000Z\",\n  \"level\": \"info\",\n  \"message\": \"User created\",\n  \"service\": \"my-express-service\",\n  \"environment\": \"production\",\n  \"trace_id\": \"a1b2c3d4e5f6...\",\n  \"span_id\": \"1234abcd...\",\n  \"context\": \"UserService\",\n  \"userId\": \"123\"\n}\n```\n\n## Winston Transport Integration\n\nIf your project already uses Winston, you can integrate observability with **zero code changes** by simply replacing your transport:\n\n### Minimal Change (Recommended)\n\nJust update your existing logger file:\n\n```typescript\n// Before\nimport { createLogger, format, transports } from 'winston'\n\nexport const logger = createLogger({\n  level: 'info',\n  format: format.combine(\n    format.timestamp(),\n    format.errors({ stack: true }),\n  ),\n  transports: [new transports.Console()],\n})\n\n// After\nimport { createLogger, format } from 'winston'\nimport { WinstonOtelTransport } from '@pafrtds/express-otel-observability'\n\nexport const logger = createLogger({\n  level: 'info',\n  format: format.combine(\n    format.timestamp(),\n    format.errors({ stack: true }),\n  ),\n  transports: [\n    new WinstonOtelTransport({\n      serviceName: 'my-service',\n      environment: process.env.NODE_ENV,\n    }),\n  ],\n})\n```\n\nNow all your existing `logger.info()`, `logger.error()`, etc. calls will automatically:\n- Include `trace_id` and `span_id`\n- Export to OTLP (OpenTelemetry Collector)\n- Output structured JSON in production\n- Mask sensitive data\n\n### Using the Convenience Function\n\n```typescript\nimport { createWinstonLogger } from '@pafrtds/express-otel-observability'\n\nexport const logger = createWinstonLogger({\n  serviceName: 'my-service',\n  environment: 'production',\n  level: 'info',\n})\n```\n\n### Transport Options\n\n```typescript\ninterface WinstonOtelTransportOptions {\n  serviceName?: string       // Service name for telemetry\n  environment?: string       // Environment (development/production)\n  sensitiveFields?: string[] // Fields to mask (password, token, etc.)\n  enableOtlp?: boolean       // Enable OTLP export (default: true)\n  enableConsole?: boolean    // Enable console output (default: true)\n}\n```\n\n## RabbitMQ Integration\n\n### Patch Your RabbitMQ Class\n\n```typescript\nimport { RabbitMQ } from './queue/rabbitmq'\nimport { initRabbitMQInterceptor, wrapConsumer } from '@pafrtds/express-otel-observability'\n\n// Patch the class (after initObservability)\ninitRabbitMQInterceptor(RabbitMQ, { serviceName: 'my-service' })\n\n// Now all sends are automatically traced\nconst rabbit = RabbitMQ.getInstance()\nawait rabbit.send('my-queue', { data: 'test' }) // Traced!\n```\n\n### Wrap Consumers for Trace Continuity\n\n```typescript\nimport { wrapConsumer } from '@pafrtds/express-otel-observability'\n\nawait rabbit.subscribe('my-queue', async ({ msg, ack, nack }) => {\n  await wrapConsumer(msg, 'my-queue', async (span) => {\n    // Handler executes within the trace context\n    // Logs will automatically include trace_id from the producer\n    logger.info('Processing message')\n    \n    ack(msg)\n  })\n})\n```\n\n### Manual Context Injection/Extraction\n\n```typescript\nimport { injectRabbitMQContext, extractRabbitMQContext } from '@pafrtds/express-otel-observability'\nimport { context } from '@opentelemetry/api'\n\n// When publishing manually\nconst headers = injectRabbitMQContext({})\nawait channel.sendToQueue(queue, msg, { headers })\n\n// When consuming manually\nconst parentContext = extractRabbitMQContext(msg.properties.headers)\ncontext.with(parentContext, () => {\n  // Handler logic with trace context\n})\n```\n\n## Socket.IO Integration\n\n### Initialize Interceptor\n\n```typescript\nimport { Server } from 'socket.io'\nimport { initSocketIOInterceptor } from '@pafrtds/express-otel-observability'\n\nconst io = new Server(httpServer)\n\n// Initialize interceptor (after initObservability)\ninitSocketIOInterceptor(io, { serviceName: 'my-service' })\n\n// Now all events are automatically traced\nio.on('connection', (socket) => {\n  socket.on('message', (data) => {\n    // This handler is traced!\n    logger.info('Message received')\n  })\n})\n```\n\n### Manual Handler Wrapping\n\n```typescript\nimport { wrapSocketHandler } from '@pafrtds/express-otel-observability'\n\nsocket.on('message', wrapSocketHandler('message', socket, async (data) => {\n  // Handler logic with tracing\n}))\n```\n\n### Inject Trace Context in Emits\n\n```typescript\nimport { injectTraceContext } from '@pafrtds/express-otel-observability'\n\n// Include trace context for clients\nsocket.emit('response', injectTraceContext({ data: 'hello' }))\n```\n\n## OpenTelemetry Collector Configuration\n\nExample `otel-collector.yaml`:\n\n```yaml\nreceivers:\n  otlp:\n    protocols:\n      http:\n        endpoint: 0.0.0.0:4318\n\nprocessors:\n  batch:\n    timeout: 5s\n    send_batch_size: 512\n\nexporters:\n  otlp/tempo:\n    endpoint: tempo:4317\n    tls:\n      insecure: true\n\n  prometheusremotewrite:\n    endpoint: http://prometheus:9090/api/v1/write\n    tls:\n      insecure: true\n\n  loki:\n    endpoint: http://loki:3100/loki/api/v1/push\n    labels:\n      resource:\n        service.name: \"service\"\n        service.version: \"version\"\n\nservice:\n  pipelines:\n    traces:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [otlp/tempo]\n\n    metrics:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [prometheusremotewrite]\n\n    logs:\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [loki]\n```\n\n## Collected Metrics\n\n### HTTP Metrics\n- `http_requests_total` - Total HTTP requests (labels: method, route, status_code)\n- `http_errors_total` - Total HTTP errors (labels: method, route, status_code)\n- `http_request_duration_seconds` - Request duration histogram\n\n### RabbitMQ Metrics\n- `rabbitmq_messages_total` - Total messages (labels: exchange, queue, routing_key, operation)\n- `rabbitmq_errors_total` - Total errors (labels: exchange, queue, routing_key, operation)\n- `rabbitmq_processing_duration_seconds` - Processing duration histogram\n\n### WebSocket Metrics\n- `websocket_events_total` - Total events (labels: event)\n- `websocket_errors_total` - Total errors (labels: event)\n- `websocket_event_duration_seconds` - Event duration histogram\n\n### Error Metrics\n- `errors_total` - Total errors (labels: error_type, context, error_code)\n\n## API Reference\n\n### Core Functions\n\n| Function | Description |\n|----------|-------------|\n| `initObservability(options)` | Initialize all observability components |\n| `initTracing(options)` | Initialize OpenTelemetry SDK |\n| `shutdownTracing()` | Gracefully shutdown SDK |\n\n### Logger\n\n| Function | Description |\n|----------|-------------|\n| `logger` | Default logger instance |\n| `createLogger(context)` | Create context-specific logger |\n| `logger.info(message, meta?)` | Log info level |\n| `logger.debug(message, meta?)` | Log debug level |\n| `logger.warn(message, meta?)` | Log warn level |\n| `logger.error(message, meta?)` | Log error level |\n| `logger.logError(error, meta?)` | Log error object |\n\n### Metrics\n\n| Function | Description |\n|----------|-------------|\n| `getMetrics()` | Get metrics service instance |\n| `metrics.recordHttpRequest(attrs)` | Record HTTP request |\n| `metrics.recordRabbitMessage(attrs)` | Record RabbitMQ message |\n| `metrics.recordWsEvent(attrs)` | Record WebSocket event |\n| `metrics.recordError(attrs)` | Record error |\n\n### Trace Context\n\n| Function | Description |\n|----------|-------------|\n| `getCurrentTraceId()` | Get current trace ID |\n| `getCurrentSpanId()` | Get current span ID |\n| `hasActiveTrace()` | Check if trace is active |\n| `getTraceContextInfo()` | Get full trace context |\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                     Express Application                          │\n│                                                                   │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                  Observability Module                      │   │\n│  │                                                            │   │\n│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │   │\n│  │  │   Tracing   │  │   Metrics   │  │   Logger    │       │   │\n│  │  │  (OTel SDK) │  │  (Counters/ │  │ (Structured │       │   │\n│  │  │             │  │  Histograms)│  │    JSON)    │       │   │\n│  │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │   │\n│  │         │                │                │               │   │\n│  │  ┌──────┴────────────────┴────────────────┴──────┐       │   │\n│  │  │              OTLP Exporters                    │       │   │\n│  │  └───────────────────────┬───────────────────────┘       │   │\n│  │                          │                                │   │\n│  └──────────────────────────┼────────────────────────────────┘   │\n│                             │                                     │\n└─────────────────────────────┼─────────────────────────────────────┘\n                              │\n                              ▼\n┌─────────────────────────────────────────────────────────────────┐\n│                  OpenTelemetry Collector                         │\n│                                                                   │\n│  ┌───────────┐  ┌───────────┐  ┌───────────┐                    │\n│  │  Traces   │  │  Metrics  │  │   Logs    │                    │\n│  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘                    │\n│        │              │              │                           │\n└────────┼──────────────┼──────────────┼───────────────────────────┘\n         │              │              │\n         ▼              ▼              ▼\n    ┌─────────┐   ┌───────────┐   ┌─────────┐\n    │  Tempo  │   │Prometheus │   │  Loki   │\n    │(traces) │   │ (metrics) │   │ (logs)  │\n    └─────────┘   └───────────┘   └─────────┘\n         │              │              │\n         └──────────────┼──────────────┘\n                        │\n                        ▼\n                  ┌───────────┐\n                  │  Grafana  │\n                  │(dashboard)│\n                  └───────────┘\n```\n\n## Security Considerations\n\n- Sensitive fields are automatically masked in logs (password, token, etc.)\n- Configure additional sensitive fields via `sensitiveFields` option\n- Request/response bodies are truncated to prevent large log entries\n- OTLP endpoints should be secured in production\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-471276a4e9d7595df9e947173cbc2a44"}