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

@ferrow/structured-logger

v1.0.0

Published

Leveled JSON logger with child loggers, key redaction, and pluggable sink

Readme

structured-logger

CI

Leveled JSON logger with child loggers, key redaction (deep), and pluggable async sink. Outputs ISO timestamps, filters by level, and propagates context through child loggers.

Installation

npm install structured-logger

Quick Start

import { StructuredLogger } from 'structured-logger';

const logger = new StructuredLogger({
  level: 'info',
  redactKeys: ['password', 'token'],
});

logger.info('user_login', { userId: 123, email: '[email protected]' });

// Create a child logger with bound context
const requestLogger = logger.child({ requestId: 'req-456' });
requestLogger.info('request_start', { method: 'GET', path: '/api/users' });

API

new StructuredLogger(options?): StructuredLogger

Create a logger.

Options:

  • level (LogLevel, default: 'info'): Minimum level to output (debug, info, warn, error)
  • redactKeys (string[], default: []): Keys to redact (case-insensitive, deep)
  • sink (LogSink, default: stdout line-JSON): Custom sink function

logger.debug(message, context?): void

Log at debug level (lowest).

logger.info(message, context?): void

Log at info level.

logger.warn(message, context?): void

Log at warn level.

logger.error(message, error?, context?): void

Log at error level. If an Error object is passed, message and stack are extracted.

try {
  throw new Error('Connection failed');
} catch (err) {
  logger.error('database_error', err, { attempt: 1 });
  // Outputs: { timestamp, level: 'error', message: 'database_error', context: { attempt: 1, message: '...', stack: '...' } }
}

logger.child(context): StructuredLogger

Create a child logger with bound context. All logs from the child include the bound fields.

const logger = new StructuredLogger();
const child = logger.child({ userId: 123, sessionId: 'abc' });

child.info('action_taken'); // Includes userId and sessionId in context

Log Entry Format

Each entry is JSON-serialized on a single line:

{
  "timestamp": "2026-08-12T01:55:00.123Z",
  "level": "info",
  "message": "request_complete",
  "context": {
    "method": "GET",
    "statusCode": 200,
    "durationMs": 45
  }
}

Fields:

  • timestamp (ISO string): When the log was created
  • level (string): debug, info, warn, error
  • message (string): Log message
  • context (object, optional): Additional fields from both bound and call-time context

Redaction

Keys are redacted (case-insensitive) to [REDACTED] throughout the entire context object, including nested objects and arrays:

const logger = new StructuredLogger({
  redactKeys: ['password', 'token', 'secret'],
});

logger.info('user_login', {
  email: '[email protected]',
  password: 'secret123',
  config: { token: 'abc-xyz' },
  tags: ['admin', 'password'],
});

// Outputs (redacted fields):
// {
//   "email": "[email protected]",
//   "password": "[REDACTED]",
//   "config": { "token": "[REDACTED]" },
//   "tags": ["admin", "password"]  // Note: array values not redacted (keys only)
// }

Level Filtering

Only logs at or above the configured level are emitted:

const logger = new StructuredLogger({ level: 'warn' });

logger.debug('debug_message'); // Not emitted
logger.info('info_message'); // Not emitted
logger.warn('warn_message'); // Emitted
logger.error('error_message'); // Emitted

Custom Sink

Replace the default stdout sink with a custom one:

const entries = [];
const logger = new StructuredLogger({
  sink: (entry) => {
    entries.push(entry);
  },
});

logger.info('test');
console.log(entries[0]); // { timestamp: '...', level: 'info', message: 'test' }

Sinks can be async:

const logger = new StructuredLogger({
  sink: async (entry) => {
    await sendToLoggingService(entry);
  },
});

Sink errors are silently ignored to prevent logger crashes.

Examples

Child logger with context

const logger = new StructuredLogger({ level: 'debug' });

// Parent
logger.info('app_start');

// Child with request context
const reqLogger = logger.child({ requestId: 'req-123', userId: 'user-456' });
reqLogger.debug('request_received', { method: 'POST', path: '/api/submit' });
reqLogger.info('validation_passed');

// Grandchild adds more context
const dbLogger = reqLogger.child({ database: 'orders' });
dbLogger.info('query_executed', { rows: 42 });

Redaction with nested objects

const logger = new StructuredLogger({
  redactKeys: ['apiKey', 'password'],
});

logger.info('auth_attempt', {
  user: 'alice',
  credentials: {
    password: 'secret',
    apiKey: 'sk-123-abc',
  },
  metadata: {
    ip: '192.168.1.1',
  },
});

// Output:
// {
//   "user": "alice",
//   "credentials": {
//     "password": "[REDACTED]",
//     "apiKey": "[REDACTED]"
//   },
//   "metadata": { "ip": "192.168.1.1" }
// }

Limits

  • No structured field types; all values JSON-serialized as-is.
  • Circular references in context objects will cause JSON.stringify to fail; ensure context is acyclic.
  • Sink errors are silently ignored; implement your own error handling in custom sinks.
  • No automatic performance metrics (duration, memory, etc.); pass these as context fields.
  • No built-in filtering by context fields; implement in custom sink if needed.

License: MIT

Sponsored by Ferrow


Part of the ferrow-toolkit collection · Sponsored by Ferrow