Files
omnivore/packages/api/src/utils/logger.ts
2023-07-27 16:06:44 +08:00

161 lines
4.3 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { LoggingWinston } from '@google-cloud/logging-winston'
import { cloneDeep, isArray, isObject, isString, truncate } from 'lodash'
import { DateTime } from 'luxon'
import {
AdvancedConsoleLogger,
Logger as TypeOrmLogger,
LoggerOptions as TypeOrmLoggerOptions,
QueryRunner,
} from 'typeorm'
import {
config,
format,
Logger,
LoggerOptions,
loggers,
transports,
} from 'winston'
import TransportStream from 'winston-transport'
import { ConsoleTransportOptions } from 'winston/lib/winston/transports'
import { env } from '../env'
export class CustomTypeOrmLogger
extends AdvancedConsoleLogger
implements TypeOrmLogger
{
private logger: Logger
constructor(options?: TypeOrmLoggerOptions) {
super(options)
this.logger = buildLogger('typeorm')
}
logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
this.logger.debug(query, parameters, queryRunner)
}
log(
level: 'log' | 'info' | 'warn',
message: any,
queryRunner?: QueryRunner
): void {
this.logger.log(level, message, queryRunner)
}
}
const colors = {
emerg: 'inverse underline magenta',
alert: 'underline magenta',
crit: 'inverse underline red', // Any error that is forcing a shutdown of the service or application to prevent data loss.
error: 'underline red', // Any error which is fatal to the operation, but not the service or application
warning: 'underline yellow', // Anything that can potentially cause application oddities
notice: 'underline cyan', // Normal but significant condition
info: 'underline green', // Generally useful information to log
debug: 'underline gray',
}
const googleConfigs = {
level: 'info',
logName: 'logger',
levels: config.syslog.levels,
}
function localConfig(id: string): ConsoleTransportOptions {
return {
level: 'debug',
format: format.combine(
format.colorize({ all: true, colors }),
format((info) =>
Object.assign(info, {
timestamp: DateTime.local().toLocaleString(
DateTime.TIME_24_WITH_SECONDS
),
})
)(),
format.printf((info) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { timestamp, message, level, ...meta } = info
return `[${id}@${info.timestamp}] ${info.message}${
Object.keys(meta).length ? '\n' + JSON.stringify(meta, null, 4) : ''
}`
})
),
}
}
// truncate any string values in the object to a given length
const truncateObjectDeep = (object: any, length: number): any => {
const copyObj = cloneDeep(object) as never
const truncateDeep = (obj: any): any => {
if (isString(obj) && obj.length > length) {
return `${truncate(obj, { length })} [truncated]`
}
if (isArray(obj)) {
return obj.map((i) => truncateDeep(i) as never)
}
if (isObject(obj)) {
Object.entries(obj).forEach(([key, value]) => {
obj[key as keyof typeof obj] = truncateDeep(value) as never
})
return obj
}
// return everything else untouched
return obj
}
return truncateDeep(copyObj)
}
class GcpLoggingTransport extends LoggingWinston {
log(info: any, callback: (err: Error | null, apiResponse?: any) => void) {
const sizeInfo = JSON.stringify(info).length
if (sizeInfo > 250000) {
info = truncateObjectDeep(info, 5000) as never // the max length for string values is 5000
}
super.log(info, callback)
}
}
/**
* Builds a logger with common options, including a transport for GCP when running in the cloud.
* @param id Name of the log stream.
* @param options Logger Options
*/
export function buildLogger(id: string, options?: LoggerOptions): Logger {
const opt = {
...options,
...{ levels: config.syslog.levels, transports: [buildLoggerTransport(id)] },
}
return loggers.get(id, opt)
}
export function buildLoggerTransport(id: string): TransportStream {
return env.dev.isLocal
? new transports.Console(localConfig(id))
: new GcpLoggingTransport({ ...googleConfigs, ...{ logName: id } })
}
/**
* A handy open-ended structure for logging.
*/
export interface LogRecord {
labels?: {
[key: string]: any
source: string
}
[key: string]: any
}
export const logger = buildLogger('app')
export default {}