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

@periodic/iridium

v1.0.0

Published

Production-grade, framework-agnostic structured logging library for Node.js with TypeScript support

Readme

🌟 Periodic Iridium

npm version License: MIT TypeScript

Production-grade, framework-agnostic structured logging library for Node.js with TypeScript support

Part of the Periodic series of Node.js packages by Uday Thakur.


💡 Why Iridium?

Iridium gets its name from the rare, dense metallic element known for its extreme durability and resistance to corrosion — just like how this library provides durable, reliable logging that won't fail under pressure.

In chemistry, iridium is one of the most corrosion-resistant metals known, found in the Earth's crust and meteorites. Similarly, @periodic/iridium was forged through real-world production experience to create logging infrastructure that's both resilient and elegant.

The name represents:

  • Durability: Never throws, never fails, production-tested
  • Clarity: Clean, structured logs with consistent formatting
  • Precision: Type-safe with strict TypeScript support
  • Rarity: A truly dependency-free, first-class logging core

Just as iridium is a fundamental building block in high-precision instruments, @periodic/iridium serves as the foundational logging layer for production-grade Node.js applications.


🎯 Why Choose Iridium?

Building robust applications requires reliable, structured logging, but most solutions come with significant challenges:

  • Existing loggers (Winston, Pino, Bunyan) are heavy with many dependencies
  • Framework-coupled solutions make library reuse difficult
  • Global state in loggers causes problems in multi-tenant apps
  • Configuration complexity leads to maintenance nightmares
  • Wrapper libraries add layers without solving core issues

Periodic Iridium provides the perfect solution:

Zero dependencies - Pure TypeScript logging core
Framework-agnostic - Safe for use in both libraries and applications
Five log levels - debug, info, warn, error, fatal
Structured logging with first-class metadata support
Child loggers with immutable context inheritance
Custom transports - Console, Memory, and extensible
Pretty & JSON formatters with TTY-aware colorization
Sensitive data redaction built-in
Type-safe with strict TypeScript from the ground up
No global state - No side effects on import
Production-ready - Never throws, graceful error handling


📦 Installation

npm install @periodic/iridium

Or with yarn:

yarn add @periodic/iridium

No other dependencies required! Iridium is completely dependency-free.


🚀 Quick Start

import { createLogger, ConsoleTransport } from '@periodic/iridium';

const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
});

// Start logging
logger.info('Application started');
logger.warn('Low memory detected', { available: '512MB' });
logger.error('Database connection failed', { host: 'db.example.com' });

// With metadata
logger.info('User logged in', {
  userId: 'user_123',
  email: '[email protected]',
  ipAddress: '192.168.1.1',
});

Console Output:

[2024-02-13T10:30:00.000Z] INFO  Application started
[2024-02-13T10:30:01.000Z] WARN  Low memory detected {"available":"512MB"}
[2024-02-13T10:30:02.000Z] ERROR Database connection failed {"host":"db.example.com"}

🧠 Core Concepts

The createLogger Function

  • createLogger is the primary factory function
  • Returns a configured Logger instance
  • Accepts flexible configuration options
  • This is the main entry point for all applications
  • No global state, safe for multi-tenant apps

Typical usage:

  • Application code creates loggers with createLogger()
  • Libraries export logger instances for consumers
  • Tests create isolated loggers with MemoryTransport
const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
  processors: [],
  redact: { paths: ['password'] }
});

The Logger Class

  • Logger is the core logging class
  • Provides five logging methods: debug, info, warn, error, fatal
  • Supports child logger creation with context
  • Never throws - all errors routed to onError handler
  • Immutable context merging for safety

Design principle:

Applications create loggers, libraries use loggers, frameworks extend loggers.

// Logging
logger.info('User registered', { userId: '123' });

// Child logger with context
const requestLogger = logger.child({ requestId: 'req_abc' });
requestLogger.info('Processing request'); // Includes requestId

// Error handling
try {
  await riskyOperation();
} catch (error) {
  logger.error('Operation failed', { error });
}

✨ Features

🎚️ Five Log Levels

Complete control over logging verbosity:

logger.debug('Detailed debugging info');
logger.info('Informational message');
logger.warn('Warning message');
logger.error('Error occurred');
logger.fatal('System failure');

Level Hierarchy:

debug < info < warn < error < fatal

Set minimum level to filter logs:

const logger = createLogger({
  level: 'warn', // Only warn, error, fatal will be logged
  transports: [new ConsoleTransport()],
});

🎯 Structured Logging

First-class support for structured metadata:

logger.info('Payment processed', {
  userId: 'user_123',
  amount: 99.99,
  currency: 'USD',
  paymentMethod: 'card',
  transactionId: 'txn_abc123',
});

JSON Output:

{
  "timestamp": "2024-02-13T10:30:00.000Z",
  "level": "info",
  "message": "Payment processed",
  "meta": {
    "userId": "user_123",
    "amount": 99.99,
    "currency": "USD",
    "paymentMethod": "card",
    "transactionId": "txn_abc123"
  }
}

🧒 Child Loggers

Create child loggers with inherited context:

// Parent logger
const logger = createLogger({
  transports: [new ConsoleTransport()],
});

// Child logger for a specific request
const requestLogger = logger.child({
  requestId: 'req_abc123',
  userId: 'user_456',
  method: 'POST',
  path: '/api/users',
});

// All logs include the context
requestLogger.info('Request started');
requestLogger.info('Validating input');
requestLogger.info('Creating user');
requestLogger.info('Request completed');

// Output includes: requestId, userId, method, path in every log

Context is immutable:

const parent = logger.child({ service: 'api' });
const child = parent.child({ requestId: '123' });

child.info('test');  // Context: { service: 'api', requestId: '123' }
parent.info('test'); // Context: { service: 'api' }

🛡️ Error Serialization

Automatic, safe error handling:

try {
  await connectToDatabase();
} catch (error) {
  logger.error('Database connection failed', { error });
}

Output:

{
  "timestamp": "2024-02-13T10:30:00.000Z",
  "level": "error",
  "message": "Database connection failed",
  "error": {
    "name": "Error",
    "message": "Connection refused",
    "stack": "Error: Connection refused\n    at ..."
  }
}

🎨 Formatters

Pretty Formatter (Human-Readable)

import { PrettyFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [
    new ConsoleTransport({
      formatter: new PrettyFormatter({ colorize: true }),
    }),
  ],
});

Output:

[2024-02-13T10:30:00.000Z] INFO  User logged in {"userId":"123"}
[2024-02-13T10:30:01.000Z] WARN  Low memory {"available":"512MB"}
[2024-02-13T10:30:02.000Z] ERROR Connection failed {"host":"db.example.com"}

JSON Formatter (Machine-Readable)

import { JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [
    new ConsoleTransport({
      formatter: new JsonFormatter(),
    }),
  ],
});

Output:

{"timestamp":"2024-02-13T10:30:00.000Z","level":"info","message":"User logged in","meta":{"userId":"123"}}

🔒 Sensitive Data Redaction

Built-in support for redacting sensitive fields:

const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
  redact: {
    paths: ['password', 'apiKey', 'user.ssn', 'creditCard.number'],
    censor: '[REDACTED]',
  },
});

logger.info('User registered', {
  username: 'john_doe',
  password: 'super_secret_123',  // Will be redacted
  email: '[email protected]',
  apiKey: 'sk_live_1234567890',  // Will be redacted
  user: {
    name: 'John Doe',
    ssn: '123-45-6789',           // Will be redacted
  },
});

Output:

{
  "username": "john_doe",
  "password": "[REDACTED]",
  "email": "[email protected]",
  "apiKey": "[REDACTED]",
  "user": {
    "name": "John Doe",
    "ssn": "[REDACTED]"
  }
}

🔌 Custom Processors

Transform log records before output:

import { addFieldsProcessor, addCorrelationIdProcessor } from '@periodic/iridium';

const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
  processors: [
    // Add static fields to every log
    addFieldsProcessor({
      environment: process.env.NODE_ENV,
      version: process.env.APP_VERSION,
      hostname: require('os').hostname(),
    }),
    
    // Add correlation ID
    addCorrelationIdProcessor(() => 
      Math.random().toString(36).substring(7)
    ),
  ],
});

logger.info('Application started');
// Output includes: environment, version, hostname, correlationId

Custom Processor:

import type { LogProcessor } from '@periodic/iridium';

const timestampProcessor: LogProcessor = (record) => ({
  ...record,
  meta: {
    ...record.meta,
    timestampMs: Date.now(),
  },
});

const logger = createLogger({
  processors: [timestampProcessor],
  transports: [new ConsoleTransport()],
});

📊 Multiple Transports

Send logs to multiple destinations:

import { ConsoleTransport, MemoryTransport } from '@periodic/iridium';

const memory = new MemoryTransport();

const logger = createLogger({
  level: 'info',
  transports: [
    new ConsoleTransport(),  // Output to console
    memory,                   // Store in memory
  ],
});

logger.info('test');

// Later: check memory logs
console.log(`Logged ${memory.count()} records`);
console.log(memory.getRecords());

📚 Common Patterns

1. Express.js Integration

import express from 'express';
import { createLogger, ConsoleTransport } from '@periodic/iridium';

const logger = createLogger({
  level: process.env.LOG_LEVEL || 'info',
  transports: [new ConsoleTransport()],
});

const app = express();

// Request logging middleware
app.use((req, res, next) => {
  req.logger = logger.child({
    requestId: req.id || Math.random().toString(36).substring(7),
    method: req.method,
    path: req.path,
    userAgent: req.get('user-agent'),
  });
  
  req.logger.info('Request started');
  next();
});

// Use request-specific logger
app.get('/api/users', async (req, res) => {
  req.logger.info('Fetching users');
  const users = await db.users.findAll();
  res.json(users);
});

// Error handling
app.use((err, req, res, next) => {
  req.logger.error('Request failed', { error: err });
  res.status(500).json({ error: 'Internal Server Error' });
});

2. Service Layer Logging

class UserService {
  constructor(private logger: Logger) {}
  
  async createUser(data: UserData) {
    const log = this.logger.child({ operation: 'createUser' });
    
    log.info('Creating user', { email: data.email });
    
    try {
      const user = await db.users.create(data);
      log.info('User created successfully', { userId: user.id });
      return user;
    } catch (error) {
      log.error('Failed to create user', { error });
      throw error;
    }
  }
}

const userService = new UserService(logger);

3. Background Jobs

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  level: 'info',
  transports: [
    new ConsoleTransport({
      formatter: new JsonFormatter(), // JSON for log aggregation
    }),
  ],
});

async function processJob(job: Job) {
  const log = logger.child({
    jobId: job.id,
    jobType: job.type,
  });
  
  log.info('Job started');
  
  try {
    await job.process();
    log.info('Job completed successfully');
  } catch (error) {
    log.error('Job failed', { error, attempts: job.attempts });
    throw error;
  }
}

4. Testing with Memory Transport

import { createLogger, MemoryTransport } from '@periodic/iridium';

describe('UserService', () => {
  let logger: Logger;
  let memory: MemoryTransport;
  let userService: UserService;

  beforeEach(() => {
    memory = new MemoryTransport();
    logger = createLogger({
      level: 'debug',
      transports: [memory],
    });
    userService = new UserService(logger);
  });

  it('should log user creation', async () => {
    await userService.createUser({ email: '[email protected]' });
    
    const logs = memory.getRecordsByLevel('info');
    expect(logs).toHaveLength(2);
    expect(logs[0].message).toBe('Creating user');
    expect(logs[1].message).toBe('User created successfully');
  });
});

5. Production Configuration

import winston from 'winston';
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const isDevelopment = process.env.NODE_ENV === 'development';

const logger = createLogger({
  level: process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info'),
  
  transports: [
    new ConsoleTransport({
      formatter: isDevelopment
        ? new PrettyFormatter({ colorize: true })
        : new JsonFormatter(),
      stderr: true, // error/fatal go to stderr
    }),
  ],
  
  processors: [
    addFieldsProcessor({
      service: 'api',
      environment: process.env.NODE_ENV,
      version: process.env.npm_package_version,
      hostname: require('os').hostname(),
    }),
  ],
  
  redact: {
    paths: [
      'password',
      'token',
      'apiKey',
      'secret',
      'creditCard',
      'ssn',
    ],
  },
  
  onError: (error) => {
    // Log logger errors to external service
    console.error('Logger error:', error);
  },
});

export default logger;

6. Graceful Shutdown

const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
});

async function shutdown(signal: string) {
  logger.info('Shutdown initiated', { signal });
  
  // Flush all async transports
  await logger.flush();
  
  logger.info('Shutdown complete');
  process.exit(0);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

🎛️ Configuration Options

Logger Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | level | LogLevel | 'info' | Minimum level to log | | transports | Transport[] | [] | Output destinations | | processors | LogProcessor[] | [] | Record transformers | | redact | RedactionConfig | - | Sensitive data redaction | | clock | ClockFunction | - | Custom timestamp function | | onError | (error) => void | - | Error handler |

const logger = createLogger({
  level: 'info',
  transports: [new ConsoleTransport()],
  processors: [addFieldsProcessor({ service: 'api' })],
  redact: { paths: ['password'], censor: '[REDACTED]' },
  clock: () => new Date().toISOString(),
  onError: (error) => console.error('Logger error:', error),
});

Console Transport Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | formatter | Formatter | PrettyFormatter | Output formatter | | colorize | boolean | Auto-detect | Enable colors | | stderr | boolean | true | Route error/fatal to stderr |

new ConsoleTransport({
  formatter: new JsonFormatter(),
  colorize: false,
  stderr: true,
});

Redaction Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | paths | string[] | - | Fields to redact (supports nested) | | censor | string | '[REDACTED]' | Replacement value |

redact: {
  paths: ['password', 'apiKey', 'user.ssn'],
  censor: '***',
}

📋 API Reference

createLogger(config?)

Create a new logger instance.

function createLogger(config?: LoggerConfig): Logger

Logger Class

Main logger interface.

class Logger {
  debug(message: string, meta?: Record<string, unknown>): void
  info(message: string, meta?: Record<string, unknown>): void
  warn(message: string, meta?: Record<string, unknown>): void
  error(message: string, meta?: Record<string, unknown>): void
  fatal(message: string, meta?: Record<string, unknown>): void
  
  child(context: LogContext): Logger
  flush(): Promise<void>
}

Transports

ConsoleTransport

new ConsoleTransport(options?: {
  formatter?: Formatter;
  colorize?: boolean;
  stderr?: boolean;
})

MemoryTransport

const memory = new MemoryTransport();

memory.log(record: LogRecord): void
memory.getRecords(): LogRecord[]
memory.getRecordsByLevel(level: string): LogRecord[]
memory.clear(): void
memory.count(): number

Formatters

PrettyFormatter

new PrettyFormatter(options?: {
  colorize?: boolean;
})

JsonFormatter

new JsonFormatter(options?: {
  pretty?: boolean;
})

Processors

addFieldsProcessor(fields: Record<string, unknown>): LogProcessor
addCorrelationIdProcessor(getId: () => string): LogProcessor
composeProcessors(...processors: LogProcessor[]): LogProcessor

🧩 Architecture

@periodic/iridium/
├── src/
│   ├── core/                  # Framework-agnostic core
│   │   ├── types.ts          # TypeScript interfaces
│   │   ├── levels.ts         # Log level utilities
│   │   ├── logger.ts         # Main Logger class
│   │   ├── record.ts         # Record building
│   │   └── processors.ts     # Processor utilities
│   ├── transports/            # Transport implementations
│   │   ├── console.ts        # Console transport
│   │   └── memory.ts         # Memory transport
│   ├── formatters/            # Formatter implementations
│   │   ├── pretty.ts         # Pretty formatter
│   │   └── json.ts           # JSON formatter
│   ├── utils/                 # Utilities
│   │   ├── serializeError.ts # Error serialization
│   │   └── redact.ts         # Redaction
│   └── index.ts               # Public API

Design Philosophy:

  • Core is pure TypeScript with no dependencies
  • Transports are pluggable output destinations
  • Formatters control output format
  • Processors enable extensibility
  • Easy to extend for custom transports and formatters

📈 Performance

Iridium is optimized for production workloads:

  • Early level filtering - Disabled logs have near-zero overhead
  • No unnecessary allocations - Minimal object creation
  • Numeric comparisons - Fast level checks
  • Deferred stringification - JSON.stringify only in transports
  • Zero dependencies - No external overhead

Benchmark Results (Node.js 20):

Disabled logs (filtered):     ~2,000,000 ops/sec
Active logs (memory):         ~500,000 ops/sec
Active logs with metadata:    ~450,000 ops/sec
Child logger:                 ~400,000 ops/sec
Multi-transport (2x):         ~300,000 ops/sec
With processor:               ~400,000 ops/sec
With redaction:               ~350,000 ops/sec

Run benchmarks:

npm run benchmark

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ File transport (use external packages)
❌ HTTP transport (use external packages)
❌ Log rotation (use external tools)
❌ Log aggregation (use Elasticsearch, Datadog, etc.)
❌ Metrics collection (use Prometheus, StatsD, etc.)
❌ Tracing (use OpenTelemetry)
❌ Configuration files (configure in code)
❌ Automatic framework detection (explicit integration)

Focus on doing one thing well: structured logging core.


🤝 Related Packages

Part of the Periodic series by Uday Thakur:

Build complete, production-ready APIs with the Periodic series!


📖 Documentation


🧪 Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

# Run benchmarks
npm run benchmark

Note: All tests achieve >80% code coverage.


🛠️ Production Recommendations

Environment Variables

LOG_LEVEL=info
NODE_ENV=production
APP_VERSION=1.0.0

Log Aggregation

Use JSON formatter with log aggregation services:

const logger = createLogger({
  transports: [
    new ConsoleTransport({
      formatter: new JsonFormatter(),
    }),
  ],
});

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Error Monitoring

Integrate with error tracking:

const logger = createLogger({
  onError: (error) => {
    Sentry.captureException(error);
  },
});

Performance Monitoring

const logger = createLogger({
  processors: [
    (record) => ({
      ...record,
      meta: {
        ...record.meta,
        duration: performance.now(),
      },
    }),
  ],
});

🎨 TypeScript Support

Full TypeScript support with complete type safety:

import type {
  Logger,
  LogLevel,
  LogRecord,
  LogContext,
  Transport,
  Formatter,
  LogProcessor,
  RedactionConfig,
} from '@periodic/iridium';

const logger: Logger = createLogger({
  level: 'info' as LogLevel,
  transports: [new ConsoleTransport()],
});

const context: LogContext = { requestId: '123' };
const child: Logger = logger.child(context);

📝 License

MIT © Uday Thakur


🙏 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Coding standards
  • Architecture principles

📞 Support


🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!


Built with ❤️ by Uday Thakur for production-grade Node.js applications