@atrim/instrument-node
v0.5.1
Published
OpenTelemetry instrumentation for Node.js with centralized YAML configuration
Maintainers
Readme
@atrim/instrument-node
One-line OpenTelemetry for Node.js
OpenTelemetry instrumentation for Node.js with centralized YAML configuration. Works with any Node.js framework (Express, Fastify, Koa, Hono) and runtime (Node.js, Bun, Deno).
Quick Start
1. Install
npm install @atrim/instrument-node2. Initialize (at the top of your app)
Promise API (Traditional)
import { initializeInstrumentation } from '@atrim/instrument-node'
await initializeInstrumentation()Effect API (Recommended)
import { Effect } from 'effect'
import { initializeInstrumentationEffect } from '@atrim/instrument-node'
await Effect.runPromise(initializeInstrumentationEffect())3. Done! Your app is now sending traces to OpenTelemetry.
By default, traces go to http://localhost:4318. To send to a remote collector:
await initializeInstrumentation({
otlp: { endpoint: 'https://otel-collector.company.com:4318' }
})What just happened?
Auto-detected and configured:
- ✅ Service name from
package.json - ✅ OTLP endpoint (local or remote)
- ✅ Auto-instrumentation for Express, HTTP, Fastify, etc.
- ✅ Graceful shutdown on SIGTERM/SIGINT
Optional: Control What Gets Traced
Create instrumentation.yaml in your project root:
version: "1.0"
instrumentation:
enabled: true
instrument_patterns:
- pattern: "^app\\." # ✅ Trace application operations
ignore_patterns:
- pattern: "^health\\." # ❌ Skip health checksThat's it!
Features
- Zero-config - Works out of the box with sensible defaults
- Universal - Node.js 20+, Bun 1.0+, Deno 1.40+
- Framework-agnostic - Express, Fastify, Koa, Hono, vanilla HTTP
- Effect-TS first - Typed error handling with Effect (optional)
- Pattern-based filtering - Control which spans are created via YAML
- HTTP filtering - Prevent noisy health checks and metrics endpoints
- Centralized config - YAML file, URL, or environment variable
- Production-ready - Graceful shutdown, error handling, performance optimized
Documentation
Full documentation is available in the main repository:
Core Docs
- 📖 Getting Started - 5-minute setup guide
- ⚙️ Configuration - YAML configuration reference
- 📋 Examples - 8+ working examples
- 🔧 Troubleshooting - Common issues and solutions
- 📚 API Reference - Complete API documentation
Specialized Guides
- 🌐 HTTP Filtering - Prevent noisy traces
- 🔄 Effect Integration - Effect-TS patterns
- 🧪 Testing Guide - How to test instrumented apps
Installation
npm
npm install @atrim/instrument-nodeyarn
yarn add @atrim/instrument-nodepnpm
pnpm add @atrim/instrument-nodeBun
bun add @atrim/instrument-nodeUsage Examples
Express Application
import express from 'express'
import { initializeInstrumentation } from '@atrim/instrument-node'
// Initialize at startup
await initializeInstrumentation()
const app = express()
app.get('/users', async (req, res) => {
// Automatically traced!
const users = await fetchUsers()
res.json(users)
})
app.listen(3000)Effect-TS Application
import { Effect, Layer } from 'effect'
import { EffectInstrumentationLive } from '@atrim/instrument-node/effect'
const program = Effect.gen(function* () {
// Automatically traced with Effect.withSpan()
yield* myOperation.pipe(Effect.withSpan('app.operation'))
}).pipe(
Effect.provide(EffectInstrumentationLive)
)
await Effect.runPromise(program)Effect-TS Span Annotation Helpers
The library provides 9 production-tested annotation helpers for enriching spans with semantic attributes:
import { Effect } from 'effect'
import {
annotateUser,
annotateBatch,
annotateDataSize,
annotateLLM,
annotateQuery,
annotateHttpRequest,
annotateError,
annotatePriority,
annotateCache,
autoEnrichSpan,
withAutoEnrichedSpan
} from '@atrim/instrument-node/effect'
// Example: Batch processing with automatic enrichment
const processBatch = Effect.gen(function* () {
// Auto-enrich with Effect metadata (fiber ID, status, parent span info)
yield* autoEnrichSpan()
// Add user context
yield* annotateUser('user-123', '[email protected]')
// Add batch metadata
yield* annotateBatch(100, 10) // 100 items in batches of 10
// Process items
const results = yield* processItems(items)
// Update with results
yield* annotateBatch(100, 10, results.success, results.failures)
return results
}).pipe(Effect.withSpan('batch.process'))
// Or use the convenience wrapper
const processWithAutoEnrich = withAutoEnrichedSpan('batch.process')(
Effect.gen(function* () {
yield* annotateBatch(100, 10)
return yield* processItems(items)
})
)Available annotation helpers:
annotateUser(userId, email?, username?)- User contextannotateDataSize(bytes, items, compressionRatio?)- Data size metricsannotateBatch(totalItems, batchSize, successCount?, failureCount?)- Batch operationsannotateLLM(model, provider, tokens?)- LLM operations (GPT, Claude, etc.)annotateQuery(query, duration?, rowCount?, database?)- Database queriesannotateHttpRequest(method, url, statusCode?, contentLength?)- HTTP requestsannotateError(error, recoverable, errorType?)- Error contextannotatePriority(priority, reason?)- Operation priorityannotateCache(hit, key, ttl?)- Cache operations
Auto-enrichment utilities:
autoEnrichSpan()- Automatically add Effect metadata (fiber ID, status, parent span info)withAutoEnrichedSpan(name, options?)- Wrapper combiningEffect.withSpan()+ auto-enrichment
All helpers return Effect.Effect<void> and use Effect.annotateCurrentSpan() under the hood.
Bun Runtime
import { initializeInstrumentation } from '@atrim/instrument-node'
// Works exactly the same as Node.js
await initializeInstrumentation()
Bun.serve({
port: 3000,
fetch(req) {
return new Response('Hello from Bun!')
}
})Remote Configuration
import { initializeInstrumentation } from '@atrim/instrument-node'
await initializeInstrumentation({
configUrl: 'https://config.company.com/instrumentation.yaml',
cacheTimeout: 300_000 // 5 minutes
})Configuration
Priority Order (Highest to Lowest)
- Explicit Config Object - Passed programmatically
- Environment Variable -
ATRIM_INSTRUMENTATION_CONFIG - Project Root File -
./instrumentation.yaml - Default Config - Built-in defaults
instrumentation.yaml Example
version: "1.0"
instrumentation:
enabled: true
logging: "on"
# Pattern-based span filtering
instrument_patterns:
- pattern: "^app\\."
enabled: true
description: "Application operations"
- pattern: "^storage\\."
enabled: true
description: "Storage layer"
ignore_patterns:
- pattern: "^health\\."
description: "Health checks"
- pattern: "^metrics\\."
description: "Metrics endpoints"
# Effect-TS specific (optional)
effect:
auto_extract_metadata: trueSee Configuration Guide for complete reference.
HTTP Request Filtering
IMPORTANT: HTTP filtering requires explicit configuration to prevent noisy traces.
Add HTTP Filtering Patterns
# instrumentation.yaml
instrumentation:
http_filtering:
enabled: true
ignore_routes:
- pattern: "^/health$"
- pattern: "^/metrics$"
- pattern: "^/api/internal/"
- pattern: "http://.*:4318/v1/traces" # Prevent OTLP trace loops!See HTTP Filtering Guide for details.
API Reference
Standard API (Promise-based)
// Main initialization
initializeInstrumentation(options?: SdkInitializationOptions): Promise<NodeSDK | null>
// Pattern matching only (skip SDK)
initializePatternMatchingOnly(options?: ConfigLoaderOptions): Promise<void>
// Configuration
loadConfig(options?: ConfigLoaderOptions): Promise<InstrumentationConfig>
// Service detection
detectServiceInfo(): Promise<ServiceInfo>
getServiceName(): Promise<string>
getServiceVersion(): Promise<string>Effect API
// Main initialization (Effect)
initializeInstrumentationEffect(options?: SdkInitializationOptions): Effect.Effect<NodeSDK | null, InitializationError | ConfigError>
// Effect-TS Layer
EffectInstrumentationLive: Layer.Layer<Tracer.Tracer, ConfigError, never>
// Service detection (Effect)
detectServiceInfoEffect: Effect.Effect<ServiceInfo, ServiceDetectionError>
getServiceNameEffect: Effect.Effect<string, ServiceDetectionError>
getServiceVersionEffect: Effect.Effect<string, never>See API Reference for complete documentation.
Runtimes Supported
- ✅ Node.js 20.0.0+
- ✅ Bun 1.0.0+
- ✅ Deno 1.40.0+ (via npm compatibility)
Frameworks Supported
- ✅ Express - Auto-instrumentation included
- ✅ Fastify - Auto-instrumentation included
- ✅ Koa - Auto-instrumentation included
- ✅ Hono - Works with manual spans
- ✅ Vanilla HTTP - Works with any Node.js HTTP server
- ✅ Effect-TS - First-class integration with Effect.withSpan()
Examples
See the examples directory for complete working examples:
- Express - Basic Express app
- Effect-TS - Advanced Effect patterns
- Effect Platform - Pure Effect HTTP server
- Vanilla TypeScript - Standard Node.js
- Bun Runtime - Bun-specific example
- Remote Config - Load config from URL
- Multi-Service - Distributed tracing
Troubleshooting
See Troubleshooting Guide for common issues and solutions.
Quick Fixes
No traces appearing?
- Check collector is running:
docker run -p 4318:4318 otel/opentelemetry-collector - Check endpoint:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
Too many traces?
- Add HTTP filtering patterns (health checks, metrics, OTLP exports)
- See HTTP Filtering Guide
Effect-TS spans not appearing?
- Make sure you're using
Effect.withSpan() - Provide
EffectInstrumentationLivelayer
Contributing
Contributions welcome! See main repository for guidelines.
License
MIT © Atrim AI
Related Packages
- @atrim/instrument-core - Internal shared logic (private)
- @atrim/instrument-web - Browser/web support (Phase 1)
