Skip to content

Commit

Permalink
feat(logger): Log level remapping (#26951)
Browse files Browse the repository at this point in the history
Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
3 people committed Feb 16, 2024
1 parent 3b3f4c1 commit c30a4b0
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 7 deletions.
56 changes: 56 additions & 0 deletions docs/usage/configuration-options.md
Expand Up @@ -1796,6 +1796,21 @@ You can configure a different maximum value in seconds using `maxRetryAfter`:
}
```

### newLogLevel

For log level remapping, `newLogLevel` will set for the particular log message:

```json
{
"logLevelRemap": [
{
"matchMessage": "/Error executing maven wrapper update command/",
"newLogLevel": "warn"
}
]
}
```

### dnsCache

Enable got [dnsCache](https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#dnsCache) support.
Expand Down Expand Up @@ -2155,6 +2170,27 @@ To enable `lockFileMaintenance` add this to your configuration:
To reduce "noise" in the repository, Renovate performs `lockFileMaintenance` `"before 4am on monday"`, i.e. to achieve once-per-week semantics.
Depending on its running schedule, Renovate may run a few times within that time window - even possibly updating the lock file more than once - but it hopefully leaves enough time for tests to run and automerge to apply, if configured.

## logLevelRemap

This option allows you to remap log levels for specific messages.

Be careful with remapping `warn` or `error` messages to lower log levels, as it may hide important information.

```json
{
"logLevelRemap": [
{
"matchMessage": "/^pip-compile:/",
"newLogLevel": "info"
},
{
"matchMessage": "Package lookup error",
"newLogLevel": "warn"
}
]
}
```

## major

Add to this object if you wish to define rules that apply only to major updates.
Expand Down Expand Up @@ -2588,6 +2624,26 @@ Use this field to restrict rules to a particular package manager. e.g.

For the full list of available managers, see the [Supported Managers](modules/manager/index.md#supported-managers) documentation.

### matchMessage

For log level remapping, use this field to match against the particular log messages.
You can match based on any of the following:

- an exact match string (e.g. `This is the string`)
- a minimatch pattern (e.g. `This*`)
- a regex pattern (e.g. `/^This/`)

```json
{
"logLevelRemap": [
{
"matchMessage": "Manager explicitly enabled*",
"newLogLevel": "warn"
}
]
}
```

### matchDatasources

Use this field to restrict rules to a particular datasource. e.g.
Expand Down
26 changes: 26 additions & 0 deletions lib/config/options/index.ts
Expand Up @@ -2818,6 +2818,32 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
{
name: 'logLevelRemap',
description: 'Remap log levels to different levels.',
type: 'array',
subType: 'object',
stage: 'repository',
cli: false,
env: false,
},
{
name: 'matchMessage',
description: 'Regex/minimatch expression to match against log message.',
type: 'string',
parents: ['logLevelRemap'],
cli: false,
env: false,
},
{
name: 'newLogLevel',
description: 'New log level to use if matchMessage matches.',
type: 'string',
allowedValues: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'],
parents: ['logLevelRemap'],
cli: false,
env: false,
},
];

export function getOptions(): RenovateOptions[] {
Expand Down
6 changes: 5 additions & 1 deletion lib/config/types.ts
@@ -1,5 +1,6 @@
import type { LogLevel } from 'bunyan';
import type { PlatformId } from '../constants';
import type { LogLevelRemap } from '../logger/types';
import type { CustomManager } from '../modules/manager/custom/types';
import type { HostRule } from '../types';
import type { GitNoVerifyOption } from '../util/git/types';
Expand Down Expand Up @@ -275,6 +276,8 @@ export interface RenovateConfig
customizeDashboard?: Record<string, string>;

statusCheckNames?: Record<StatusCheckKey, string | null>;

logLevelRemap?: LogLevelRemap[];
}

const CustomDatasourceFormats = ['json', 'plain', 'yaml', 'html'] as const;
Expand Down Expand Up @@ -374,7 +377,8 @@ export type AllowedParents =
| 'customDatasources'
| 'hostRules'
| 'postUpgradeTasks'
| 'packageRules';
| 'packageRules'
| 'logLevelRemap';
export interface RenovateOptionBase {
/**
* If true, the option can only be configured by people with access to the Renovate instance.
Expand Down
30 changes: 25 additions & 5 deletions lib/logger/index.ts
Expand Up @@ -6,6 +6,7 @@ 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 type { BunyanRecord, Logger } from './types';
import { ProblemStream, validateLogLevel, withSanitizer } from './utils';

Expand Down Expand Up @@ -61,20 +62,39 @@ const bunyanLogger = bunyan.createLogger({
].map(withSanitizer),
});

const logFactory =
(level: bunyan.LogLevelString) =>
(p1: any, p2: any): void => {
const logFactory = (
_level: bunyan.LogLevelString,
): ((p1: unknown, p2: unknown) => void) => {
return (p1: any, p2: any): void => {
let level = _level;
if (p2) {
// meta and msg provided
bunyanLogger[level]({ logContext, ...curMeta, ...p1 }, p2);
const msg = p2;
const meta: Record<string, unknown> = { logContext, ...curMeta, ...p1 };
const remappedLevel = getRemappedLevel(msg);
// istanbul ignore if: not testable
if (remappedLevel) {
meta.oldLevel = level;
level = remappedLevel;
}
bunyanLogger[level](meta, msg);
} else if (is.string(p1)) {
// only message provided
bunyanLogger[level]({ logContext, ...curMeta }, p1);
const msg = p1;
const meta: Record<string, unknown> = { logContext, ...curMeta };
const remappedLevel = getRemappedLevel(msg);
// istanbul ignore if: not testable
if (remappedLevel) {
meta.oldLevel = level;
level = remappedLevel;
}
bunyanLogger[level](meta, msg);
} else {
// only meta provided
bunyanLogger[level]({ logContext, ...curMeta, ...p1 });
}
};
};

const loggerLevels: bunyan.LogLevelString[] = [
'trace',
Expand Down
72 changes: 72 additions & 0 deletions lib/logger/remap.spec.ts
@@ -0,0 +1,72 @@
import {
getRemappedLevel,
resetGlobalLogLevelRemaps,
resetRepositoryLogLevelRemaps,
setGlobalLogLevelRemaps,
setRepositoryLogLevelRemaps,
} from './remap';

describe('logger/remap', () => {
afterEach(() => {
resetRepositoryLogLevelRemaps();
resetGlobalLogLevelRemaps();
});

it('returns null if no remaps are set', () => {
setGlobalLogLevelRemaps(undefined);
setRepositoryLogLevelRemaps(undefined);

const res = getRemappedLevel('foo');

expect(res).toBeNull();
});

it('performs global remaps', () => {
setGlobalLogLevelRemaps([{ matchMessage: '*foo*', newLogLevel: 'error' }]);
setRepositoryLogLevelRemaps(undefined);

const res = getRemappedLevel('foo');

expect(res).toBe('error');
});

it('performs repository-level remaps', () => {
setGlobalLogLevelRemaps(undefined);
setRepositoryLogLevelRemaps([
{ matchMessage: '*bar*', newLogLevel: 'error' },
]);

const res = getRemappedLevel('bar');

expect(res).toBe('error');
});

it('prioritizes repository-level remaps over global remaps', () => {
setGlobalLogLevelRemaps([{ matchMessage: '*foo*', newLogLevel: 'error' }]);
setRepositoryLogLevelRemaps([
{ matchMessage: '*bar*', newLogLevel: 'warn' },
]);

const res = getRemappedLevel('foobar');

expect(res).toBe('warn');
});

it('supports regex patterns', () => {
setGlobalLogLevelRemaps([{ matchMessage: '/foo/', newLogLevel: 'error' }]);
setRepositoryLogLevelRemaps(undefined);

const res = getRemappedLevel('foo');

expect(res).toBe('error');
});

it('does not match against invalid regex patterns', () => {
setGlobalLogLevelRemaps([{ matchMessage: '/(/', newLogLevel: 'error' }]);
setRepositoryLogLevelRemaps(undefined);

const res = getRemappedLevel('()');

expect(res).toBeNull();
});
});
68 changes: 68 additions & 0 deletions lib/logger/remap.ts
@@ -0,0 +1,68 @@
import type { LogLevelString } from 'bunyan';
import {
StringMatchPredicate,
makeRegexOrMinimatchPredicate,
} from '../util/string-match';
import type { LogLevelRemap } from './types';

let globalRemaps: LogLevelRemap[] | undefined;
let repositoryRemaps: LogLevelRemap[] | undefined;

let matcherCache = new WeakMap<LogLevelRemap, StringMatchPredicate>();

function match(remap: LogLevelRemap, input: string): boolean {
const { matchMessage: pattern } = remap;
let matchFn = matcherCache.get(remap);
if (!matchFn) {
matchFn = makeRegexOrMinimatchPredicate(pattern) ?? (() => false);
matcherCache.set(remap, matchFn);
}

return matchFn(input);
}

export function getRemappedLevel(msg: string): LogLevelString | null {
if (repositoryRemaps) {
for (const remap of repositoryRemaps) {
if (match(remap, msg)) {
return remap.newLogLevel;
}
}
}

if (globalRemaps) {
for (const remap of globalRemaps) {
if (match(remap, msg)) {
return remap.newLogLevel;
}
}
}

return null;
}

function resetMatcherCache(): void {
matcherCache = new WeakMap();
}

export function setGlobalLogLevelRemaps(
remaps: LogLevelRemap[] | undefined,
): void {
globalRemaps = remaps;
}

export function resetGlobalLogLevelRemaps(): void {
globalRemaps = undefined;
resetMatcherCache();
}

export function setRepositoryLogLevelRemaps(
remaps: LogLevelRemap[] | undefined,
): void {
repositoryRemaps = remaps;
}

export function resetRepositoryLogLevelRemaps(): void {
repositoryRemaps = undefined;
resetMatcherCache();
}
7 changes: 6 additions & 1 deletion lib/logger/types.ts
@@ -1,5 +1,5 @@
import type { Stream } from 'node:stream';
import type { LogLevel } from 'bunyan';
import type { LogLevel, LogLevelString } from 'bunyan';

export interface LogError {
level: LogLevel;
Expand Down Expand Up @@ -40,3 +40,8 @@ export type BunyanStream = (NodeJS.WritableStream | Stream) & {
cb: (err?: Error | null) => void,
) => void;
};

export interface LogLevelRemap {
matchMessage: string;
newLogLevel: LogLevelString;
}
3 changes: 3 additions & 0 deletions lib/workers/global/index.ts
Expand Up @@ -17,6 +17,7 @@ import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages';
import { pkg } from '../../expose.cjs';
import { instrument } from '../../instrumentation';
import { getProblems, logger, setMeta } from '../../logger';
import { setGlobalLogLevelRemaps } from '../../logger/remap';
import * as hostRules from '../../util/host-rules';
import * as queue from '../../util/http/queue';
import * as throttle from '../../util/http/throttle';
Expand Down Expand Up @@ -158,6 +159,8 @@ export async function start(): Promise<number> {

// validate secrets. Will throw and abort if invalid
validateConfigSecrets(config);

setGlobalLogLevelRemaps(config.logLevelRemap);
});

// autodiscover repositories (needs to come after platform initialization)
Expand Down
2 changes: 2 additions & 0 deletions lib/workers/global/initialize.ts
Expand Up @@ -4,6 +4,7 @@ import upath from 'upath';
import { applySecretsToConfig } from '../../config/secrets';
import type { AllConfig, RenovateConfig } from '../../config/types';
import { logger } from '../../logger';
import { resetGlobalLogLevelRemaps } from '../../logger/remap';
import { initPlatform } from '../../modules/platform';
import * as packageCache from '../../util/cache/package';
import { setEmojiConfig } from '../../util/emoji';
Expand Down Expand Up @@ -93,4 +94,5 @@ export async function globalInitialize(

export async function globalFinalize(config: RenovateConfig): Promise<void> {
await packageCache.cleanup(config);
resetGlobalLogLevelRemaps();
}
2 changes: 2 additions & 0 deletions lib/workers/repository/index.ts
Expand Up @@ -10,6 +10,7 @@ import {
import { pkg } from '../../expose.cjs';
import { instrument } from '../../instrumentation';
import { logger, setMeta } from '../../logger';
import { resetRepositoryLogLevelRemaps } from '../../logger/remap';
import { removeDanglingContainers } from '../../util/exec/docker';
import { deleteLocalFile, privateCacheDir } from '../../util/fs';
import { isCloned } from '../../util/git';
Expand Down Expand Up @@ -129,6 +130,7 @@ export async function renovateRepository(
clearDnsCache();
const cloned = isCloned();
logger.info({ cloned, durationMs: splits.total }, 'Repository finished');
resetRepositoryLogLevelRemaps();
return repoResult;
}

Expand Down

0 comments on commit c30a4b0

Please sign in to comment.