npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@beethovn/logging

v1.0.0

Published

Structured logging with correlation tracking for the Beethovn platform

Readme

@beethovn/logging

Structured logging with correlation tracking for the Beethovn monorepo.

Features

  • Structured Logging: JSON-formatted logs for easy parsing and analysis
  • Correlation Tracking: Automatic correlation ID propagation across async operations
  • Context Management: Track user ID, tenant ID, and custom context fields
  • Error Integration: Seamless integration with @beethovn/errors package
  • Multiple Log Levels: DEBUG, INFO, WARN, ERROR with priority filtering
  • Transport System: Flexible output to console, files, or custom destinations
  • Child Loggers: Create context-specific loggers with shared configuration

Installation

pnpm add @beethovn/logging

Quick Start

import { Logger, LogLevel } from '@beethovn/logging';

const logger = new Logger({
  level: LogLevel.INFO,
  service: 'payment-service',
  json: true,
});

logger.info('Payment processed', { paymentId: '123', amount: 100 });

Core Concepts

Log Levels

enum LogLevel {
  DEBUG = 'debug',  // Detailed debugging information
  INFO = 'info',    // General informational messages
  WARN = 'warn',    // Warning messages
  ERROR = 'error',  // Error messages
}

Structured Log Entry

Every log entry includes:

interface LogEntry {
  timestamp: string;        // ISO 8601 timestamp
  level: LogLevel;          // Log level
  message: string;          // Log message
  service?: string;         // Service name
  correlationId?: string;   // Request correlation ID
  userId?: string;          // User ID from context
  tenantId?: string;        // Tenant ID from context
  metadata?: Record<string, unknown>;  // Additional data
  error?: {                 // Error details (if present)
    name: string;
    code: string;
    message: string;
    stack?: string;
    metadata?: Record<string, unknown>;
  };
}

Usage Examples

Basic Logging

const logger = new Logger({
  level: LogLevel.INFO,
  service: 'user-service',
});

logger.debug('User lookup started', { userId: '123' });
logger.info('User authenticated', { userId: '123' });
logger.warn('Rate limit approaching', { remaining: 10 });
logger.error('Database connection failed');

Error Logging

import { ErrorFactory } from '@beethovn/errors';

try {
  await processPayment(paymentId);
} catch (error: unknown) {
  const err = ErrorFactory.fromUnknown(error, 'processPayment');
  logger.error('Payment processing failed', err, { paymentId });
}

Correlation Tracking

import { CorrelationContextManager } from '@beethovn/logging';

// In your request handler
const correlationId = CorrelationContextManager.generateCorrelationId();

CorrelationContextManager.run(
  {
    correlationId,
    userId: req.user.id,
    tenantId: req.tenant.id,
  },
  async () => {
    // All logs within this context automatically include correlation data
    logger.info('Processing request');
    await service.execute();
    logger.info('Request completed');
  }
);

Child Loggers

const mainLogger = new Logger({ service: 'api-server' });

// Create specialized logger for payment operations
const paymentLogger = mainLogger.child({ service: 'payment-processor' });

paymentLogger.info('Payment started', { paymentId: '123' });
// Logs with service: 'payment-processor'

Custom Transports

import { Logger, type LogTransport, type LogEntry } from '@beethovn/logging';

class CustomTransport implements LogTransport {
  log(entry: LogEntry): void {
    // Send to external logging service
    externalService.send(entry);
  }
}

const logger = new Logger();
logger.addTransport(new CustomTransport());

Console Transport

import { Logger, ConsoleTransport } from '@beethovn/logging';

const logger = new Logger({ json: false }); // Disable default JSON output
logger.addTransport(new ConsoleTransport({
  json: false,      // Human-readable format
  colorize: true,   // ANSI color codes
}));

logger.info('User logged in', { userId: '123' });
// Output: 2024-01-01T12:00:00.000Z [INFO] user-service User logged in
//   Metadata: { "userId": "123" }

Correlation Context Manager

The CorrelationContextManager uses Node.js AsyncLocalStorage to maintain context across async operations without explicit parameter passing.

API

class CorrelationContextManager {
  // Run code with correlation context
  static run<T>(context: LogContext, fn: () => T): T;
  
  // Get current context
  static getContext(): LogContext | undefined;
  
  // Get correlation ID from context
  static getCorrelationId(): string | undefined;
  
  // Get user ID from context
  static getUserId(): string | undefined;
  
  // Get tenant ID from context
  static getTenantId(): string | undefined;
  
  // Update current context
  static updateContext(updates: Partial<LogContext>): void;
  
  // Generate new correlation ID
  static generateCorrelationId(): string;
}

Express Middleware Example

import { CorrelationContextManager } from '@beethovn/logging';

app.use((req, res, next) => {
  const correlationId = 
    req.headers['x-correlation-id'] as string ||
    CorrelationContextManager.generateCorrelationId();
  
  CorrelationContextManager.run(
    {
      correlationId,
      userId: req.user?.id,
      tenantId: req.tenant?.id,
    },
    () => next()
  );
});

Event Handler Example

eventBus.on('payment.created', async (event) => {
  CorrelationContextManager.run(
    {
      correlationId: event.correlationId,
      userId: event.userId,
    },
    async () => {
      logger.info('Processing payment event', { paymentId: event.paymentId });
      await processPayment(event);
      logger.info('Payment event processed');
    }
  );
});

Integration with @beethovn/errors

The logger automatically extracts and formats error details from BeethovnError instances:

import { ValidationError } from '@beethovn/errors';

const error = new ValidationError('Invalid email format', 'email');
logger.error('Validation failed', error, { userId: '123' });

// Log output includes:
// {
//   "level": "error",
//   "message": "Validation failed",
//   "error": {
//     "name": "ValidationError",
//     "code": "VALIDATION_ERROR",
//     "message": "Invalid email format",
//     "stack": "...",
//     "metadata": { "field": "email" }
//   },
//   "metadata": { "userId": "123" }
// }

Configuration

Logger Config

interface LoggerConfig {
  level: LogLevel;          // Minimum log level (default: INFO)
  service: string;          // Service name (default: 'beethovn')
  json: boolean;            // JSON output format (default: true)
  timestamps: boolean;      // Include timestamps (default: true)
  colorize: boolean;        // ANSI color codes (default: false)
}

Log Context

interface LogContext {
  correlationId?: string;   // Request correlation ID
  userId?: string;          // Authenticated user ID
  tenantId?: string;        // Multi-tenant ID
  service?: string;         // Service name override
  [key: string]: unknown;   // Custom fields
}

Best Practices

1. Use Appropriate Log Levels

logger.debug('Cache hit', { key });           // Development debugging
logger.info('User registered', { userId });   // Business events
logger.warn('Cache miss', { key });           // Potential issues
logger.error('Database error', err);          // Errors requiring attention

2. Include Structured Metadata

// ✅ Good: Structured metadata
logger.info('Order placed', {
  orderId: '123',
  userId: '456',
  total: 99.99,
  items: 3,
});

// ❌ Bad: Unstructured string
logger.info(`Order 123 placed by user 456 for $99.99`);

3. Always Use Correlation Context

// ✅ Good: Context-wrapped handler
app.post('/orders', (req, res) => {
  CorrelationContextManager.run(
    {
      correlationId: req.headers['x-correlation-id'] || generateId(),
      userId: req.user.id,
    },
    async () => {
      await createOrder(req.body);
    }
  );
});

// ❌ Bad: No correlation tracking
app.post('/orders', async (req, res) => {
  await createOrder(req.body);
});

4. Create Service-Specific Loggers

// ✅ Good: Service-specific logger
const paymentLogger = new Logger({ service: 'payment-service' });

// ❌ Bad: Generic logger everywhere
const logger = new Logger({ service: 'app' });

5. Log Errors with Context

// ✅ Good: Error with context
try {
  await operation();
} catch (error: unknown) {
  const err = ErrorFactory.fromUnknown(error);
  logger.error('Operation failed', err, { operationId, userId });
}

// ❌ Bad: Generic error log
try {
  await operation();
} catch (error) {
  logger.error('Error occurred', error);
}

Log Output Examples

JSON Output (Default)

{
  "timestamp": "2024-01-01T12:00:00.000Z",
  "level": "info",
  "message": "Payment processed",
  "service": "payment-service",
  "correlationId": "cor-1704110400000-a1b2c3d4",
  "userId": "user-123",
  "tenantId": "tenant-456",
  "metadata": {
    "paymentId": "pay-789",
    "amount": 99.99,
    "currency": "USD"
  }
}

Formatted Output

2024-01-01T12:00:00.000Z [INFO] payment-service (cor-1704110400000-a1b2c3d4) Payment processed
  Metadata: {
    "paymentId": "pay-789",
    "amount": 99.99,
    "currency": "USD"
  }

Error Output

{
  "timestamp": "2024-01-01T12:00:00.000Z",
  "level": "error",
  "message": "Payment processing failed",
  "service": "payment-service",
  "correlationId": "cor-1704110400000-a1b2c3d4",
  "metadata": {
    "paymentId": "pay-789"
  },
  "error": {
    "name": "DatabaseError",
    "code": "DATABASE_ERROR",
    "message": "Connection timeout",
    "stack": "DatabaseError: Connection timeout\n    at ...",
    "metadata": {
      "operation": "insert",
      "table": "payments"
    }
  }
}

Testing

import { describe, it, expect, vi } from 'vitest';
import { Logger, LogLevel } from '@beethovn/logging';

describe('MyService', () => {
  it('should log operations', () => {
    const logger = new Logger({ json: true });
    const logSpy = vi.spyOn(console, 'log');
    
    logger.info('Operation completed', { id: '123' });
    
    expect(logSpy).toHaveBeenCalledWith(
      expect.stringContaining('"message":"Operation completed"')
    );
  });
});

Migration Guide

From console.log

// Before
console.log('User logged in:', userId);

// After
logger.info('User logged in', { userId });

From winston/bunyan

// Before (winston)
const logger = winston.createLogger({
  transports: [new winston.transports.Console()],
});

// After
const logger = new Logger({
  service: 'my-service',
});
logger.addTransport(new ConsoleTransport());

Performance Considerations

  • Log level filtering happens before serialization (zero overhead for filtered logs)
  • JSON serialization only occurs for logs that will be output
  • Correlation context uses AsyncLocalStorage (minimal overhead)
  • Child loggers share transports (no duplication)

TypeScript Support

Full TypeScript support with strict types:

import type { LogEntry, LogContext, LoggerConfig } from '@beethovn/logging';

const config: LoggerConfig = {
  level: LogLevel.INFO,
  service: 'my-service',
  json: true,
  timestamps: true,
  colorize: false,
};

const context: LogContext = {
  correlationId: 'cor-123',
  userId: 'user-456',
};

License

MIT


Package Version: 1.0.0
Dependencies: @beethovn/errors
Node Version: >= 18.0.0