@speckle/shared
Version:
Shared code between various Speckle JS packages
105 lines (92 loc) • 3.12 kB
text/typescript
import { pino } from 'pino'
import type { LoggerOptions } from 'pino'
import { toClef, toClefLogLevel } from './pinoClef.js'
import { TIME_MS } from '../core/index.js'
import inspector from 'node:inspector'
let logger: pino.Logger
export type MixinFn = (mergeObject: object, level: number) => object
type LogLevelFormatter = (label: string, number: number) => object
type LogFormatter = (logObject: Record<string, unknown>) => Record<string, unknown>
const allowPrettyDebugger = ['1', 'true'].includes(
process.env.ALLOW_PRETTY_DEBUGGER || 'false'
)
const defaultLevelFormatterFactory =
(pretty: boolean): LogLevelFormatter =>
(label, number) =>
// for not pretty, we're providing clef levels
pretty ? { level: label } : toClefLogLevel(number)
const defaultLogFormatterFactory =
(pretty: boolean): LogFormatter =>
(logObject) =>
pretty ? logObject : toClef(logObject)
export function getLogger(
minimumLoggedLevel = 'info',
pretty = false,
mixin?: MixinFn
): pino.Logger {
if (logger) return logger
const pinoOptions: LoggerOptions = {
base: undefined, // Set to undefined to avoid adding pid, hostname properties to each log.
formatters: {
level: defaultLevelFormatterFactory(pretty),
log: defaultLogFormatterFactory(pretty)
},
mixin,
// when not pretty, to produce a clef format, we need the message to be the message template key
messageKey: pretty ? 'msg' : '@mt',
level: minimumLoggedLevel,
// when not pretty, we need the time in the clef appropriate field, not from pino
timestamp: pretty ? pino.stdTimeFunctions.isoTime : false
}
// pino-pretty hangs in debugger mode in node 22 for some (Ubuntu/WSL2?), dunno why
if (pretty && (allowPrettyDebugger || !inspector.url())) {
pinoOptions.transport = {
target: '@speckle/shared/pinoPrettyTransport.cjs',
options: {
colorize: true,
destination: 2, //stderr
ignore: 'time',
levelFirst: true,
singleLine: true
}
}
}
logger = pino(pinoOptions)
return logger
}
export function extendLoggerComponent(
otherChild: pino.Logger,
...subComponent: string[]
): pino.Logger {
const otherChildBindings = otherChild.bindings()
otherChildBindings.component = [otherChildBindings.component, ...subComponent]
.filter(Boolean)
.join('/')
return otherChild.child(otherChildBindings)
}
/**
* Very simple RPM counter to catch extreme spam scenarios (e.g. a ton of errors being thrown). It's not going
* to always report accurately, but as long as hits are being registered consistently it should be accurate enough.
*/
export function simpleRpmCounter() {
const getTimestamp = () => new Date().getTime()
let lastDateTimestamp = getTimestamp()
let hits = 0
const validateHits = () => {
const timestamp = getTimestamp()
if (timestamp > lastDateTimestamp + TIME_MS.minute) {
hits = 0
lastDateTimestamp = timestamp
}
}
return {
hit: () => {
validateHits()
return ++hits
},
get: () => {
validateHits()
return hits
}
}
}