Package Exports
- @recoverysky/wonder-logger
Readme
⭐Wonder Logger⭐
Production-ready observability toolkit combining OpenTelemetry instrumentation with structured Pino logging for Node.js applications
Overview
⭐Wonder Logger⭐ is a comprehensive observability solution that unifies structured logging and distributed tracing for Node.js applications. Built on industry-standard tools (Pino and OpenTelemetry), it provides a clean, modular API for instrumenting your applications with production-grade observability.
Key Features
- Structured Logging - Fast, JSON-based logging with Pino
- Distributed Tracing - Full OpenTelemetry SDK integration with automatic instrumentation
- Metrics Collection - Prometheus and OTLP metrics exporters
- Multiple Transports - Console, file, OpenTelemetry, and in-memory transports
- Trace Context Correlation - Automatic injection of trace IDs into logs
- Modular Architecture - Composable transports and exporters
- Zero Globals - Factory pattern with no singleton state
- Full TypeScript - Complete type definitions included
- Production Ready - Battle-tested with 319 tests (unit + integration + E2E)
Supported Backends
- Grafana Loki - Log aggregation
- Grafana Tempo - Distributed tracing
- Jaeger - Distributed tracing
- Prometheus - Metrics collection
- Any OTLP-compatible backend (Honeycomb, Datadog, etc.)
Installation
npm install wonder-loggeryarn add wonder-loggerpnpm add wonder-logger📚 Documentation
Comprehensive guides for all features:
📝 Structured Logging Guide - Complete Pino logger documentation
- Transports (console, file, OTEL, memory)
- Plugins (trace context, Morgan HTTP logging)
- RxJS streaming and real-time monitoring
- Testing and best practices
🔭 OpenTelemetry Guide - Telemetry instrumentation and tracing
- Trace exporters (console, OTLP, Jaeger)
- Metrics exporters (Prometheus, OTLP)
- Auto-instrumentation setup
- Manual span instrumentation with
withSpan
⚙️ Configuration Guide - YAML-based configuration system
- Environment variable interpolation
- Config-driven factories
- Validation with Zod schemas
- Multi-environment setup
Quick Start
Basic Logging
import { createLogger } from 'wonder-logger'
const logger = createLogger({
name: 'my-service',
level: 'info'
})
logger.info('Application started')
logger.info({ userId: 123 }, 'User logged in')
logger.error({ err: new Error('Failed') }, 'Operation failed')OpenTelemetry Instrumentation
import { createTelemetry } from 'wonder-logger'
// Initialize telemetry BEFORE other imports
const sdk = createTelemetry({
serviceName: 'my-api',
serviceVersion: '1.0.0',
environment: 'production',
tracing: {
exporter: 'otlp' // or 'console', 'jaeger'
},
metrics: {
exporters: ['prometheus', 'otlp'],
port: 9090
}
})
// Auto-instrumentation is now active!
// HTTP, Express, databases, and more are automatically tracedCombined Logging + Tracing
import { createLogger, createTelemetry, withTraceContext, withSpan } from 'wonder-logger'
// Initialize telemetry
createTelemetry({ serviceName: 'my-api' })
// Create trace-aware logger
const baseLogger = createLogger({ name: 'my-api' })
const logger = withTraceContext(baseLogger)
// Logs will include trace_id and span_id
await withSpan('process-order', async () => {
logger.info('Processing order')
// { level: 30, trace_id: "abc123", span_id: "def456", msg: "Processing order" }
})Config-Driven Setup
⭐Wonder Logger⭐ supports YAML-based configuration with environment variable interpolation:
1. Create wonder-logger.yaml in your project root:
service:
name: ${SERVICE_NAME:-my-api}
version: ${SERVICE_VERSION:-1.0.0}
environment: ${NODE_ENV:-development}
logger:
enabled: true
level: ${LOG_LEVEL:-info}
redact:
- password
- token
transports:
- type: console
pretty: ${LOG_PRETTY:-false}
- type: file
dir: ./logs
fileName: app.log
- type: otel
endpoint: ${OTEL_LOGS_ENDPOINT:-http://localhost:4318/v1/logs}
plugins:
traceContext: true
otel:
enabled: true
tracing:
enabled: true
exporter: ${OTEL_TRACE_EXPORTER:-otlp}
endpoint: ${OTEL_TRACES_ENDPOINT:-http://localhost:4318/v1/traces}
sampleRate: 1.0
metrics:
enabled: true
exporters:
- type: prometheus
port: ${PROMETHEUS_PORT:-9464}
- type: otlp
endpoint: ${OTEL_METRICS_ENDPOINT:-http://localhost:4318/v1/metrics}
exportIntervalMillis: 60000
instrumentation:
auto: true
http: true2. Load configuration in your application:
import { createLoggerFromConfig, createTelemetryFromConfig } from 'wonder-logger'
// Load from default location (wonder-logger.yaml)
const sdk = createTelemetryFromConfig()
const logger = createLoggerFromConfig()
// With custom config path
const logger = createLoggerFromConfig({
configPath: './config/production.yaml'
})
// With runtime overrides
const sdk = createTelemetryFromConfig({
overrides: {
serviceName: 'override-service',
environment: 'staging'
}
})3. Environment variable syntax:
# Required variable (throws error if not set)
service:
name: ${SERVICE_NAME}
# Optional variable with default
service:
name: ${SERVICE_NAME:-my-api}
version: ${npm_package_version:-1.0.0}See Configuration Guide for complete documentation.
Configuration Approaches
⭐Wonder Logger⭐ supports two complementary configuration approaches that work together seamlessly:
1. Programmatic API (Direct Code Configuration)
Best for: Fine-grained control, dynamic configuration, testing, and programmatic use cases.
import { createLogger, createTelemetry } from 'wonder-logger'
// Direct configuration in code
const sdk = createTelemetry({
serviceName: 'my-api',
serviceVersion: '1.0.0',
environment: process.env.NODE_ENV || 'development',
tracing: { exporter: 'otlp' },
metrics: { exporters: ['prometheus'] }
})
const logger = createLogger({
name: 'my-api',
level: process.env.LOG_LEVEL || 'info',
transports: [
createConsoleTransport({ pretty: true }),
createOtelTransport({ serviceName: 'my-api' })
]
})How it works:
- Configuration values are set directly in TypeScript/JavaScript code
- Environment variables are read using
process.envdirectly - Full programmatic control with IDE autocompletion
- Library internals may also read environment variables (e.g.,
OTEL_EXPORTER_OTLP_ENDPOINT)
2. Config-Driven API (YAML Configuration)
Best for: Centralized configuration, multiple environments, deployment flexibility, and declarative setup.
import { createLoggerFromConfig, createTelemetryFromConfig } from 'wonder-logger'
// Load configuration from YAML file
const sdk = createTelemetryFromConfig()
const logger = createLoggerFromConfig()
// With overrides for specific needs
const logger = createLoggerFromConfig({
overrides: { level: 'debug' } // Runtime override
})How it works:
- Configuration is defined in
wonder-logger.yaml(see wonder-logger.example.yaml) - YAML supports environment variable interpolation:
${VAR_NAME}and${VAR_NAME:-default} - The YAML file reads from environment variables (
.envfiles or system environment) - Enables environment-specific configs without code changes
Relationship with .env Files
Both approaches work with .env files, but in different ways:
Programmatic API + .env
// .env file
LOG_LEVEL=debug
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
// Your code reads .env directly
import 'dotenv/config' // Load .env into process.env
const logger = createLogger({
level: process.env.LOG_LEVEL // Read from process.env
})Config-Driven API + .env
# wonder-logger.yaml interpolates .env variables
logger:
level: ${LOG_LEVEL:-info} # Reads LOG_LEVEL from process.env
otel:
tracing:
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}// Your code only needs to load config
import { createLoggerFromConfig } from 'wonder-logger'
const logger = createLoggerFromConfig() // Reads YAML, which reads .envWhen to Use Each Approach
| Use Case | Recommended Approach | Reason |
|---|---|---|
| Development/Testing | Programmatic API | Direct control, easier debugging |
| Production Deployment | Config-Driven API | Environment flexibility, no code changes |
| Library Development | Programmatic API | Full control, testability |
| Multi-Environment Apps | Config-Driven API | Separate YAML per environment |
| Dynamic Configuration | Programmatic API | Runtime decision-making |
| Docker/Kubernetes | Config-Driven API | ConfigMaps, environment injection |
| Local .env Development | Either | Both support .env files |
Best Practices
- Use
.envfor local development secrets (ensure.envis in.gitignore) - Use YAML templates (
wonder-logger.example.yaml) for documentation - Combine both approaches when needed:
// Load base config from YAML, override for specific cases const logger = createLoggerFromConfig({ configPath: './config/production.yaml', overrides: { level: isDebugMode ? 'debug' : 'info' } })
- Environment variable precedence:
- Config-driven: YAML interpolation → Runtime overrides
- Programmatic: Code defaults → Environment variables → Runtime values
Architecture
wonder-logger/
├── Logger (Pino-based)
│ ├── Transports
│ │ ├── Console (with pretty printing)
│ │ ├── File (async I/O)
│ │ ├── OpenTelemetry (OTLP)
│ │ └── Memory (queryable in-memory store)
│ └── Plugins
│ ├── Trace Context (OTEL correlation)
│ └── Morgan Stream (HTTP request logging)
│
└── OpenTelemetry
├── Trace Exporters
│ ├── Console
│ ├── OTLP (Tempo, Jaeger, Honeycomb)
│ └── Jaeger
├── Metrics Exporters
│ ├── Prometheus (pull-based)
│ └── OTLP (push-based)
└── Auto-Instrumentation
└── HTTP, Express, GraphQL, databases, etc.Documentation
Core Modules
- Structured Logging Guide - Complete logger documentation
- OpenTelemetry Guide - Telemetry setup and configuration
- Configuration Guide - YAML-based configuration system
Examples
Multiple Transports
import {
createLogger,
createConsoleTransport,
createFileTransport,
createOtelTransport
} from 'wonder-logger'
const logger = createLogger({
name: 'my-api',
transports: [
createConsoleTransport({ pretty: true, level: 'debug' }),
createFileTransport({ dir: './logs', fileName: 'app.log' }),
createOtelTransport({
serviceName: 'my-api',
endpoint: 'http://localhost:4318/v1/logs'
})
]
})Express Integration
import express from 'express'
import morgan from 'morgan'
import {
createLogger,
createTelemetry,
withTraceContext,
createMorganStream,
withSpan
} from 'wonder-logger'
// Initialize telemetry first
createTelemetry({ serviceName: 'my-api' })
// Create trace-aware logger
const logger = withTraceContext(createLogger({ name: 'my-api' }))
const app = express()
// HTTP request logging
app.use(morgan('combined', { stream: createMorganStream(logger) }))
// Request-scoped logging
app.use((req, res, next) => {
req.logger = logger.child({ requestId: req.headers['x-request-id'] })
next()
})
app.get('/users/:id', async (req, res) => {
const user = await withSpan('fetch-user', async () => {
req.logger.info({ userId: req.params.id }, 'Fetching user')
return await db.users.findById(req.params.id)
})
res.json(user)
})
app.listen(3000, () => {
logger.info({ port: 3000 }, 'Server started')
})Memory Transport for Testing
import {
createLogger,
createMemoryTransport,
getMemoryLogs
} from 'wonder-logger'
const logger = createLogger({
name: 'test',
transports: [createMemoryTransport({ name: 'test-logs' })]
})
logger.info({ userId: 123 }, 'User action')
// Query logs
const logs = getMemoryLogs('test-logs', {
level: 'info',
format: 'parsed'
})
console.log(logs[0].userId) // 123Manual Span Instrumentation
import { withSpan } from 'wonder-logger'
import { metrics } from '@opentelemetry/api'
async function processPayment(orderId: string) {
return withSpan('process-payment', async () => {
// Your business logic
const charge = await stripe.charges.create(...)
// Record custom metrics
const meter = metrics.getMeter('payments')
const counter = meter.createCounter('payments_processed')
counter.add(1, { status: 'success' })
return charge
})
}API Reference
Logger
// Create logger (programmatic)
createLogger(options: LoggerOptions): pino.Logger
// Create logger (config-driven)
createLoggerFromConfig(options?: {
configPath?: string
required?: boolean
overrides?: Partial<LoggerOptions>
}): pino.Logger
// Transports
createConsoleTransport(options?: ConsoleTransportOptions): pino.StreamEntry
createFileTransport(options?: FileTransportOptions): pino.StreamEntry
createOtelTransport(options: OtelTransportOptions): pino.StreamEntry
createMemoryTransport(options?: MemoryTransportOptions): pino.StreamEntry
// Memory transport utilities
getMemoryLogs(name: string, options?: MemoryQueryOptions): RawLogEntry[] | ParsedLogEntry[]
clearMemoryLogs(name: string): void
getMemoryLogSize(name: string): number
getAllMemoryStoreNames(): string[]
disposeMemoryStore(name: string): void
// Memory transport streaming (RxJS)
getMemoryLogStream(name: string): Observable<RawLogEntry> | null
filterByLevel(level: string | string[]): OperatorFunction<RawLogEntry, RawLogEntry>
filterSince(timestamp: number): OperatorFunction<RawLogEntry, RawLogEntry>
withBackpressure(options: BackpressureOptions): OperatorFunction<RawLogEntry, RawLogEntry | RawLogEntry[]>
// Plugins
withTraceContext(logger: pino.Logger): pino.Logger
createMorganStream(logger: pino.Logger): NodeJS.WritableStreamOpenTelemetry
// Create telemetry SDK (programmatic)
createTelemetry(options: TelemetryOptions): TelemetrySDK
// Create telemetry SDK (config-driven)
createTelemetryFromConfig(options?: {
configPath?: string
required?: boolean
overrides?: Partial<TelemetryOptions>
}): TelemetrySDK
// Manual instrumentation
withSpan(spanName: string, fn: () => Promise<T>, tracerName?: string): Promise<T>
// SDK methods
sdk.start(): void
sdk.shutdown(): Promise<void>
sdk.forceFlush(): Promise<void>Configuration
// Load and parse configuration
loadConfig(options?: {
configPath?: string
required?: boolean
}): WonderLoggerConfig | null
// Load from specific file
loadConfigFromFile(filePath: string): WonderLoggerConfig
// Find config file in cwd
findConfigFile(fileName?: string): string | null
// Parse YAML with env var interpolation
parseYamlWithEnv(yamlContent: string): anyEnvironment Variables
Logging
LOG_LEVEL=info # Minimum log level (trace, debug, info, warn, error, fatal)OpenTelemetry
# OTLP Exporter
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_HEADERS='{"x-api-key":"secret"}'
# Jaeger Exporter
JAEGER_ENDPOINT=http://localhost:14268/api/traces
# Metrics
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics
# General
NODE_ENV=productionTesting
⭐Wonder Logger⭐ includes comprehensive test coverage:
- 237 unit tests - Fast, isolated component testing
- 63 integration tests - Real behavior validation
- 19 E2E tests - Production stack validation
# Run all tests
pnpm test
# Run by category
pnpm test:unit
pnpm test:integration
pnpm test:e2e
# Coverage reports
pnpm test:coverage
pnpm test:unit:coverage
# Watch mode
pnpm test:watchPerformance
Logging (Pino)
- 30,000+ logs/second (JSON mode)
- 20,000+ logs/second (pretty mode)
- < 1ms per log entry
- Async by default with background worker threads
OpenTelemetry
- Negligible overhead with batching
- Configurable sampling rates
- Background export workers
Production Best Practices
Use JSON in production
createConsoleTransport({ pretty: false })
Set appropriate log levels
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug'
Use OTLP for centralized collection
createOtelTransport({ serviceName: 'my-api' })
Include trace context for correlation
const logger = withTraceContext(baseLogger)
Use structured data, not string interpolation
logger.info({ userId, orderId }, 'Order placed') // Good logger.info(`Order ${orderId} by ${userId}`) // Bad
Child loggers for scoped context
const requestLogger = logger.child({ requestId })
Configure sampling for high-volume services
createTelemetry({ tracing: { sampleRate: 0.1 } // 10% sampling })
TypeScript
⭐Wonder Logger⭐ is written in TypeScript and provides complete type definitions:
import type {
// Logger types
LoggerOptions,
ConsoleTransportOptions,
FileTransportOptions,
OtelTransportOptions,
MemoryTransportOptions,
MemoryQueryOptions,
RawLogEntry,
ParsedLogEntry,
BackpressureOptions,
// OpenTelemetry types
TelemetryOptions,
TracingOptions,
MetricsOptions,
TelemetrySDK,
// Configuration types
WonderLoggerConfig,
ServiceConfig,
LoggerConfig,
OtelConfig,
TransportConfig,
LoggerPluginsConfig,
TracingConfig,
MetricsConfig,
MetricsExporterConfig,
PrometheusExporterConfig,
OtlpMetricsExporterConfig,
InstrumentationConfig
} from 'wonder-logger'Module Configuration
Your tsconfig.json should include:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"target": "ES2022"
}
}Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Write tests for your changes
- Ensure all tests pass:
pnpm test - Commit with descriptive messages
- Submit a pull request
Development Setup
# Clone repository
git clone https://github.com/jenova-marie/wonder-logger.git
cd wonder-logger
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build
pnpm buildLicense
MIT © jenova-marie
Acknowledgments
⭐Wonder Logger⭐ is built on these excellent open-source projects:
- Pino - Fast and low overhead logging
- OpenTelemetry - Vendor-neutral observability framework
- Grafana - Visualization and observability platform
Support
Made with ✨ by jenova-marie