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

@starkow/logger

v3.0.0

Published

simple yet effective logger

Downloads

273

Readme

@starkow/logger

why?

  • i wanted to make it by my own
  • it's gorgeous i think
  • fixed colors for static names (logger name 'foo' will produce deterministic yet still random color every time)!
  • it have log level
  • file logs exist Yes👍
  • color disable when not terminal
  • child loggers (app:db), timers, namespace filtering (LOG=app:*)
  • yeah

[!NOTE] v3 is esm-only and targets node 18+. if you're still on commonjs, use a dynamic import()

usage

import { Logger, Color, TextStyle } from '@starkow/logger'

const log = Logger.create('foo') // log will be given random yet deterministic color

log('bar', Logger.color('baz', Color.Red, TextStyle.Bold), ':)')

alternative imports:

import { Logger } from '@starkow/logger'
import { Color, TextStyle } from '@starkow/logger/colors'

log levels

levels are ordered by severity (low → high):

  1. LogLevel.Debug - log.debug(...)
  2. LogLevel.Info - log.info(...)
  3. LogLevel.Warn - log.warn(...)
  4. LogLevel.Error - log.error(...)
  5. LogLevel.Silent - mutes every leveled method

Logger.config.setLogLevel(level) sets the minimum severity that gets printed. so setLogLevel(LogLevel.Warn) shows warnings and errors but hides info & debug; setLogLevel(LogLevel.Error) shows only errors; setLogLevel(LogLevel.Silent) hides them all

the plain log(...) / log.log(...) call is raw output — it's never filtered and always prints

default level is LogLevel.Debug, so everything is visible out of the box

import { Logger, LogLevel } from '@starkow/logger'

Logger.config.setLogLevel(LogLevel.Warn) // only warn & error (and raw log) will show

const log = Logger.create('@starkow/logger')

log('is cool!')      // shown (raw, never filtered)
log.warn('careful!') // shown
log.info('fyi')      // hidden
log.debug('noise')   // hidden

colors

colors are emitted automatically when it makes sense — i.e. when you're writing to an actual terminal. when output is piped or redirected, or NO_COLOR is set, they're stripped so you don't get \x1b[...m garbage in your files/pipes. FORCE_COLOR forces them back on

you can override the detection:

import { Logger } from '@starkow/logger'

Logger.config.setColor('always') // 'auto' (default) | 'always' | 'never'

file logs

import { resolve } from 'node:path'

import { Logger } from '@starkow/logger'

Logger.config.setFilePath(resolve(import.meta.dirname, 'epic.log')) // import.meta.dirname: node 20.11+

Logger.create('this will be logged')('into a file!', { 42: true })

file {dir}/epic.log will look like this:

2023-10-05T22:47:16.630Z [this will be logged] into a file! {"42":true}

writes are serialized (no interleaving), failures won't crash your process, and any ansi colors are stripped before hitting the file. errors keep their stack instead of collapsing to {}. call await Logger.flush() before process.exit() to make sure pending writes land

child loggers

log.child(name) creates a sub-logger namespaced under its parent. it gets its own deterministic color and nests as deep as you want

const app = Logger.create('app')

app('starting')

const db = app.child('db')
db('connected')        // app:db connected

const pool = db.child('pool')
pool('acquired')       // app:db:pool acquired

per-logger options

instead of variadic colors you can pass an options object to Logger.create (and child) with colors and/or a per-logger level that overrides the global one

import { Logger, Color, LogLevel } from '@starkow/logger'

Logger.config.setLogLevel(LogLevel.Error) // global: only errors

const db = Logger.create('db', { colors: [Color.Cyan], level: LogLevel.Debug })

db.debug('still shown — this logger overrides the global level')

children inherit their parent's level override

timers

log.time(label) returns a function that, when called, logs the elapsed time

const done = log.time('build')
// ... work ...
done()             // build: 1.23s
done('extra data') // build: 1.23s extra data

timestamps & symbols

both off by default. opt in for a richer console line

Logger.config.setTimestamps(true)                 // dim HH:MM:SS
Logger.config.setTimestamps(d => d.toISOString()) // or a custom formatter
Logger.config.setSymbols(true)                    // ✖ ⚠ ℹ ⬦ per level

with both on: 12:34:56 ✖ app:db connection failed (raw log() gets no symbol)

namespace filtering

a per-logger master switch, separate from log levels. drive it with the LOG env var or set it programmatically. when unset, everything is enabled

LOG=app:*          node app.mjs # only app and its descendants
LOG=db,api         node app.mjs # only these
LOG='*,-app:noisy' node app.mjs # everything except app:noisy
Logger.config.setNamespaces('app:*,-app:noisy')

a disabled logger is fully silent — even its raw log() call

taps

Logger.config.onLog(fn) calls fn for every emitted log — handy for shipping logs elsewhere. it returns an unsubscribe function

const off = Logger.config.onLog(entry => {
  // entry: { name, level, data, message, timestamp }
  sendSomewhere(entry)
})

off() // stop listening

reference

Logger.create(name: string, ...colors: AnyColor[]), Logger.create(name: string, options: CreateOptions)

initializes logger function

returns: LoggerInstance

Logger.create('bot', Color.Green, TextStyle.Underline)('started!')
Logger.create('db', { colors: [Color.Cyan], level: LogLevel.Debug })('connected')

Logger.log(...), Logger.error(...), Logger.warn(...), Logger.debug(...), Logger.info(...)

logs data to chosen stream

Logger.log('foo', { bar: 'baz' })
Logger.error('error!')
Logger.debug({ type: 'paid', amount: 13.49 })

Logger.prefix(name: string, ...colors: AnyColor[])

generates current logger prefix

returns: string

const prefix = Logger.prefix('server', Color.Cyan)

Logger.log(prefix, 'started!')

Logger.color(text: string, ...colors: AnyColor[])

colorizes given text with given colors

returns: string

const coloredFoo = Logger.color('foo', Color.Magenta, BackgroundColor.White)

Logger.log(coloredFoo)

Logger.colorByRGB(text: string, color: RGBColor, options?: ColorByRGBOptions)

colorizes given text with a 24-bit RGB color (foreground by default)

returns: string

Logger.colorByRGB('foo', { r: 235, g: 87, b: 87 })
Logger.colorByRGB('bar', { r: 20, g: 20, b: 20 }, { background: true })

Logger.updateLog(text: string, params?: LogUpdateParams)

updates last log line

returns: void

Logger.log(      'Loading:   9%'        )
Logger.updateLog('Loading:  45%'        )
Logger.updateLog('Loading:  77%'        )
Logger.updateLog('Loading: 100% [Done!]')

Logger.flush()

awaits all pending file writes (e.g. before process.exit)

returns: Promise<void>

await Logger.flush()

Logger.config

runtime configuration

Logger.config.setLogLevel(LogLevel.Error)         // minimum severity to print
Logger.config.setColor('never')                   // 'auto' | 'always' | 'never'
Logger.config.setTimestamps(true)                 // bool | (date) => string
Logger.config.setSymbols(true)                    // level glyphs on/off
Logger.config.setNamespaces('app:*')              // namespace filter (overrides $LOG)
Logger.config.setFilePath('./app.log')            // enable file logging
Logger.config.resetFilePath()                     // disable file logging
const off = Logger.config.onLog(entry => { ... }) // tap; returns unsubscribe

LoggerInstance

const log = Logger.create('logger')

(...data: any[]): void, log(...data: any[]): void

log(    'foo', ['bar', 13.37])
log.log('foo', ['bar', 13.37])

update(...): void

log('1')
log.update('2')
log.update('3')

error(...): void

log.error('failed!')

warn(...): void

log.warn('something is about to crash!')

debug(...): void

log.debug({ 42: 'the truth' })

info(...): void

log.info('this log is very mandatory keep listening to me i swear')

child(name: string, ...colors: AnyColor[]): LoggerInstance, child(name, options): LoggerInstance

const db = log.child('db')        // namespaced under the parent
const pool = db.child('pool', Color.Cyan)

time(label: string): (...data: any[]) => void

const done = log.time('build')
// ... work ...
done() // build: 1.23s