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

@rudderjs/log

v1.0.1

Published

Structured logging for RudderJS — channels, levels, formatters, context propagation

Readme

@rudderjs/log

Structured logging for RudderJS — channels, log levels, formatters, context propagation, and testing fakes.

Laravel's Log facade, rebuilt for Node.js.

Installation

pnpm add @rudderjs/log

Quick Start

// bootstrap/providers.ts
import { LogProvider } from '@rudderjs/log'

export default [
  LogProvider,
  // ...other providers
]
// config/log.ts
import { Env } from '@rudderjs/core'
import type { LogConfig } from '@rudderjs/log'

export default {
  default: Env.get('LOG_CHANNEL', 'stack'),

  channels: {
    stack: {
      driver:   'stack',
      channels: ['console', 'daily'],
    },
    console: {
      driver: 'console',
      level:  Env.get('LOG_LEVEL', 'debug') as 'debug',
    },
    daily: {
      driver: 'daily',
      path:   'storage/logs/rudderjs.log',
      days:   14,
    },
    null: {
      driver: 'null',
    },
  },
} satisfies LogConfig

Usage

import { Log } from '@rudderjs/log'

// Log levels (RFC 5424, highest → lowest severity)
Log.emergency('System is unusable')
Log.alert('Action must be taken immediately')
Log.critical('Critical condition')
Log.error('Runtime error', { exception: err.message })
Log.warning('Something unexpected happened')
Log.notice('Normal but significant event')
Log.info('User logged in', { userId: 42 })
Log.debug('Query executed', { sql: '...', ms: 12 })

// Generic log method
Log.log('info', 'message', { key: 'value' })

logger() helper

import { logger } from '@rudderjs/log'

logger('quick debug message')        // logs at debug level
logger().info('or use the facade')   // returns Log facade

Channels

Selecting a channel

Log.channel('daily').error('Written to daily log only')

On-demand stacks

Log.stack(['console', 'daily']).warning('Fan-out to multiple channels')
Log.stack(['console', 'daily'], true).error('Ignore sub-channel errors')

Context

Per-log context

Log.info('Order placed', { orderId: 123, total: 99.99 })

Per-channel context (persists across calls)

Log.withContext({ requestId: 'abc-123' })
Log.info('Processing request')   // context: { requestId: 'abc-123' }

Log.withoutContext(['requestId'])  // remove specific keys
Log.withoutContext()               // clear all channel context

Shared context (all channels)

Log.shareContext({ appVersion: '1.2.0', environment: 'production' })

// Clear it later
Log.flushSharedContext()

Merge priority: inline context > channel context > shared context.

Listeners

Log.listen((entry) => {
  // entry: { level, message, context, timestamp, channel }
  reportToErrorTracker(entry)
})

Drivers

| Driver | Description | |---|---| | console | Colored output to stdout/stderr (errors → stderr) | | single | Appends to a single log file | | daily | Date-rotated files (rudderjs-2026-04-06.log), auto-cleanup | | stack | Fan-out to multiple channels | | null | Discards all messages (useful for testing) |

console

console: {
  driver:    'console',
  level:     'debug',
  formatter: 'line',  // 'line' (default) | 'json'
}

single

single: {
  driver:    'single',
  path:      'storage/logs/app.log',
  level:     'warning',
  formatter: 'json',
}

daily

daily: {
  driver:    'daily',
  path:      'storage/logs/app.log',    // produces app-2026-04-06.log
  days:      14,                         // retain last 14 days (default)
  level:     'debug',
  formatter: 'line',
}

stack

stack: {
  driver:           'stack',
  channels:         ['console', 'daily'],
  ignoreExceptions: false,  // true = swallow sub-channel errors
}

Formatters

LineFormatter (default)

[2026-04-06T12:00:00.000Z] app.INFO      User logged in {"userId":42}
[2026-04-06T12:00:01.000Z] app.ERROR     Database failed {"code":500}

JsonFormatter

{"timestamp":"2026-04-06T12:00:00.000Z","channel":"app","level":"info","message":"User logged in","context":{"userId":42}}

Set per channel: formatter: 'json'

Custom Drivers

import { extendLog } from '@rudderjs/log'

extendLog('sentry', (config) => ({
  log(entry) {
    Sentry.captureMessage(entry.message, {
      level: entry.level,
      extra: entry.context,
    })
  },
}))

Then use in config:

sentry: {
  driver: 'sentry',
  level:  'error',
  dsn:    process.env.SENTRY_DSN,
}

Direct API (without ServiceProvider)

import { LogRegistry, ConsoleAdapter, FileAdapter } from '@rudderjs/log'

LogRegistry.register('console', new ConsoleAdapter(), 'debug')
LogRegistry.register('file', new FileAdapter('storage/logs/app.log'), 'warning')
LogRegistry.setDefault('console')

Testing

Use LogFake to capture and assert on log entries in tests:

import { LogFake, LogRegistry, Log } from '@rudderjs/log'

const fake = new LogFake()
LogRegistry.register('fake', fake, 'debug')
LogRegistry.setDefault('fake')

// ... code under test ...

fake.assertLogged('error', 'Payment failed')
fake.assertLogged('info', (msg, ctx) => ctx['userId'] === 42)
fake.assertNotLogged('debug')
fake.assertLoggedTimes('warning', 3)
fake.assertNothingLogged()
fake.clear()

Assertion API

| Method | Description | |---|---| | assertLogged(level, match?) | Assert an entry exists at level, optionally matching a string or predicate | | assertNotLogged(level, match?) | Assert no matching entry exists | | assertLoggedTimes(level, count, match?) | Assert exact number of matching entries | | assertNothingLogged() | Assert the log is empty | | clear() | Reset captured entries |

Log Levels

RFC 5424 severity order (0 = most severe):

| Level | Severity | Use case | |---|---|---| | emergency | 0 | System is unusable | | alert | 1 | Immediate action required | | critical | 2 | Critical conditions | | error | 3 | Runtime errors | | warning | 4 | Unexpected but recoverable | | notice | 5 | Normal but significant | | info | 6 | Informational messages | | debug | 7 | Debug information |

Setting a channel's level filters out messages with lower severity (higher number). For example, level: 'warning' drops notice, info, and debug.