diff --git a/lib/logger/__snapshots__/err-serializer.spec.ts.snap b/lib/logger/__snapshots__/err-serializer.spec.ts.snap index 99b318b08bb5ef90baf8d4a42cc684a077ecfc81..8fb0bda601dfc318333ab39b5e35e9e7d59edc8a 100644 --- a/lib/logger/__snapshots__/err-serializer.spec.ts.snap +++ b/lib/logger/__snapshots__/err-serializer.spec.ts.snap @@ -33,7 +33,23 @@ Array [ ] `; -exports[`logger/err-serializer got handles http error 2`] = ` +exports[`logger/err-serializer got sanitize http error 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic OnRva2Vu", + "host": "github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "POST", + "url": "https://:token@github.com/api", + }, +] +`; + +exports[`logger/err-serializer got sanitize http error 2`] = ` Object { "code": undefined, "message": "Response code 412 (Precondition Failed)", diff --git a/lib/logger/err-serializer.spec.ts b/lib/logger/err-serializer.spec.ts index 2aa9d3ff76a9b2c7cdfd59eafd53372baf0208b2..16237ef8f2f045e6b0cec711e28816552cc740e4 100644 --- a/lib/logger/err-serializer.spec.ts +++ b/lib/logger/err-serializer.spec.ts @@ -2,7 +2,7 @@ import * as httpMock from '../../test/httpMock'; import { partial } from '../../test/util'; import * as hostRules from '../util/host-rules'; import { Http } from '../util/http'; -import configSerializer from './err-serializer'; +import errSerializer from './err-serializer'; import { sanitizeValue } from './utils'; describe('logger/err-serializer', () => { @@ -21,7 +21,7 @@ describe('logger/err-serializer', () => { }, }, }); - expect(configSerializer(err)).toMatchSnapshot(); + expect(errSerializer(err)).toMatchSnapshot(); }); it('handles missing fields', () => { const err = partial<Error & Record<string, unknown>>({ @@ -29,7 +29,7 @@ describe('logger/err-serializer', () => { stack: 'foo', body: 'some body', }); - expect(configSerializer(err)).toMatchSnapshot(); + expect(errSerializer(err)).toMatchSnapshot(); }); describe('got', () => { @@ -58,14 +58,29 @@ describe('logger/err-serializer', () => { try { await new Http('any').postJson('https://:token@github.com/api'); } catch (error) { - err = configSerializer(error); + err = errSerializer(error); } expect(httpMock.getTrace()).toMatchSnapshot(); expect(err).toBeDefined(); - expect(err.response.body).toBeDefined(); expect(err.options).toBeDefined(); + }); + + it('sanitize http error', async () => { + httpMock + .scope(baseUrl) + .post('/api') + .reply(412, { err: { message: 'failed' } }); + let err: any; + try { + await new Http('any').postJson('https://:token@github.com/api'); + } catch (error) { + err = error; + } + + expect(httpMock.getTrace()).toMatchSnapshot(); + expect(err).toBeDefined(); // remove platform related props delete err.timings; diff --git a/lib/logger/err-serializer.ts b/lib/logger/err-serializer.ts index 9aa9dc30a815d5c10d1129fe44332aa246ee1524..82b8b2963ebe0558db8dc9aecb946fe17b12f6e0 100644 --- a/lib/logger/err-serializer.ts +++ b/lib/logger/err-serializer.ts @@ -1,46 +1,10 @@ import is from '@sindresorhus/is'; -import { RequestError } from 'got'; -import { clone } from '../util/clone'; +import prepareError from './utils'; Error.stackTraceLimit = 20; export default function errSerializer(err: Error): any { - const response: Record<string, unknown> = { - ...err, - }; - - // Can maybe removed? - if (!response.message && err.message) { - response.message = err.message; - } - - // Can maybe removed? - if (!response.stack && err.stack) { - response.stack = err.stack; - } - - // handle got error - if (err instanceof RequestError) { - const options: Record<string, unknown> = { - headers: clone(err.options.headers), - url: err.options.url?.toString(), - }; - response.options = options; - - for (const k of ['username', 'password', 'method', 'http2']) { - options[k] = err.options[k]; - } - - if (err.response) { - response.response = { - statusCode: err.response?.statusCode, - statusMessage: err.response?.statusMessage, - body: clone(err.response.body), - headers: clone(err.response.headers), - httpVersion: err.response.httpVersion, - }; - } - } + const response: Record<string, unknown> = prepareError(err); // already done by `sanitizeValue` ? const redactedFields = ['message', 'stack', 'stdout', 'stderr']; diff --git a/lib/logger/index.ts b/lib/logger/index.ts index 9e06d13b33ccdf12de9bcfc8f8dd8f760c8aa487..2d8e3ed7757a3b612425d134ea9767993fcdd7fc 100644 --- a/lib/logger/index.ts +++ b/lib/logger/index.ts @@ -21,10 +21,13 @@ const problems = new ProblemStream(); const stdout: bunyan.Stream = { name: 'stdout', - level: (process.env.LOG_LEVEL as bunyan.LogLevel) || 'info', + level: + (process.env.LOG_LEVEL as bunyan.LogLevel) || + /* istanbul ignore next: not testable */ 'info', stream: process.stdout, }; +// istanbul ignore else: not testable if (process.env.LOG_FORMAT !== 'json') { // TODO: typings const prettyStdOut = new RenovateStream() as any; diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index 4cf60954eb3502dc5debcc059c3359ff69225ef7..955f088d3f0c21d0fbb603f050f2736a2ccd9bd6 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -1,6 +1,8 @@ import { Stream } from 'stream'; import bunyan from 'bunyan'; import fs from 'fs-extra'; +import { RequestError } from 'got'; +import { clone } from '../util/clone'; import { redactedFields, sanitize } from '../util/sanitize'; export interface BunyanRecord extends Record<string, any> { @@ -49,6 +51,48 @@ const contentFields = [ 'yarnLockParsed', ]; +export default function prepareError(err: Error): Record<string, unknown> { + const response: Record<string, unknown> = { + ...err, + }; + + // Required as message is non-enumerable + if (!response.message && err.message) { + response.message = err.message; + } + + // Required as stack is non-enumerable + if (!response.stack && err.stack) { + response.stack = err.stack; + } + + // handle got error + if (err instanceof RequestError) { + const options: Record<string, unknown> = { + headers: clone(err.options.headers), + url: err.options.url?.toString(), + }; + response.options = options; + + for (const k of ['username', 'password', 'method', 'http2']) { + options[k] = err.options[k]; + } + + // istanbul ignore else + if (err.response) { + response.response = { + statusCode: err.response?.statusCode, + statusMessage: err.response?.statusMessage, + body: clone(err.response.body), + headers: clone(err.response.headers), + httpVersion: err.response.httpVersion, + }; + } + } + + return response; +} + export function sanitizeValue(value: unknown, seen = new WeakMap()): any { if (Array.isArray(value)) { const length = value.length; @@ -67,6 +111,11 @@ export function sanitizeValue(value: unknown, seen = new WeakMap()): any { return '[content]'; } + if (value instanceof Error) { + // eslint-disable-next-line no-param-reassign + value = prepareError(value); + } + const valueType = typeof value; if (value != null && valueType !== 'function' && valueType === 'object') {