From 8e48d97392c3f9e24e319116fd00d590e70eb727 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@keylocation.sg>
Date: Wed, 19 Jul 2017 08:05:26 +0200
Subject: [PATCH] feat(logs): Support custom log detail fields

This removes the need to JSON.stringify() objects in logs. Default bunyan fields plus our custom meta fields are stripped out, and everything else is stringified in the details field.

Closes #498
---
 lib/api/github.js                             | 58 ++++++++-----------
 lib/api/gitlab.js                             | 15 ++---
 lib/api/npm.js                                |  5 +-
 lib/config/index.js                           |  4 +-
 lib/logger/pretty-stdout.js                   | 39 +++++++++----
 lib/workers/branch/index.js                   |  8 +--
 lib/workers/branch/schedule.js                |  8 +--
 lib/workers/dep-type/index.js                 |  6 +-
 lib/workers/global/versions.js                |  4 +-
 lib/workers/package-file/index.js             |  5 +-
 lib/workers/package/index.js                  |  2 +-
 lib/workers/pr/changelog.js                   |  4 +-
 lib/workers/repository/apis.js                |  6 +-
 lib/workers/repository/cleanup.js             |  5 +-
 lib/workers/repository/onboarding.js          |  2 +-
 .../__snapshots__/pretty-stdout.spec.js.snap  |  2 +-
 test/logger/pretty-stdout.spec.js             |  7 ++-
 test/workers/pr/changelog.spec.js             | 15 +++++
 18 files changed, 105 insertions(+), 90 deletions(-)

diff --git a/lib/api/github.js b/lib/api/github.js
index e60589c8d9..3e9b56e81b 100644
--- a/lib/api/github.js
+++ b/lib/api/github.js
@@ -58,7 +58,7 @@ async function getInstallations(appToken) {
     logger.debug(`Returning ${res.body.length} results`);
     return res.body;
   } catch (err) {
-    logger.error(`GitHub getInstallations error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `GitHub getInstallations error`);
     throw err;
   }
 }
@@ -77,7 +77,7 @@ async function getInstallationToken(appToken, installationId) {
     const res = await ghGot.post(url, options);
     return res.body.token;
   } catch (err) {
-    logger.error(`GitHub getInstallationToken error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `GitHub getInstallationToken error`);
     throw err;
   }
 }
@@ -100,9 +100,7 @@ async function getInstallationRepositories(userToken) {
     );
     return res.body;
   } catch (err) {
-    logger.error(
-      `GitHub getInstallationRepositories error: ${JSON.stringify(err)}`
-    );
+    logger.error({ err }, `GitHub getInstallationRepositories error`);
     throw err;
   }
 }
@@ -122,7 +120,7 @@ async function getRepos(token, endpoint) {
     const res = await ghGot('user/repos');
     return res.body.map(repo => repo.full_name);
   } catch (err) /* istanbul ignore next */ {
-    logger.error(`GitHub getRepos error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `GitHub getRepos error`);
     throw err;
   }
 }
@@ -130,7 +128,7 @@ async function getRepos(token, endpoint) {
 // Initialize GitHub by getting base branch and SHA
 async function initRepo(repoName, token, endpoint, repoLogger) {
   logger = repoLogger || logger;
-  logger.debug(`initRepo(${JSON.stringify(repoName)})`);
+  logger.debug(`initRepo("${repoName}")`);
   if (repoLogger) {
     logger = repoLogger;
   }
@@ -167,7 +165,7 @@ async function initRepo(repoName, token, endpoint, repoLogger) {
       logger.debug('Repository is not initiated');
       throw new Error('uninitiated');
     }
-    logger.error('Unknown GitHub initRepo error');
+    logger.error({ err }, 'Unknown GitHub initRepo error');
     throw err;
   }
   return config;
@@ -251,7 +249,7 @@ async function isBranchStale(branchName) {
   const branchCommit = await getBranchCommit(branchName);
   logger.debug(`branchCommit=${branchCommit}`);
   const commitDetails = await getCommitDetails(branchCommit);
-  logger.debug(`commitDetails=${JSON.stringify(commitDetails)}`);
+  logger.debug({ commitDetails }, `commitDetails`);
   const parentSha = commitDetails.parents[0].sha;
   logger.debug(`parentSha=${parentSha}`);
   // Return true if the SHAs don't match
@@ -281,11 +279,7 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
   }
   if (requiredStatusChecks.length) {
     // This is Unsupported
-    logger.warn(
-      `Unsupported requiredStatusChecks: ${JSON.stringify(
-        requiredStatusChecks
-      )}`
-    );
+    logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
     return 'failed';
   }
   const gotString = `repos/${config.repoName}/commits/${branchName}/status`;
@@ -310,8 +304,7 @@ async function mergeBranch(branchName, mergeType) {
     try {
       await ghGot.patch(url, options);
     } catch (err) {
-      logger.error(`Error pushing branch merge for ${branchName}`);
-      logger.debug(JSON.stringify(err));
+      logger.error({ err }, `Error pushing branch merge for ${branchName}`);
       throw new Error('branch-push failed');
     }
   } else if (mergeType === 'branch-merge-commit') {
@@ -325,8 +318,7 @@ async function mergeBranch(branchName, mergeType) {
     try {
       await ghGot.post(url, options);
     } catch (err) {
-      logger.error(`Error pushing branch merge for ${branchName}`);
-      logger.debug(JSON.stringify(err));
+      logger.error({ err }, `Error pushing branch merge for ${branchName}`);
       throw new Error('branch-push failed');
     }
   } else {
@@ -446,7 +438,7 @@ async function getPr(prNo) {
         `repos/${config.repoName}/pulls/${prNo}/commits`
       )).body;
       const authors = prCommits.reduce((arr, commit) => {
-        logger.trace(`Checking commit: ${JSON.stringify(commit)}`);
+        logger.trace({ commit }, `Checking commit`);
         let author = 'unknown';
         if (commit.author) {
           author = commit.author.login;
@@ -496,39 +488,37 @@ async function mergePr(pr) {
     // This path is taken if we have auto-detected the allowed merge types from the repo
     options.body.merge_method = config.mergeMethod;
     try {
-      logger.debug(`mergePr: ${url}, ${JSON.stringify(options)}`);
+      logger.debug({ options, url }, `mergePr`);
       await ghGot.put(url, options);
     } catch (err) {
-      logger.error(
-        `Failed to ${options.body.merge_method} PR: ${JSON.stringify(err)}`
-      );
+      logger.error({ err }, `Failed to ${options.body.merge_method} PR`);
       return;
     }
   } else {
     // We need to guess the merge method and try squash -> rebase -> merge
     options.body.merge_method = 'rebase';
     try {
-      logger.debug(`mergePr: ${url}, ${JSON.stringify(options)}`);
+      logger.debug({ options, url }, `mergePr`);
       await ghGot.put(url, options);
     } catch (err1) {
-      logger.debug(
-        `Failed to ${options.body.merge_method} PR: ${JSON.stringify(err1)}`
-      );
+      logger.debug({ err: err1 }, `Failed to ${options.body.merge_method} PR}`);
       try {
         options.body.merge_method = 'squash';
-        logger.debug(`mergePr: ${url}, ${JSON.stringify(options)}`);
+        logger.debug({ options, url }, `mergePr`);
         await ghGot.put(url, options);
       } catch (err2) {
         logger.debug(
-          `Failed to ${options.body.merge_method} PR: ${JSON.stringify(err2)}`
+          { err: err2 },
+          `Failed to ${options.body.merge_method} PR`
         );
         try {
           options.body.merge_method = 'merge';
-          logger.debug(`mergePr: ${url}, ${JSON.stringify(options)}`);
+          logger.debug({ options, url }, `mergePr`);
           await ghGot.put(url, options);
         } catch (err3) {
           logger.debug(
-            `Failed to ${options.body.merge_method} PR: ${JSON.stringify(err3)}`
+            { err: err3 },
+            `Failed to ${options.body.merge_method} PR`
           );
           logger.error('All merge attempts failed');
           return;
@@ -574,7 +564,7 @@ async function getFileJson(filePath, branchName) {
   try {
     fileJson = JSON.parse(await getFileContent(filePath, branchName));
   } catch (err) {
-    logger.error(`Failed to parse JSON for ${filePath}`);
+    logger.error({ err }, `Failed to parse JSON for ${filePath}`);
   }
   return fileJson;
 }
@@ -681,7 +671,7 @@ async function createTree(baseTree, files) {
       sha: file.blob,
     });
   });
-  logger.debug(body);
+  logger.trace({ body }, 'createTree body');
   return (await ghGot.post(`repos/${config.repoName}/git/trees`, { body })).body
     .sha;
 }
@@ -704,7 +694,7 @@ async function getCommitMessages() {
     const res = await ghGot(`repos/${config.repoName}/commits`);
     return res.body.map(commit => commit.commit.message);
   } catch (err) {
-    logger.error(`getCommitMessages error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `getCommitMessages error`);
     return [];
   }
 }
diff --git a/lib/api/gitlab.js b/lib/api/gitlab.js
index c2b6a1e47d..62f5d70d6b 100644
--- a/lib/api/gitlab.js
+++ b/lib/api/gitlab.js
@@ -65,7 +65,7 @@ async function getRepos(token, endpoint) {
     logger.info(`Discovered ${projects.length} project(s)`);
     return projects;
   } catch (err) {
-    logger.error(`GitLab getRepos error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `GitLab getRepos error`);
     throw err;
   }
 }
@@ -105,7 +105,7 @@ async function initRepo(repoName, token, endpoint, repoLogger) {
     // Discover our user email
     config.email = (await glGot(`user`)).body.email;
   } catch (err) {
-    logger.error(`GitLab init error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `GitLab init error`);
     throw err;
   }
   return config;
@@ -158,8 +158,7 @@ async function getBranch(branchName) {
   try {
     return (await glGot(url)).body;
   } catch (err) {
-    logger.warn(`Failed to getBranch ${branchName}`);
-    logger.debug(JSON.stringify(err));
+    logger.warn({ err }, `Failed to getBranch ${branchName}`);
     return null;
   }
 }
@@ -191,11 +190,7 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
   }
   if (requiredStatusChecks.length) {
     // This is Unsupported
-    logger.warn(
-      `Unsupported requiredStatusChecks: ${JSON.stringify(
-        requiredStatusChecks
-      )}`
-    );
+    logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
     return 'failed';
   }
   // First, get the branch to find the commit SHA
@@ -479,7 +474,7 @@ async function getCommitMessages() {
     const res = await glGot(`projects/${config.repoName}/repository/commits`);
     return res.body.map(commit => commit.title);
   } catch (err) {
-    logger.error(`getCommitMessages error: ${JSON.stringify(err)}`);
+    logger.error({ err }, `getCommitMessages error`);
     return [];
   }
 }
diff --git a/lib/api/npm.js b/lib/api/npm.js
index 98c5987ed5..58b31fda80 100644
--- a/lib/api/npm.js
+++ b/lib/api/npm.js
@@ -76,11 +76,10 @@ async function getDependency(name, logger) {
       dep.versions[version] = {};
     });
     npmCache[cacheKey] = dep;
-    logger.trace({ dependency: dep }, JSON.stringify(dep));
+    logger.trace({ dependency: dep }, 'dependency');
     return dep;
   } catch (err) {
-    logger.debug(`Dependency not found: ${name}`);
-    logger.debug(`err: ${JSON.stringify(err)}`);
+    logger.debug({ err }, `Dependency not found: ${name}`);
     return null;
   }
 }
diff --git a/lib/config/index.js b/lib/config/index.js
index 15324004e6..b5439da381 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -142,9 +142,7 @@ function mergeChildConfig(parentConfig, childConfig) {
           childConfig[option.name]
         );
       }
-      logger.debug(
-        `config.${option.name}=${JSON.stringify(config[option.name])}`
-      );
+      logger.debug({ option: config[option.name] }, `config.${option.name}`);
     }
   }
   return config;
diff --git a/lib/logger/pretty-stdout.js b/lib/logger/pretty-stdout.js
index 7fad24e171..919d2efbe9 100644
--- a/lib/logger/pretty-stdout.js
+++ b/lib/logger/pretty-stdout.js
@@ -6,6 +6,15 @@ const util = require('util');
 const chalk = require('chalk');
 const stringify = require('json-stringify-pretty-compact');
 
+const bunyanFields = ['name', 'hostname', 'pid', 'level', 'v', 'time', 'msg'];
+const metaFields = [
+  'repository',
+  'packageFile',
+  'depType',
+  'dependency',
+  'branch',
+];
+
 const levels = {
   10: chalk.gray('TRACE'),
   20: chalk.blue('DEBUG'),
@@ -24,25 +33,33 @@ function getMeta(rec) {
   if (!rec) {
     return '';
   }
-  const metaFields = [
-    'repository',
-    'packageFile',
-    'depType',
-    'dependency',
-    'branch',
-  ].filter(elem => rec[elem]);
-  if (!metaFields.length) {
+  const filteredMeta = metaFields.filter(elem => rec[elem]);
+  if (!filteredMeta.length) {
     return '';
   }
-  const metaStr = metaFields.map(field => `${field}=${rec[field]}`).join(', ');
+  const metaStr = filteredMeta
+    .map(field => `${field}=${rec[field]}`)
+    .join(', ');
   return chalk.gray(` (${metaStr})`);
 }
 
 function getDetails(rec) {
-  if (!rec || !rec.config) {
+  if (!rec) {
+    return '';
+  }
+  const recFiltered = Object.assign({}, rec);
+  Object.keys(recFiltered).forEach(key => {
+    if (bunyanFields.indexOf(key) !== -1 || metaFields.indexOf(key) !== -1) {
+      delete recFiltered[key];
+    }
+  });
+  const remainingKeys = Object.keys(recFiltered);
+  if (remainingKeys.length === 0) {
     return '';
   }
-  return `${indent(stringify(rec.config), true)}\n`;
+  return `${remainingKeys
+    .map(key => `${indent(`"${key}": ${stringify(recFiltered[key])}`, true)}`)
+    .join(',\n')}\n`;
 }
 
 function formatRecord(rec) {
diff --git a/lib/workers/branch/index.js b/lib/workers/branch/index.js
index 6dd58848a2..a92d08d515 100644
--- a/lib/workers/branch/index.js
+++ b/lib/workers/branch/index.js
@@ -89,7 +89,7 @@ async function ensureBranch(config) {
           }
         }
       } catch (err) {
-        logger.debug(JSON.stringify(err));
+        logger.debug({ err }, 'Error maintaining lock files');
         throw new Error('Error maintaining lock files');
       }
     } else {
@@ -188,8 +188,7 @@ async function ensureBranch(config) {
     try {
       await api.mergeBranch(branchName, config.automergeType);
     } catch (err) {
-      logger.error(`Failed to automerge branch`);
-      logger.debug(JSON.stringify(err));
+      logger.error({ err }, `Failed to automerge branch`);
       throw err;
     }
   } else {
@@ -239,8 +238,7 @@ async function processBranchUpgrades(branchUpgrades, errors, warnings) {
       }
     }
   } catch (err) {
-    logger.error(`Error updating branch: ${err.message}`);
-    logger.debug(JSON.stringify(err));
+    logger.error({ err }, `Error updating branch: ${err.message}`);
     // Don't throw here - we don't want to stop the other renovations
   }
 }
diff --git a/lib/workers/branch/schedule.js b/lib/workers/branch/schedule.js
index fd00ed749c..2d31641592 100644
--- a/lib/workers/branch/schedule.js
+++ b/lib/workers/branch/schedule.js
@@ -12,7 +12,7 @@ function fixShortHours(input) {
 
 function hasValidSchedule(schedule, logger) {
   if (!Array.isArray(schedule)) {
-    logger.debug(`Invalid schedule: ${JSON.stringify(schedule)}`);
+    logger.debug({ schedule }, `Invalid schedule`);
     return false;
   }
   if (schedule.length === 0) {
@@ -46,7 +46,7 @@ function hasValidSchedule(schedule, logger) {
 }
 
 function isScheduledNow(config) {
-  config.logger.debug(`Checking schedule ${JSON.stringify(config.schedule)}`);
+  config.logger.debug({ schedule: config.schedule }, `Checking schedule`);
   // Massage into array
   const configSchedule =
     typeof config.schedule === 'string' ? [config.schedule] : config.schedule;
@@ -79,7 +79,7 @@ function isScheduledNow(config) {
     return parsedSchedule.schedules.some(schedule => {
       // Check if days are defined
       if (schedule.d) {
-        config.logger.debug(`schedule.d=${JSON.stringify(schedule.d)}`);
+        config.logger.debug({ schedule_d: schedule.d }, `schedule.d`);
         // We need to compare text instead of numbers because
         // 'moment' adjusts day of week for locale while 'later' does not
         // later days run from 1..7
@@ -94,7 +94,7 @@ function isScheduledNow(config) {
           'Saturday',
         ];
         const scheduledDays = schedule.d.map(day => dowMap[day]);
-        config.logger.debug(`scheduledDays=${JSON.stringify(scheduledDays)}`);
+        config.logger.debug({ scheduledDays }, `scheduledDays`);
         if (scheduledDays.indexOf(currentDay) === -1) {
           config.logger.debug(
             `Does not match schedule because ${currentDay} is not in ${scheduledDays}`
diff --git a/lib/workers/dep-type/index.js b/lib/workers/dep-type/index.js
index cf4947dda7..a3d653a8fa 100644
--- a/lib/workers/dep-type/index.js
+++ b/lib/workers/dep-type/index.js
@@ -23,12 +23,14 @@ async function renovateDepType(packageContent, config) {
   if (currentDeps.length === 0) {
     return [];
   }
-  logger.debug(`currentDeps=${JSON.stringify(currentDeps)}`);
+  logger.debug(`currentDeps length is ${currentDeps.length}`);
+  logger.debug({ currentDeps }, `currentDeps`);
   // Filter out ignored dependencies
   const filteredDeps = currentDeps.filter(
     dependency => config.ignoreDeps.indexOf(dependency.depName) === -1
   );
-  logger.debug(`filteredDeps=${JSON.stringify(filteredDeps)}`);
+  logger.debug(`filteredDeps length is ${filteredDeps.length}`);
+  logger.debug({ filteredDeps }, `filteredDeps`);
   // Obtain full config for each dependency
   const depConfigs = filteredDeps.map(dep =>
     module.exports.getDepConfig(config, dep)
diff --git a/lib/workers/global/versions.js b/lib/workers/global/versions.js
index 60ff9b9e21..ed9ffafd5e 100644
--- a/lib/workers/global/versions.js
+++ b/lib/workers/global/versions.js
@@ -24,10 +24,8 @@ function detectVersions(config) {
     });
     versions.yarn = result.stdout.toString().split('\n')[0];
   } catch (err) {
-    config.logger.error('Error detecting versions');
-    config.logger.debug(JSON.stringify(err));
+    config.logger.error({ err }, 'Error detecting versions');
   }
   config.logger.debug({ versions }, 'Detected versions');
-  config.logger.debug(`Versions: ${JSON.stringify(versions)}`);
   return versions;
 }
diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js
index 580b01102f..35099da4fc 100644
--- a/lib/workers/package-file/index.js
+++ b/lib/workers/package-file/index.js
@@ -79,7 +79,10 @@ async function renovatePackageFile(packageFileConfig) {
     if (lockFileMaintenanceConf.enabled) {
       logger.debug('lockFileMaintenance enabled');
       lockFileMaintenanceConf.type = 'lockFileMaintenance';
-      logger.debug(`lock config=${JSON.stringify(lockFileMaintenanceConf)}`);
+      logger.debug(
+        { config: lockFileMaintenanceConf },
+        `lockFileMaintenanceConf`
+      );
       upgrades.push(lockFileMaintenanceConf);
     }
   }
diff --git a/lib/workers/package/index.js b/lib/workers/package/index.js
index 485b4ef431..e90b2bb63a 100644
--- a/lib/workers/package/index.js
+++ b/lib/workers/package/index.js
@@ -36,7 +36,7 @@ async function renovatePackage(config) {
     logger.warn(result.message);
     results = [result];
   }
-  logger.debug(`${config.depName} results: ${JSON.stringify(results)}`);
+  logger.debug({ results }, `${config.depName} lookup results`);
   // Flatten the result on top of config, add repositoryUrl
   return results.map(result => {
     const upg = configParser.mergeChildConfig(config, result);
diff --git a/lib/workers/pr/changelog.js b/lib/workers/pr/changelog.js
index a7ba4b9d66..988666c3fa 100644
--- a/lib/workers/pr/changelog.js
+++ b/lib/workers/pr/changelog.js
@@ -20,9 +20,9 @@ async function getChangeLogJSON(depName, fromVersion, newVersion, logger) {
       err.message.indexOf('did not specify the repository url') !== -1 ||
       err.message.indexOf('Unknown Github Repo') !== -1
     ) {
-      logger.debug(`getChangeLogJSON error: ${JSON.stringify(err)}`);
+      logger.debug({ err }, `getChangeLogJSON error`);
     } else {
-      logger.warn(`getChangeLogJSON error: ${JSON.stringify(err)}`);
+      logger.warn({ err }, `getChangeLogJSON error`);
     }
     return null;
   }
diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js
index df7e9f73b3..484387fba0 100644
--- a/lib/workers/repository/apis.js
+++ b/lib/workers/repository/apis.js
@@ -114,8 +114,10 @@ async function detectPackageFiles(input) {
   const config = Object.assign({}, input);
   config.logger.trace({ config }, 'detectPackageFiles');
   config.packageFiles = await config.api.findFilePaths('package.json');
-  config.logger.debug(`Found ${config.packageFiles.length} package file(s)`);
-  config.logger.trace(JSON.stringify(config.packageFiles));
+  config.logger.debug(
+    { packageFiles: config.packageFiles },
+    `Found ${config.packageFiles.length} package file(s)`
+  );
   if (config.ignoreNodeModules) {
     const skippedPackageFiles = [];
     config.packageFiles = config.packageFiles.filter(packageFile => {
diff --git a/lib/workers/repository/cleanup.js b/lib/workers/repository/cleanup.js
index 8ae52d51f0..4e922f4289 100644
--- a/lib/workers/repository/cleanup.js
+++ b/lib/workers/repository/cleanup.js
@@ -5,10 +5,7 @@ module.exports = {
 async function pruneStaleBranches(config, branchList) {
   const logger = config.logger;
   logger.debug('Removing any stale branches');
-  logger.trace(
-    { config },
-    `pruneStaleBranches:\n${JSON.stringify(branchList)}`
-  );
+  logger.trace({ config, branchList }, `pruneStaleBranches`);
   if (config.platform !== 'github') {
     logger.debug('Platform is not GitHub - returning');
     return;
diff --git a/lib/workers/repository/onboarding.js b/lib/workers/repository/onboarding.js
index 71ceaccb07..9d5f01a7be 100644
--- a/lib/workers/repository/onboarding.js
+++ b/lib/workers/repository/onboarding.js
@@ -16,7 +16,7 @@ module.exports = {
 
 async function determineSemanticCommits(config) {
   const commitMessages = await config.api.getCommitMessages();
-  config.logger.trace(`commitMessages=${JSON.stringify(commitMessages)}`);
+  config.logger.trace({ commitMessages }, 'commitMessages');
   const type = conventionalCommitsDetector(commitMessages);
   if (type === 'unknown') {
     config.logger.debug('No semantic commit type found');
diff --git a/test/logger/__snapshots__/pretty-stdout.spec.js.snap b/test/logger/__snapshots__/pretty-stdout.spec.js.snap
index 6940a43ac5..f00fbfb156 100644
--- a/test/logger/__snapshots__/pretty-stdout.spec.js.snap
+++ b/test/logger/__snapshots__/pretty-stdout.spec.js.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`logger/pretty-stdout getDetails(rec) supports a config 1`] = `
-"       {\\"a\\": \\"b\\", \\"d\\": [\\"e\\", \\"f\\"]}
+"       \\"config\\": {\\"a\\": \\"b\\", \\"d\\": [\\"e\\", \\"f\\"]}
 "
 `;
diff --git a/test/logger/pretty-stdout.spec.js b/test/logger/pretty-stdout.spec.js
index 9d04efcfd0..0a3f7e8b07 100644
--- a/test/logger/pretty-stdout.spec.js
+++ b/test/logger/pretty-stdout.spec.js
@@ -42,15 +42,16 @@ describe('logger/pretty-stdout', () => {
     it('returns empty string if empty rec', () => {
       expect(prettyStdout.getDetails({})).toEqual('');
     });
-    it('returns empty string if no meta fields', () => {
+    it('returns empty string if all are meta fields', () => {
       const rec = {
-        foo: 'bar',
+        branch: 'bar',
+        v: 0,
       };
       expect(prettyStdout.getDetails(rec)).toEqual('');
     });
     it('supports a config', () => {
       const rec = {
-        foo: 'bar',
+        v: 0,
         config: {
           a: 'b',
           d: ['e', 'f'],
diff --git a/test/workers/pr/changelog.spec.js b/test/workers/pr/changelog.spec.js
index b00e50aa9a..18964574c4 100644
--- a/test/workers/pr/changelog.spec.js
+++ b/test/workers/pr/changelog.spec.js
@@ -38,4 +38,19 @@ describe('workers/pr/changelog', () => {
       ).toBe('No changelog available');
     });
   });
+  describe('getChangeLogJSON', () => {
+    it('filters unnecessary warns', async () => {
+      changelog.generate = jest.fn(() => {
+        throw new Error('Unknown Github Repo');
+      });
+      expect(
+        await changelogHelper.getChangeLogJSON(
+          'renovate',
+          '1.0.0',
+          '2.0.0',
+          logger
+        )
+      ).toBe(null);
+    });
+  });
 });
-- 
GitLab