mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-27 07:50:47 +00:00
e456197add
When certain meta fields are logged, we pull them out of the meta fields
and into the log line, such as:
DEBUG: Branch is not pending, removing pending upgrades (branch="renovate/express-4.x")
However, this also would affect non-string values such as:
DEBUG: Git operations statistics (branch="[object Object]")
This only affects the "pretty" logs, but would mean no `branch` metadata
appears.
Instead, we can only include them when they're a string.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
138 lines
3.8 KiB
TypeScript
138 lines
3.8 KiB
TypeScript
// Code originally derived from https://github.com/hadfieldn/node-bunyan-prettystream but since heavily edited
|
|
// Neither fork nor original repo appear to be maintained
|
|
|
|
import { Writable } from 'node:stream';
|
|
import * as util from 'node:util';
|
|
import { isNonEmptyObject, isPlainObject, isString } from '@sindresorhus/is';
|
|
import stringify from 'json-stringify-pretty-compact';
|
|
import { regEx } from '../util/regex.ts';
|
|
import type { BunyanRecord } from './types.ts';
|
|
|
|
const bunyanFields = [
|
|
'name',
|
|
'hostname',
|
|
'pid',
|
|
'level',
|
|
'v',
|
|
'time',
|
|
'msg',
|
|
'start_time',
|
|
];
|
|
/**
|
|
* If a log contains any of these meta fields in the meta, promote them up to the log line, rather than rendering them with other meta fields.
|
|
*
|
|
* This only applies when these fields have a string value, otherwise they are treated as a regular meta field.
|
|
*/
|
|
const metaFields = [
|
|
'repository',
|
|
'baseBranch',
|
|
'packageFile',
|
|
'depType',
|
|
'dependency',
|
|
'branch',
|
|
];
|
|
|
|
const levels: Record<number, string> = {
|
|
10: 'TRACE',
|
|
20: 'DEBUG',
|
|
30: ' INFO',
|
|
40: ' WARN',
|
|
50: 'ERROR',
|
|
60: 'FATAL',
|
|
};
|
|
|
|
const colorizedLevels: Record<number, string> = {
|
|
10: util.styleText('gray', 'TRACE'),
|
|
20: util.styleText('blue', 'DEBUG'),
|
|
30: util.styleText('green', ' INFO'),
|
|
40: util.styleText('magenta', ' WARN'),
|
|
50: util.styleText('red', 'ERROR'),
|
|
60: util.styleText('bgRed', 'FATAL'),
|
|
};
|
|
|
|
export function indent(str: string, leading = false): string {
|
|
const prefix = leading ? ' ' : '';
|
|
return prefix + str.split(regEx(/\r?\n/)).join('\n ');
|
|
}
|
|
|
|
export function getMeta(rec: BunyanRecord, colorize = true): string {
|
|
if (!rec) {
|
|
return '';
|
|
}
|
|
let res = rec.module ? ` [${rec.module}]` : ``;
|
|
const filteredMeta = metaFields.filter((elem) => isString(rec[elem]));
|
|
if (!filteredMeta.length) {
|
|
return res;
|
|
}
|
|
const metaStr = filteredMeta
|
|
.map((field) => `${field}=${String(rec[field])}`)
|
|
.join(', ');
|
|
res = ` (${metaStr})${res}`;
|
|
return colorize ? util.styleText('gray', res) : res;
|
|
}
|
|
|
|
export function getDetails(rec: BunyanRecord): string {
|
|
if (!rec) {
|
|
return '';
|
|
}
|
|
const recFiltered = { ...rec };
|
|
delete recFiltered.module;
|
|
Object.keys(recFiltered).forEach((key) => {
|
|
if (
|
|
key === 'logContext' ||
|
|
bunyanFields.includes(key) ||
|
|
(metaFields.includes(key) && isString(recFiltered[key]))
|
|
) {
|
|
delete recFiltered[key];
|
|
}
|
|
});
|
|
const remainingKeys = Object.keys(recFiltered);
|
|
if (remainingKeys.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
// Handle err.stack specially for readable multi-line output
|
|
const err = recFiltered.err;
|
|
if (isPlainObject(err) && isString(err.stack)) {
|
|
const { stack, ...errRest } = err;
|
|
recFiltered.err = isNonEmptyObject(errRest) ? errRest : undefined;
|
|
const parts: string[] = [];
|
|
for (const key of remainingKeys) {
|
|
if (key === 'err' && recFiltered.err === undefined) {
|
|
continue;
|
|
}
|
|
parts.push(indent(`"${key}": ${stringify(recFiltered[key])}`, true));
|
|
}
|
|
const jsonPart = parts.join(',\n');
|
|
const stackPart = indent(stack, true);
|
|
return jsonPart ? `${jsonPart}\n${stackPart}\n` : `${stackPart}\n`;
|
|
}
|
|
|
|
return `${remainingKeys
|
|
.map((key) => `${indent(`"${key}": ${stringify(recFiltered[key])}`, true)}`)
|
|
.join(',\n')}\n`;
|
|
}
|
|
|
|
export function formatRecord(rec: BunyanRecord, colorize = true): string {
|
|
const level = colorize ? colorizedLevels[rec.level] : levels[rec.level];
|
|
const msg = `${indent(rec.msg)}`;
|
|
const meta = getMeta(rec, colorize);
|
|
const details = getDetails(rec);
|
|
return util.format('%s: %s%s\n%s', level, msg, meta, details);
|
|
}
|
|
|
|
export class PrettyStdoutStream extends Writable {
|
|
constructor() {
|
|
super({ objectMode: true });
|
|
}
|
|
|
|
override _write(
|
|
data: BunyanRecord,
|
|
_encoding: string,
|
|
callback: (error?: Error | null) => void,
|
|
): void {
|
|
process.stdout.write(formatRecord(data));
|
|
callback();
|
|
}
|
|
}
|