diff --git a/lib/logger/__mocks__/index.ts b/lib/logger/__mocks__/index.ts index 073ffa76d47e19660d32d3e62131a18ffca9fde1..b303a70ad2212b1584ddffa7da63553a7aeeac09 100644 --- a/lib/logger/__mocks__/index.ts +++ b/lib/logger/__mocks__/index.ts @@ -16,5 +16,6 @@ loggerLevels.forEach(k => { export const setMeta = jest.fn(); export const levels = jest.fn(); export const addStream = jest.fn(); +export const getErrors = () => []; export { logger }; diff --git a/lib/logger/index.ts b/lib/logger/index.ts index b8a6d90cbcda4a3a06998228de46ff60a24fb0b7..6fe6dd27515d79acff76a467fa3a365e2b37a98b 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -5,8 +5,16 @@ import { RenovateStream } from './pretty-stdout'; import configSerializer from './config-serializer'; import errSerializer from './err-serializer'; import cmdSerializer from './cmd-serializer'; +import { ErrorStream } from './utils'; let meta = {}; +export interface LogError { + level: bunyan.LogLevel; + meta: any; + msg?: string; +} + +const errors = new ErrorStream(); const stdout: bunyan.Stream = { name: 'stdout', @@ -33,13 +41,19 @@ const bunyanLogger = bunyan.createLogger({ presetConfig: configSerializer, err: errSerializer, }, - streams: [stdout], + streams: [ + stdout, + { + name: 'error', + level: 'error' as bunyan.LogLevel, + stream: errors as any, + type: 'raw', + }, + ], }); const logFactory = (level: bunyan.LogLevelString): any => { return (p1: any, p2: any): void => { - global.renovateError = - global.renovateError || level === 'error' || level === 'fatal'; if (p2) { // meta and msg provided bunyanLogger[level]({ ...meta, ...p1 }, p2); @@ -97,3 +111,7 @@ export /* istanbul ignore next */ function addStream( export function levels(name: string, level: bunyan.LogLevel): void { bunyanLogger.levels(name, level); } + +export function getErrors() { + return errors.getErrors(); +} diff --git a/lib/logger/pretty-stdout.ts b/lib/logger/pretty-stdout.ts index 676c1c345acf86dd7538bc9f9cb0a0ecf5ebc2d8..318a528d0bb13177062c0fb745a9078f28247e1f 100644 --- a/lib/logger/pretty-stdout.ts +++ b/lib/logger/pretty-stdout.ts @@ -5,6 +5,7 @@ import * as util from 'util'; import { Stream } from 'stream'; import chalk from 'chalk'; import stringify from 'json-stringify-pretty-compact'; +import { BunyanRecord } from './utils'; const bunyanFields = [ 'name', @@ -100,9 +101,3 @@ export class RenovateStream extends Stream { return true; } } - -export interface BunyanRecord extends Record<string, any> { - level: number; - msg: string; - module?: string; -} diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..7730d2c7d54f40c52e33d2c9202ed4d302bb30e6 --- /dev/null +++ b/lib/logger/utils.ts @@ -0,0 +1,34 @@ +import { Stream } from 'stream'; + +export interface BunyanRecord extends Record<string, any> { + level: number; + msg: string; + module?: string; +} + +const excludeProps = ['pid', 'time', 'v', 'hostname']; + +export class ErrorStream extends Stream { + private _errors: BunyanRecord[] = []; + + readable: boolean; + + writable: boolean; + + constructor() { + super(); + this.readable = false; + this.writable = true; + } + + write(data: BunyanRecord) { + const err = { ...data }; + for (const prop of excludeProps) delete err[prop]; + this._errors.push(err); + return true; + } + + getErrors() { + return this._errors; + } +} diff --git a/lib/renovate.ts b/lib/renovate.ts index 69faef340003e548281da1ce3fe5a4f0d41d52cc..e44bbd478f94276da997ccdaa19a7f36e4c33497 100644 --- a/lib/renovate.ts +++ b/lib/renovate.ts @@ -6,9 +6,5 @@ import * as globalWorker from './workers/global'; proxy.bootstrap(); (async () => { - await globalWorker.start(); - // istanbul ignore if - if ((global as any).renovateError) { - process.exitCode = 1; - } + process.exitCode = await globalWorker.start(); })(); diff --git a/lib/types.d.ts b/lib/types.d.ts index edca5fb38c671fd9c6442214cb9d508af8ebdfa3..c07b0a454338177b75ad25b06d47507a8957d3c2 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -26,7 +26,6 @@ declare namespace NodeJS { interface Global { appMode?: boolean; gitAuthor?: { name: string; email: string }; - renovateError?: boolean; renovateVersion: string; // TODO: declare interface for all platforms platform: typeof import('./platform/github'); diff --git a/lib/workers/global/index.js b/lib/workers/global/index.js index a960078cf5ec39cd2d0411ce399b68d9aac87675..2e3ec31f0541ae657299296c4039db8b19bd233a 100644 --- a/lib/workers/global/index.js +++ b/lib/workers/global/index.js @@ -3,7 +3,7 @@ import is from '@sindresorhus/is'; const fs = require('fs-extra'); const os = require('os'); const path = require('path'); -const { logger, setMeta } = require('../../logger'); +const { logger, setMeta, getErrors } = require('../../logger'); const configParser = require('../../config'); const repositoryWorker = require('../repository'); const cache = require('./cache'); @@ -75,6 +75,16 @@ async function start() { logger.fatal({ err }, `Fatal error: ${err.message}`); } } + const loggerErrors = getErrors(); + /* istanbul ignore if */ + if (loggerErrors.length) { + logger.info( + { loggerErrors }, + 'Renovate is exiting with a non-zero code due to the following logged errors' + ); + return 1; + } + return 0; } // istanbul ignore next diff --git a/test/logger/__snapshots__/index.spec.ts.snap b/test/logger/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..751bb6c0f6cd3bb84d11fd9ea6c9fd03b05df769 --- /dev/null +++ b/test/logger/__snapshots__/index.spec.ts.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`logger saves errors 1`] = ` +Array [ + Object { + "any": "test", + "level": 50, + "msg": "some meta", + "name": "renovate", + }, + Object { + "any": "test", + "level": 50, + "msg": "", + "name": "renovate", + "some": "meta", + }, + Object { + "any": "test", + "level": 50, + "msg": "message", + "name": "renovate", + "some": "meta", + }, +] +`; diff --git a/test/logger/index.spec.ts b/test/logger/index.spec.ts index 2521befed3acaddf5af91e4e642afca496062d67..c895df090bad55c2b5dbc622da9c2d1f51af807d 100644 --- a/test/logger/index.spec.ts +++ b/test/logger/index.spec.ts @@ -1,4 +1,4 @@ -import { logger, setMeta, levels } from '../../lib/logger'; +import { logger, setMeta, levels, getErrors } from '../../lib/logger'; jest.unmock('../../lib/logger'); @@ -23,4 +23,12 @@ describe('logger', () => { it('sets level', () => { levels('stdout', 'debug'); }); + + it('saves errors', () => { + levels('stdout', 'fatal'); + logger.error('some meta'); + logger.error({ some: 'meta' }); + logger.error({ some: 'meta' }, 'message'); + expect(getErrors()).toMatchSnapshot(); + }); }); diff --git a/test/logger/pretty-stdout.spec.ts b/test/logger/pretty-stdout.spec.ts index 16e70420740b6591b4c2397f1449966799090948..1a215dda14ebb4bd1759c8ad820df8d47c3060d5 100644 --- a/test/logger/pretty-stdout.spec.ts +++ b/test/logger/pretty-stdout.spec.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; import * as prettyStdout from '../../lib/logger/pretty-stdout'; +import { BunyanRecord } from '../../lib/logger/utils'; jest.mock('chalk', () => ['bgRed', 'blue', 'gray', 'green', 'magenta', 'red'].reduce( @@ -76,7 +77,7 @@ describe('logger/pretty-stdout', () => { delete process.env.FORCE_COLOR; }); it('formats record', () => { - const rec: prettyStdout.BunyanRecord = { + const rec: BunyanRecord = { level: 10, msg: 'test message', v: 0,