@spot-meetings/backend-logger
Version:
Spot's backend logger module.
144 lines (122 loc) âĒ 3.49 kB
text/typescript
/* eslint-disable no-bitwise */
import { format as formatDate } from 'date-fns'
import colorizer from 'json-colorizer'
import winston from 'winston'
import chalk from 'chalk'
const { timestamp, splat, json, errors, combine } = winston.format
/**
* @see https://github.com/winstonjs/winston#logging
*/
export enum LogLevel {
Error = 'error',
Warn = 'warn',
Info = 'info',
Http = 'http',
Verbose = 'verbose',
Debug = 'debug',
Silly = 'silly',
}
export enum LogOutput {
Summary = 'summary',
Details = 'details',
Silent = 'silent',
Raw = 'raw',
}
export const logIcons = new Map<LogLevel, string>([
[LogLevel.Error, 'ðĨ'],
[LogLevel.Warn, 'ð'],
[LogLevel.Info, 'ð'],
[LogLevel.Http, 'ð'],
[LogLevel.Verbose, 'ðŽ'],
[LogLevel.Debug, 'ð'],
[LogLevel.Silly, 'ðĪŠ'],
])
export interface LogReporter {
readonly version: string
readonly id: string
readonly ip: string
}
export interface LogData {
// Default info
readonly reporter: LogReporter
readonly timestamp: string
// Optional error stack
stack?: string
// Optional additional info
[prop: string]: any
}
const colors = {
STRING_LITERAL: 'white',
BOOLEAN_LITERAL: 'blue',
NUMBER_LITERAL: 'cyan',
NULL_LITERAL: 'magenta',
STRING_KEY: 'white.dim',
}
const getLogOutputType = (value: LogOutput = LogOutput.Raw) => {
if (!Object.values(LogOutput).includes(value)) {
// eslint-disable-next-line no-console
console.warn(
`The provided LOG_OUTPUT "${value}" is not supported. Defaulting to "${LogOutput.Raw}".`,
)
return LogOutput.Raw
}
return value
}
const getLogLevel = (value: LogLevel = LogLevel.Info) => {
if (!Object.values(LogLevel).includes(value)) {
// eslint-disable-next-line no-console
console.warn(
`The provided LOG_LEVEL "${value}" is not supported. Defaulting to "${LogLevel.Info}".`,
)
return LogLevel.Info
}
return value
}
const getReadableFormatter = (details = false) =>
winston.format.printf((info) => {
const { level, message, timestamp: ts } = info
const label = logIcons.get(level as LogLevel) || 'â'
const time = chalk.yellow(formatDate(new Date(ts), '[HH:mm:ss.SSS]'))
const header = chalk.bold(`${time} ${label} ${message}`)
const parts = [header]
if (details) {
parts.push(
colorizer(JSON.stringify(info), {
pretty: true,
colors,
}).replace(/\\n/g, '\n'), // To avoid having "\n" in the stack log
)
}
return parts.join('\n')
})
/**
* SpotLogger Class
*/
export const createLogger = (id: string, ip: string, version: string) => {
const { LOG_OUTPUT, LOG_LEVEL, RUNTIME_ENV, NODE_ENV } = process.env
const outputType = getLogOutputType(LOG_OUTPUT as LogOutput)
const logLevel = getLogLevel(LOG_LEVEL as LogLevel)
const formats = [errors({ stack: true }), timestamp(), splat(), json()]
// If any of the formatted output flags are set we pretty print.
if ([LogOutput.Details, LogOutput.Summary].includes(outputType)) {
formats.push(getReadableFormatter(outputType === LogOutput.Details))
}
return winston.createLogger({
format: combine(...formats),
level: logLevel,
transports: [
new winston.transports.Console({
silent: outputType === LogOutput.Silent,
}),
],
defaultMeta: {
environment: RUNTIME_ENV || NODE_ENV,
type: 'log',
reporter: {
version,
id,
ip,
},
},
})
}