pear-api
Version:
Pear API Base & Integration Module
190 lines (178 loc) • 6.79 kB
JavaScript
'use strict'
const { isBare } = require('which-runtime')
const { formatWithOptions } = isBare ? require('bare-format') : require('util')
const hrtime = isBare ? require('bare-hrtime') : process.hrtime
const pear = require('./cmd')(isBare ? global.Bare.argv.slice(1) : process.argv.slice(1))
const max = pear?.flags.logMax ?? false
const verbose = pear?.flags.logVerbose || max
const log = pear?.flags.log || !!pear?.flags.logLabels || verbose || max
const switches = {
log,
level: pear?.flags.logLevel ?? (max ? 3 : (log ? 2 : 1)),
labels: pear?.flags.logLabels ?? '',
fields: verbose ? 'date,time,level,label,delta' : (pear?.flags.logFields ?? ''),
stacks: pear?.flags.logStacks ?? false,
verbose,
max
}
class Logger {
static switches = switches
static OFF = 0
static ERR = 1
static INF = 2
static TRC = 3
static [0] = 'OFF'
static [1] = 'ERR'
static [2] = 'INF'
static [3] = 'TRC'
name = '' // for stacks
constructor ({ labels = '', fields, stacks, level, pretty } = {}) {
this._fields = this._parseFields(fields + this.constructor.switches.fields)
labels = this._parseLabels(labels)
.concat(this._parseLabels(this.constructor.switches.labels))
.filter(Boolean)
this._max = this.constructor.switches.max
this._verbose = this.constructor.switches.verbose
this._labels = new Set(labels)
this._show = this._fields.show
this._stacks = stacks ?? this.constructor.switches.stacks
this._times = {}
if (this._verbose === false && this._max === false && pretty) {
if (this._fields.seen.has('level') === false) this._show.level = false
if (this._fields.seen.has('label') === false) this._show.label = this._labels.size > 2
}
this.stack = ''
this.LEVEL = this._parseLevel(level ?? this.constructor.switches.level)
this._tty = null
}
get OFF () { return this.LEVEL === this.constructor.OFF }
get ERR () { return this.LEVEL >= this.constructor.ERR }
get INF () { return this.LEVEL >= this.constructor.INF }
get TRC () { return this.LEVEL >= this.constructor.TRC }
_args (level, label, ...args) {
const now = hrtime.bigint()
const ms = Number(now) / 1e6
const delta = this._times[label] ? ms - Number(this._times[label]) / 1e6 : 0
this._times[label] = now
const datetime = (this._show.date || this._show.time) ? new Date().toISOString().split('T') : []
const date = this._show.date ? datetime[0] : ''
const time = this._show.time ? datetime[1].slice(0, -1) : ''
label = this._show.label ? '[ ' + label.slice(0, 21) + ' ]' : ''
level = this._show.level ? level : ''
const prefix = [level, date, time, label].filter(Boolean)
return [...prefix, ...args, this._show.delta ? '[+' + (Math.round(delta * Math.pow(10, 4)) / Math.pow(10, 4)) + 'ms]' : '']
}
error (label, ...args) {
if (this.LEVEL < this.constructor.ERR) return
if (Array.isArray(label)) {
for (const lbl of label) this.error(lbl, ...args)
return
}
if (this._max === false && !this._labels.has(label)) return
if (this._stacks) Error.captureStackTrace(this, this.error)
args = this._args('ERR', label, ...args)
if (this._stacks) {
console.error(...args, this.stack)
this.stack = ''
} else {
console.error(...args)
}
}
info (label, ...args) {
if (this.LEVEL < this.constructor.INF) return
if (Array.isArray(label)) {
for (const lbl of label) this.info(lbl, ...args)
return
}
if (this._max === false && !this._labels.has(label)) return
if (this._stacks) Error.captureStackTrace(this, this.info)
args = this._args('INF', label, ...args)
if (this._stacks) {
console.log(...args, this.stack)
this.stack = ''
} else {
console.log(...args)
}
}
trace (label, ...args) {
if (this.LEVEL < this.constructor.TRC) return
if (Array.isArray(label)) {
for (const lbl of label) this.trace(lbl, ...args)
return
}
if (this._max === false && !this._labels.has(label)) return
if (this._stacks) Error.captureStackTrace(this, this.trace)
args = this._args('TRC', label, ...args)
if (this._stacks) {
console.error(...args, this.stack)
this.stack = ''
} else {
console.error(...args)
}
}
format (level, label, ...args) {
if (this._tty === null) this._tty = isBare ? require('bare-tty').isTTY(0) : require('tty').isatty(0) // lazy
if (Object.hasOwn(this.constructor, level) === false) return ''
if (typeof level === 'number') level = this.constructor[level]
if (this.LEVEL < this.constructor[level]) return ''
if (Array.isArray(label)) {
return label.map((lbl) => this.format(level, lbl, ...args).join('\n'))
}
if (!this._labels.has(label)) return ''
Error.captureStackTrace(this, this.format)
args = this._args(level, label, ...args)
if (this._stacks) {
const output = formatWithOptions({ colors: this._tty }, ...args, this.stack).replace(/\u0000/g, '') // eslint-disable-line no-control-regex
this.stack = ''
return output
} else {
return formatWithOptions({ colors: this._tty }, ...args).replace(/\u0000/g, '') // eslint-disable-line no-control-regex
}
}
_parseLevel (level) {
if (typeof level === 'number') return level
if (typeof level === 'string') level = level.toUpperCase()
switch (true) {
case (level === 'OFF'): return this.constructor.OFF
case (level === 'ERR'): return this.constructor.ERR
case (level === 'INF'): return this.constructor.INF
case (level === 'TRC'): return this.constructor.TRC
case (level === '0'): return this.constructor.OFF
case (level === '1'): return this.constructor.ERR
case (level === '2'): return this.constructor.INF
case (level === '3'): return this.constructor.TRC
case (level === 'ERROR'): return this.constructor.ERR
case (level === 'INFO'): return this.constructor.INF
case (level === 'TRACE'): return this.constructor.TRC
default: return this.constructor.INF
}
}
_parseFields (fields = '') {
const show = {
date: false,
time: false,
level: true,
label: true,
delta: true
}
const seen = new Set()
for (let field of fields.split(',').concat(this.constructor.switches.fields.split(','))) {
if (seen.has(field)) continue
field = field.trim()
if (field.startsWith('h:')) {
field = field.slice(2)
seen.add(field)
show[field] = false
continue
}
seen.add(field)
show[field] = true
}
return { seen, show }
}
_parseLabels (labels) {
if (typeof labels !== 'string') return labels
return labels.split(',')
}
}
module.exports = Logger