diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts index a6c8a0e0ae6094..1a566fae72117c 100644 --- a/lib/logger/index.spec.ts +++ b/lib/logger/index.spec.ts @@ -3,7 +3,7 @@ import fs from 'fs-extra'; import { partial } from '../../test/util'; import { add } from '../util/host-rules'; import { addSecretForSanitizing as addSecret } from '../util/sanitize'; -import type { RenovateLogger } from '.'; +import type { RenovateLogger } from './renovate-logger'; import { addMeta, addStream, diff --git a/lib/logger/index.ts b/lib/logger/index.ts index e6d27079c7c748..da81437eba9a6e 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -6,9 +6,8 @@ import upath from 'upath'; import cmdSerializer from './cmd-serializer'; import configSerializer from './config-serializer'; import errSerializer from './err-serializer'; -import { once, reset as onceReset } from './once'; import { RenovateStream } from './pretty-stdout'; -import { getRemappedLevel } from './remap'; +import { RenovateLogger } from './renovate-logger'; import type { BunyanRecord, Logger } from './types'; import { ProblemStream, @@ -20,19 +19,11 @@ import { const problems = new ProblemStream(); let stdoutLevel = validateLogLevel(getEnv('LOG_LEVEL'), 'info'); -export function getProblems(): BunyanRecord[] { - return problems.getProblems(); -} - -export function clearProblems(): void { - return problems.clearProblems(); -} - export function logLevel(): bunyan.LogLevelString { return stdoutLevel; } -function createDefaultStreams( +export function createDefaultStreams( stdoutLevel: bunyan.LogLevelString, problems: ProblemStream, logFile: string | undefined, @@ -82,141 +73,22 @@ export function createLogFileStream(logFile: string): bunyan.Stream { }; } -type loggerFunction = (p1: string | Record, p2?: string) => void; - -function logFactory( - bunyanLogger: bunyan, - _level: bunyan.LogLevelString, - curMeta: Record, - logContext: string, -): loggerFunction { - return function (p1: string | Record, p2?: string): void { - const meta: Record = { - logContext, - ...curMeta, - ...toMeta(p1), - }; - const msg = getMessage(p1, p2); - let level = _level; - - if (is.string(msg)) { - const remappedLevel = getRemappedLevel(msg); - // istanbul ignore if: not testable - if (remappedLevel) { - meta.oldLevel = level; - level = remappedLevel; - } - bunyanLogger[level](meta, msg); - } else { - bunyanLogger[level](meta); - } - }; -} - -function getMessage( - p1: string | Record, - p2?: string, -): string | undefined { - return is.string(p1) ? p1 : p2; -} - -function toMeta(p1: string | Record): Record { - return is.object(p1) ? p1 : {}; -} - -const loggerLevels: bunyan.LogLevelString[] = [ - 'trace', - 'debug', - 'info', - 'warn', - 'error', - 'fatal', -]; - -export class RenovateLogger implements Logger { - logger: Logger = { once: { reset: onceReset } } as any; - - constructor( - private readonly bunyanLogger: bunyan, - private context: string, - private meta: Record, - ) { - for (const level of loggerLevels) { - this.logger[level] = this.logFactory(level) as never; - this.logger.once[level] = this.logOnceFn(level); - } - } - - addStream(stream: bunyan.Stream): void { - this.bunyanLogger.addStream(withSanitizer(stream)); - } - - childLogger(): RenovateLogger { - return new RenovateLogger( - this.bunyanLogger.child({}), - this.context, - this.meta, - ); - } - - trace = this.log.bind(this, 'trace'); - debug = this.log.bind(this, 'debug'); - info = this.log.bind(this, 'info'); - warn = this.log.bind(this, 'warn'); - error = this.log.bind(this, 'error'); - fatal = this.log.bind(this, 'fatal'); - - once = this.logger.once; - - get logContext(): string { - return this.context; - } - - set logContext(context: string) { - this.context = context; - } - - setMeta(obj: Record): void { - this.meta = { ...obj }; - } - - addMeta(obj: Record): void { - this.meta = { ...this.meta, ...obj }; - } - - removeMeta(fields: string[]): void { - for (const key of Object.keys(this.meta)) { - if (fields.includes(key)) { - delete this.meta[key]; - } - } - } - - private logFactory(level: bunyan.LogLevelString): loggerFunction { - return logFactory(this.bunyanLogger, level, this.meta, this.context); - } - - private logOnceFn(level: bunyan.LogLevelString): loggerFunction { - const logOnceFn = (p1: string | Record, p2?: string): void => { - once(() => { - this.log(level, p1, p2); - }, logOnceFn); - }; - return logOnceFn; - } - - private log( - level: bunyan.LogLevelString, - p1: string | Record, - p2?: string, - ): void { - const logFn = this.logger[level]; - if (is.string(p1)) { - logFn(p1); - } else { - logFn(p1, p2); - } - } +export function createSanitizerLogger(streams: bunyan.Stream[]): bunyan { + return bunyan.createLogger({ + name: 'renovate', + serializers: { + body: configSerializer, + cmd: cmdSerializer, + config: configSerializer, + migratedConfig: configSerializer, + originalConfig: configSerializer, + presetConfig: configSerializer, + oldConfig: configSerializer, + newConfig: configSerializer, + err: errSerializer, + }, + streams: streams.map(withSanitizer), + }); } const defaultStreams = createDefaultStreams( @@ -224,22 +96,8 @@ const defaultStreams = createDefaultStreams( problems, getEnv('LOG_FILE'), ); -const bunyanLogger = bunyan.createLogger({ - name: 'renovate', - serializers: { - body: configSerializer, - cmd: cmdSerializer, - config: configSerializer, - migratedConfig: configSerializer, - originalConfig: configSerializer, - presetConfig: configSerializer, - oldConfig: configSerializer, - newConfig: configSerializer, - err: errSerializer, - }, - streams: defaultStreams.map(withSanitizer), -}); +const bunyanLogger = createSanitizerLogger(defaultStreams); const logContext: string = getEnv('LOG_CONTEXT') ?? nanoid(); const loggerInternal = new RenovateLogger(bunyanLogger, logContext, {}); @@ -289,3 +147,11 @@ export function levels( stdoutLevel = level; } } + +export function getProblems(): BunyanRecord[] { + return problems.getProblems(); +} + +export function clearProblems(): void { + return problems.clearProblems(); +} diff --git a/lib/logger/once.spec.ts b/lib/logger/once.spec.ts index 4059a4f124956a..64523e21204cc4 100644 --- a/lib/logger/once.spec.ts +++ b/lib/logger/once.spec.ts @@ -1,5 +1,4 @@ import { once, reset } from './once'; -import type { RenovateLogger } from '.'; import { logger } from '.'; jest.unmock('.'); @@ -56,7 +55,7 @@ describe('logger/once', () => { describe('logger', () => { it('logs once per function call', () => { - const debug = jest.spyOn((logger as RenovateLogger).logger, 'debug'); + const debug = jest.spyOn(logger, 'debug'); function doSomething() { logger.once.debug('test'); @@ -69,8 +68,8 @@ describe('logger/once', () => { }); it('distincts between log levels', () => { - const debug = jest.spyOn((logger as RenovateLogger).logger, 'debug'); - const info = jest.spyOn((logger as RenovateLogger).logger, 'info'); + const debug = jest.spyOn(logger, 'debug'); + const info = jest.spyOn(logger, 'info'); function doSomething() { logger.once.debug('test'); @@ -85,7 +84,7 @@ describe('logger/once', () => { }); it('distincts between different log statements', () => { - const debug = jest.spyOn((logger as RenovateLogger).logger, 'debug'); + const debug = jest.spyOn(logger, 'debug'); function doSomething() { logger.once.debug('foo'); @@ -102,7 +101,7 @@ describe('logger/once', () => { }); it('allows mixing single-time and regular logging', () => { - const debug = jest.spyOn((logger as RenovateLogger).logger, 'debug'); + const debug = jest.spyOn(logger, 'debug'); function doSomething() { logger.once.debug('foo'); @@ -124,7 +123,7 @@ describe('logger/once', () => { }); it('supports reset method', () => { - const debug = jest.spyOn((logger as RenovateLogger).logger, 'debug'); + const debug = jest.spyOn(logger, 'debug'); function doSomething() { logger.once.debug('foo'); diff --git a/lib/logger/renovate-logger.ts b/lib/logger/renovate-logger.ts new file mode 100644 index 00000000000000..9efa12e8174f02 --- /dev/null +++ b/lib/logger/renovate-logger.ts @@ -0,0 +1,137 @@ +import is from '@sindresorhus/is'; +import type * as bunyan from 'bunyan'; +import { once, reset as onceReset } from './once'; +import { getRemappedLevel } from './remap'; +import type { Logger } from './types'; +import { getMessage, toMeta, withSanitizer } from './utils'; + +const loggerLevels: bunyan.LogLevelString[] = [ + 'trace', + 'debug', + 'info', + 'warn', + 'error', + 'fatal', +]; + +type loggerFunction = (p1: string | Record, p2?: string) => void; + +export class RenovateLogger implements Logger { + logger: Logger = { once: { reset: onceReset } } as any; + + constructor( + private readonly bunyanLogger: bunyan, + private context: string, + private meta: Record, + ) { + for (const level of loggerLevels) { + this.logger[level] = this.logFactory(level) as never; + this.logger.once[level] = this.logOnceFn(level); + } + } + + addStream(stream: bunyan.Stream): void { + this.bunyanLogger.addStream(withSanitizer(stream)); + } + + childLogger(): RenovateLogger { + return new RenovateLogger( + this.bunyanLogger.child({}), + this.context, + this.meta, + ); + } + + trace = this.log.bind(this, 'trace'); + debug = this.log.bind(this, 'debug'); + info = this.log.bind(this, 'info'); + warn = this.log.bind(this, 'warn'); + error = this.log.bind(this, 'error'); + fatal = this.log.bind(this, 'fatal'); + + once = this.logger.once; + + get logContext(): string { + return this.context; + } + + set logContext(context: string) { + this.context = context; + } + + setMeta(obj: Record): void { + this.meta = { ...obj }; + } + + addMeta(obj: Record): void { + this.meta = { ...this.meta, ...obj }; + } + + removeMeta(fields: string[]): void { + for (const key of Object.keys(this.meta)) { + if (fields.includes(key)) { + delete this.meta[key]; + } + } + } + + private logFactory(level: bunyan.LogLevelString): loggerFunction { + return logFactory(this.bunyanLogger, level, this.meta, this.context); + } + + private logOnceFn(level: bunyan.LogLevelString): loggerFunction { + const logOnceFn = (p1: string | Record, p2?: string): void => { + once(() => { + const logFn = this[level]; + if (is.string(p1)) { + logFn(p1); + } else { + logFn(p1, p2); + } + }, logOnceFn); + }; + return logOnceFn; + } + + private log( + level: bunyan.LogLevelString, + p1: string | Record, + p2?: string, + ): void { + const logFn = this.logger[level]; + if (is.string(p1)) { + logFn(p1); + } else { + logFn(p1, p2); + } + } +} + +function logFactory( + bunyanLogger: bunyan, + _level: bunyan.LogLevelString, + curMeta: Record, + logContext: string, +): loggerFunction { + return function (p1: string | Record, p2?: string): void { + const meta: Record = { + logContext, + ...curMeta, + ...toMeta(p1), + }; + const msg = getMessage(p1, p2); + let level = _level; + + if (is.string(msg)) { + const remappedLevel = getRemappedLevel(msg); + // istanbul ignore if: not testable + if (remappedLevel) { + meta.oldLevel = level; + level = remappedLevel; + } + bunyanLogger[level](meta, msg); + } else { + bunyanLogger[level](meta); + } + }; +} diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index 5b2d512601ac71..b85f2c44de22e4 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -339,3 +339,16 @@ export function getEnv(key: string): string | undefined { .map((v) => v?.toLowerCase().trim()) .find(is.nonEmptyStringAndNotWhitespace); } + +export function getMessage( + p1: string | Record, + p2?: string, +): string | undefined { + return is.string(p1) ? p1 : p2; +} + +export function toMeta( + p1: string | Record, +): Record { + return is.object(p1) ? p1 : {}; +}