@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):
LogLevel.Debug-log.debug(...)LogLevel.Info-log.info(...)LogLevel.Warn-log.warn(...)LogLevel.Error-log.error(...)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') // hiddencolors
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 acquiredper-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 datatimestamps & 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 levelwith 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:noisyLogger.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 listeningreference
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
textwith givencolors
returns: string
const coloredFoo = Logger.color('foo', Color.Magenta, BackgroundColor.White)
Logger.log(coloredFoo)Logger.colorByRGB(text: string, color: RGBColor, options?: ColorByRGBOptions)
colorizes given
textwith a 24-bit RGBcolor(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 unsubscribeLoggerInstance
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