@jenova-marie/wonder-logger - v1.0.12
    Preparing search index...

    @jenova-marie/wonder-logger - v1.0.12

    Wonder Logger Logo

    ⭐Wonder Logger⭐

    Production-ready observability toolkit combining OpenTelemetry instrumentation with structured Pino logging for Node.js applications

    Build Status codecov Node.js npm version TypeScript PRs Welcome License: MIT


    ⭐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.

    • 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)
    • Grafana Loki - Log aggregation
    • Grafana Tempo - Distributed tracing
    • Jaeger - Distributed tracing
    • Prometheus - Metrics collection
    • Any OTLP-compatible backend (Honeycomb, Datadog, etc.)
    npm install wonder-logger
    
    yarn add wonder-logger
    
    pnpm add wonder-logger
    

    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

    ⭐Wonder Logger⭐ uses YAML-based configuration for production deployments. For programmatic usage, see the Configuration Guide.

    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:
    # Console transport (JSON format in production)
    - type: console
    pretty: ${LOG_PRETTY:-false}

    # File transport (relative paths resolve from config file location)
    - type: file
    dir: ./logs
    fileName: app.log

    # Memory transport (for testing and runtime log inspection)
    - type: memory
    name: ${SERVICE_NAME:-my-api}
    maxSize: 10000
    level: debug

    # OpenTelemetry transport (send to Loki, etc.)
    - type: otel
    endpoint: ${OTEL_LOGS_ENDPOINT:-http://localhost:4318/v1/logs}

    plugins:
    # Inject trace_id and span_id into logs
    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: true

    2. Load configuration in your application:

    import { createLoggerFromConfig, createTelemetryFromConfig } from 'wonder-logger'

    // Load from wonder-logger.yaml in project root
    const sdk = createTelemetryFromConfig()
    const logger = createLoggerFromConfig()

    // Now start logging with full observability
    logger.info('Application started')
    logger.info({ userId: 123 }, 'User logged in')

    3. Query memory logs programmatically:

    import { getMemoryLogs } from 'wonder-logger'

    // Query logs from last 5 minutes
    const recentLogs = getMemoryLogs('my-api', {
    since: Date.now() - 300000,
    level: ['error', 'warn'],
    format: 'parsed'
    })

    console.log(recentLogs)

    4. 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}

    For programmatic API usage, see Configuration Guide - Programmatic API.

    ⚠️ IMPORTANT: ⭐Wonder Logger⭐ uses Pino for structured logging. The data object MUST come BEFORE the message string:

    // ✅ CORRECT - Data object first, then message
    logger.info({ userId: 123 }, 'User logged in')
    logger.debug({ endpoint: '/api/users' }, 'Processing request')
    logger.error({ err, code: 'DB_ERROR' }, 'Database connection failed')

    // ❌ INCORRECT - Message first (data will be lost!)
    logger.info('User logged in', { userId: 123 }) // userId is NOT logged!
    logger.debug('Processing request', { endpoint: '/api/users' }) // endpoint is NOT logged!

    Why this matters: When you pass arguments in the wrong order, Pino treats the message string as the merge object and converts your data object to "[object Object]" in the message field. Your structured data is completely lost.

    API Signature:

    logger.info([mergingObject], [message], [...interpolationValues])
    logger.debug([mergingObject], [message], [...interpolationValues])
    logger.error([mergingObject], [message], [...interpolationValues])
    // All log levels follow this pattern

    This applies to all log levels: trace(), debug(), info(), warn(), error(), and fatal().

    Examples:

    // Data object only (no message)
    logger.info({ userId: 123, action: 'login' })

    // Message only (no data)
    logger.info('Server started')

    // Data object + message (most common)
    logger.info({ userId: 123 }, 'User logged in')

    // Data object + message + interpolation
    logger.info({ userId: 123 }, 'User %s logged in at %s', username, timestamp)

    // Error logging (err is serialized automatically by Pino)
    try {
    await riskyOperation()
    } catch (err) {
    logger.error({ err, userId: 123 }, 'Operation failed')
    }

    ⭐Wonder Logger⭐ uses YAML configuration files for production deployments, providing centralized configuration, environment variable interpolation, and easy multi-environment setup.

    Key benefits:

    • Environment flexibility - No code changes needed for different environments
    • Centralized settings - All configuration in one place
    • Variable interpolation - Use ${VAR_NAME} or ${VAR_NAME:-default} syntax
    • Validation - Automatic schema validation with helpful error messages
    • Version control friendly - Configuration as code

    Example:

    # wonder-logger.yaml
    service:
    name: ${SERVICE_NAME:-my-api}

    logger:
    level: ${LOG_LEVEL:-info}
    transports:
    - type: console
    pretty: false
    - type: memory
    name: ${SERVICE_NAME}
    maxSize: 10000

    otel:
    enabled: true
    tracing:
    exporter: otlp
    endpoint: ${OTEL_ENDPOINT}
    // Your application
    import { createLoggerFromConfig, createTelemetryFromConfig } from 'wonder-logger'

    const sdk = createTelemetryFromConfig()
    const logger = createLoggerFromConfig()

    See the complete Configuration Guide for:

    • Full schema documentation
    • Environment variable syntax
    • Multi-environment setup
    • Programmatic API (for dynamic configuration)
    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.
    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'
    })
    ]
    })
    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')
    })
    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) // 123
    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
    })
    }
    // 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.WritableStream
    // 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>
    // 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): any
    LOG_LEVEL=info  # Minimum log level (trace, debug, info, warn, error, fatal)
    
    # 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=production

    ⭐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:watch
    • 30,000+ logs/second (JSON mode)
    • 20,000+ logs/second (pretty mode)
    • < 1ms per log entry
    • Async by default with background worker threads
    • Negligible overhead with batching
    • Configurable sampling rates
    • Background export workers
    1. Use JSON in production

      createConsoleTransport({ pretty: false })
      
    2. Set appropriate log levels

      level: process.env.NODE_ENV === 'production' ? 'info' : 'debug'
      
    3. Use OTLP for centralized collection

      createOtelTransport({ serviceName: 'my-api' })
      
    4. Include trace context for correlation

      const logger = withTraceContext(baseLogger)
      
    5. Use structured data, not string interpolation

      logger.info({ userId, orderId }, 'Order placed')  // Good
      logger.info(`Order ${orderId} by ${userId}`) // Bad
    6. Child loggers for scoped context

      const requestLogger = logger.child({ requestId })
      
    7. Configure sampling for high-volume services

      createTelemetry({
      tracing: { sampleRate: 0.1 } // 10% sampling
      })

    ⭐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'

    Your tsconfig.json should include:

    {
    "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "target": "ES2022"
    }
    }

    Contributions are welcome! Please follow these guidelines:

    1. Fork the repository
    2. Create a feature branch: git checkout -b feature/my-feature
    3. Write tests for your changes
    4. Ensure all tests pass: pnpm test
    5. Commit with descriptive messages
    6. Submit a pull request
    # 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 build

    MIT © jenova-marie

    ⭐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

    Made with ✨ by jenova-marie