@emeryld/otel-bootstrap
v0.1.7
Published
Node helper to bootstrap OpenTelemetry traces + logs with OTLP exports
Readme
@rumbl/otel-bootstrap
Minimal OpenTelemetry bootstrap for Node services that sends traces and logs to an OTLP collector. It is safe to import once at process startup, idempotently patches console.* to emit log records with trace context, and exposes a helper logger that tack on trace_id + span_id attributes. The package is published on npm as @rumbl/otel-bootstrap, so you can add it directly to any Node project.
Installation
npm install @rumbl/otel-bootstrapThen either import '@rumbl/otel-bootstrap'; at the top of your entrypoint or preload it via NODE_OPTIONS (see below).
Runtime behavior
- Auto-instruments Node core modules via
@opentelemetry/auto-instrumentations-node(HTTP, gRPC, Express, etc.). - Sends traces to OTLP (default HTTP protocol +
/v1/traces). - Registers a log emitter that exports OTLP log records (default HTTP +
/v1/logs). - Automatically patches
console.log/info/warn/error/debugto emit correlated logs. - Exposes
createLogger,initTelemetry, andshutdownTelemetryfor manual control in tests.
Environment variables
| Name | Purpose | Default |
| --- | --- | --- |
| OTEL_SERVICE_NAME | Service name attached to traces/logs | rumbl-node-service |
| OTEL_EXPORTER_OTLP_ENDPOINT | Base OTLP endpoint for traces & logs | http://localhost:4318 |
| OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf or grpc | http/protobuf |
| OTEL_RESOURCE_ATTRIBUTES | key=value pairs separated by commas (e.g. environment=dev,team=rumbl) | – |
| OTEL_LOG_LEVEL | Sets the OpenTelemetry diagnostic logger (info, debug, warn, error, verbose) | info |
Copy ./.env.example to .env (or load the file via dotenv) so your service defines these values before importing @rumbl/otel-bootstrap. The example already documents the defaults shown above.
If you are running the backend alongside the observability stack, point to the collector service inside the Compose network:
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_SERVICE_NAME=my-api
OTEL_RESOURCE_ATTRIBUTES=environment=devWhen the backend runs outside of the compose stack (e.g. local host), point at the exposed port:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318Usage
Import first
import '@rumbl/otel-bootstrap'; import './server';This is the simplest workflow when you can control the entrypoint. The OpenTelemetry instrumentations (e.g. dataloader, Express,
@grpc/grpc-js, ioredis, socket.io) must be initialized before their targets are required—if you see warnings likeModule express has been loaded before @opentelemetry/instrumentation-express, move the bootstrap import to the very top of your entrypoint or preload the bootstrap viaNODE_OPTIONSbefore the problematic modules load.Preload via Node options (useful in Docker):
ENV NODE_OPTIONS="--require ./node_modules/@rumbl/otel-bootstrap/dist/otel.cjs" CMD ["node", "dist/server.js"]When Node starts, it will require
dist/otel.cjs, which triggers the same initialization path.Optional logger helper
import { createLogger } from '@rumbl/otel-bootstrap'; const logger = createLogger('server'); logger.info('Server ready', { port: process.env.PORT });
The helper emits structured log records and automatically adds `trace_id`/`span_id` when a span is active.
### Typed telemetry models
The bootstrap now exports `ApplicationLogAttributes`, `RequestLogAttributes`, `CacheLogAttributes`, `CacheTraceAttributes`,
`ScheduleLogAttributes`, `PresetAttributes`, `PresetOperationAttributes`, `SocketEventAttributes`, and a `TelemetryLogger<T>`
so you can align your log payloads with the Prisma-backed schemas we store.
```ts
import { createLogger, ApplicationLogAttributes } from '@rumbl/otel-bootstrap';
const logger = createLogger<ApplicationLogAttributes>('server');
logger.info('Startup completed', {
name: 'startup.complete',
tags: ['bootstrap']
});ApplicationLogAttributes mirrors the ApplicationLog model and supports fields like name, groupId, tags,
level, and meta. Use RequestLogAttributes when emitting HTTP logs, CacheLogAttributes/CacheTraceAttributes
for cache operations, and ScheduleLogAttributes/SocketEventAttributes for background jobs and realtime traffic.
If you prefer one logger that switches between log types, use the discriminated union:
import { CacheOperation, DiscUnionAttributes, createLogger } from '@rumbl/otel-bootstrap';
const logger = createLogger<DiscUnionAttributes>('server');
logger.info('Cache read', {
logType: 'cache_trace',
key: 'user:1234',
operation: CacheOperation.hit,
durationMs: 2
});We also export helpers that are typed for the common log types:
import {
CacheOperation,
HttpMethod,
createApplicationLogger,
createRequestLogger,
createCacheLogger,
createCacheTraceLogger
} from '@rumbl/otel-bootstrap';
const appLogger = createApplicationLogger('server');
const requestLogger = createRequestLogger('http');
const cacheLogger = createCacheLogger('redis');
const cacheTraceLogger = createCacheTraceLogger('redis');
appLogger.info('Startup complete', { name: 'startup.complete', tags: ['bootstrap'] });
requestLogger.info('Request handled', {
name: 'GET /health',
method: HttpMethod.get,
path: '/health',
status: 200,
durationMs: 12
});
cacheLogger.info('Cache entry refreshed', {
name: 'user:1234',
ttlMs: 60000
});
cacheTraceLogger.info('Cache lookup', {
key: 'user:1234',
operation: CacheOperation.hit,
durationMs: 2
});Service shutdown (tests / scripts)
If you need to tear down instrumentation (tests, graceful shutdown), call:
import { shutdownTelemetry } from '@rumbl/otel-bootstrap';
await shutdownTelemetry();Code examples
Entry-point bootstrap
Importing @rumbl/otel-bootstrap before anything else guarantees the SDK, exporters, and console patching are ready when your service starts emitting traces or logs. Combine that with the helper logger for structured data:
import '@rumbl/otel-bootstrap';
import http from 'node:http';
import { HttpMethod, createLogger } from '@rumbl/otel-bootstrap';
const logger = createLogger('server');
const server = http.createServer((req, res) => {
logger.info('Request received', {
name: `${req.method ?? 'GET'} ${req.url ?? '/'}`,
method: (req.method ?? 'GET').toLowerCase() as HttpMethod,
path: req.url ?? '/'
});
res.end('Telemetry service is ready');
});
server.listen(process.env.PORT ?? 3000, () => {
logger.info('Server listening', { port: 3000 });
});The helper logger automatically enriches every record with trace_id/span_id when a span is active and your console.* calls still emit OTLP logs.
Manual control (tests or scripts)
When you need to gate telemetry initialization—such as in a test harness or a script that must finish flushing before exiting—await initTelemetry and later call shutdownTelemetry:
import { initTelemetry, shutdownTelemetry } from '@rumbl/otel-bootstrap';
beforeAll(async () => {
await initTelemetry();
});
afterAll(async () => {
await shutdownTelemetry();
});Wrap the startup/shutdown pair in try/finally blocks when your script should keep running so exporters flush before the process exits.
Troubleshooting
- Telemetry does not appear: ensure
OTEL_EXPORTER_OTLP_ENDPOINTis reachable from your process and the collector is running (obs-stack urls). - Collector rejects logs/traces: confirm
OTEL_EXPORTER_OTLP_PROTOCOLmatches the protocol the collector listens on (http/protobuffor port 4318,grpcfor 4317). - Env vars not loaded: make sure
.envor Docker Compose passes the variables before importing the bootstrap. - Console logs missing: the package patches
console.*once telemetry initializes; ensure initialization runs before you log. - Module already loaded warnings: messages such as
Module dataloader has been loaded before @opentelemetry/instrumentation-dataloader(also reported for Express,@grpc/grpc-js, ioredis, socket.io, etc.) mean those modules were required before telemetry initialized. Re-order your imports so@rumbl/otel-bootstrapruns first, or preload it viaNODE_OPTIONS="--require ./node_modules/@rumbl/otel-bootstrap/dist/otel.cjs"to guarantee the instrumentations are registered before the instrumented libraries load.
