refactor: avoid recompiling the regex on every test

Signed-off-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
This commit is contained in:
Thomas Cardonne
2025-09-01 15:20:48 +02:00
parent f4045adea0
commit 7b5129c490
3 changed files with 86 additions and 75 deletions
@@ -31,7 +31,7 @@ import {
import { MESSAGE } from 'triple-beam';
import { escapeRegExp } from '../../lib/escapeRegExp';
import { winstonLevels, WinstonLoggerLevelOverride } from './types';
import { isLogMatching } from './utils';
import { createLogMatcher } from './utils';
/**
* @public
@@ -188,12 +188,15 @@ export class WinstonLogger implements RootLoggerService {
format: Format;
setOverrides: (overrides: WinstonLoggerLevelOverride[]) => void;
} {
const overrides: WinstonLoggerLevelOverride[] = [];
const overrides: {
predicate: (log: TransformableInfo) => boolean;
level: string;
}[] = [];
return {
format: format(log => {
for (const override of overrides) {
if (isLogMatching(log, override.matchers)) {
if (override.predicate(log)) {
// Discard the log if the log level is below the override
// eg, if the override level is 'warn' (1) and the log is 'debug' (5)
if (winstonLevels[log.level] > winstonLevels[override.level]) {
@@ -213,8 +216,12 @@ export class WinstonLogger implements RootLoggerService {
return log;
})(),
setOverrides: newOverrides => {
// Replace the content while preserving the reference
overrides.splice(0, overrides.length, ...newOverrides);
const newOverridesPredicates = newOverrides.map(o => ({
predicate: createLogMatcher(o.matchers),
level: o.level,
}));
// Replace the content while preserving the reference to support live config updates
overrides.splice(0, overrides.length, ...newOverridesPredicates);
},
};
}
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isLogMatching } from './utils';
import { createLogMatcher } from './utils';
describe('isLogMatching', () => {
describe('createLogMatcher', () => {
const log = {
level: 'info',
message: 'This is a simple log from the catalog plugin',
@@ -25,68 +25,56 @@ describe('isLogMatching', () => {
};
it('should match with a simple matcher', () => {
expect(
isLogMatching(log, {
plugin: 'catalog',
}),
).toEqual(true);
const matcher = createLogMatcher({ plugin: 'catalog' });
expect(matcher(log)).toEqual(true);
});
it('should not match with a simple matcher', () => {
expect(
isLogMatching(log, {
plugin: 'search',
}),
).toEqual(false);
const matcher = createLogMatcher({ plugin: 'search' });
expect(matcher(log)).toEqual(false);
});
it('should match with an AND matcher', () => {
expect(
isLogMatching(log, {
plugin: 'catalog',
action: 'read',
}),
).toEqual(true);
const matcher = createLogMatcher({
plugin: 'catalog',
action: 'read',
});
expect(matcher(log)).toEqual(true);
});
it('should not match log with an AND matcher', () => {
expect(
isLogMatching(log, {
plugin: 'catalog',
action: 'write',
}),
).toEqual(false);
const matcher = createLogMatcher({
plugin: 'catalog',
action: 'write',
});
expect(matcher(log)).toEqual(false);
});
it('should match with an OR matcher', () => {
expect(
isLogMatching(log, {
plugin: ['auth', 'catalog'],
}),
).toEqual(true);
const matcher = createLogMatcher({
plugin: ['auth', 'catalog'],
});
expect(matcher(log)).toEqual(true);
});
it('should not match log with an OR matcher', () => {
expect(
isLogMatching(log, {
plugin: ['auth', 'search'],
}),
).toEqual(false);
const matcher = createLogMatcher({
plugin: ['auth', 'search'],
});
expect(matcher(log)).toEqual(false);
});
it('should match log with a regex matcher', () => {
expect(
isLogMatching(log, {
message: '/This is a simple log/',
}),
).toEqual(true);
const matcher = createLogMatcher({
message: '/This is a simple log/',
});
expect(matcher(log)).toEqual(true);
});
it('should not match log with a regex matcher', () => {
expect(
isLogMatching(log, {
message: '/^simple log/',
}),
).toEqual(false);
const matcher = createLogMatcher({
message: '/^simple log/',
});
expect(matcher(log)).toEqual(false);
});
});
@@ -17,62 +17,78 @@
import { TransformableInfo } from 'logform';
import { WinstonLoggerLevelOverrideMatchers } from './types';
/** Parse a slash-delimited regex like `/pattern/flags` into a RegExp, or null if not a regex-string */
const parseRegex = (s: string): RegExp | null => {
if (!s.startsWith('/')) return null;
const lastSlash = s.lastIndexOf('/');
if (lastSlash <= 0) return null;
const pattern = s.slice(1, lastSlash);
const flags = s.slice(lastSlash + 1);
try {
return new RegExp(pattern, flags);
} catch {
return null; // fall back to treating it as a plain string
}
};
/**
* Determines if a given log field matches a specified matcher.
* Create a predicate function that determines whether a log field matches a given matcher.
*
* The matcher can be:
* - A string (exact match or regex pattern delimited by slashes, e.g. `/pattern/`)
* - A non-string value (compared by strict equality)
* - An array of matchers (returns true if any matcher matches)
*
* @param logField - The log field value to test for a match.
* @param matcher - The matcher or array of matchers to compare against the log field.
* @returns `true` if the log field matches the matcher, otherwise `false`.
* @returns A function that takes a log field and returns `true` if it matches the matcher, otherwise `false`.
*/
const isLogFieldMatching = (
logField: unknown,
const createLogFieldMatcher = (
matcher: WinstonLoggerLevelOverrideMatchers[0],
): boolean => {
): ((logField: unknown) => boolean) => {
// Array of matchers: create predicates for each element and OR them together
if (Array.isArray(matcher)) {
return matcher.some(m => isLogFieldMatching(logField, m));
const fns = matcher.map(m => createLogFieldMatcher(m));
return (logField: unknown) => fns.some(fn => fn(logField));
}
// Non-string matcher: strict equality
if (typeof matcher !== 'string') {
return logField === matcher;
return (logField: unknown) => logField === matcher;
}
if (
matcher.startsWith('/') &&
matcher.endsWith('/') &&
typeof logField === 'string'
) {
const regex = new RegExp(matcher.slice(1, -1));
return regex.test(logField);
// String matcher: maybe a slash-delimited regex (/pattern/flags)
const regex = parseRegex(matcher);
if (regex) {
return (logField: unknown) =>
typeof logField === 'string' && regex.test(logField);
}
return logField === matcher;
// Plain string matcher: strict equality
return (logField: unknown) => logField === matcher;
};
/**
* Determines whether a log entry matches all specified override matchers.
* Create a predicate function that determines whether a log entry matches
* all specified override matchers.
*
* Iterates over each key-matcher pair in the provided `matchers` object,
* retrieves the corresponding field from the `log` object, and checks if
* the field matches the matcher using `isLogFieldMatching`. Returns `true`
* only if all matchers are satisfied.
*
* @param log - The log entry to be checked, typically containing various log fields.
* @param matchers - An object where each key corresponds to a log field and each value is a matcher to test against that field.
* @returns `true` if the log entry matches all provided matchers, otherwise `false`.
* @returns A function that takes a log entry and returns `true` if it matches all specified matchers, otherwise `false`.
*/
export const isLogMatching = (
log: TransformableInfo,
export const createLogMatcher = (
matchers: WinstonLoggerLevelOverrideMatchers,
): boolean => {
const matched = Object.entries(matchers).every(([key, matcher]) => {
const logField = log[key];
return isLogFieldMatching(logField, matcher);
): ((log: TransformableInfo) => boolean) => {
const logFieldMatchers = Object.entries(matchers).map(([key, m]) => {
const fn = createLogFieldMatcher(m);
return [key, fn] as const;
});
return matched;
return (log: TransformableInfo) =>
logFieldMatchers.every(([key, fn]) => fn(log[key]));
};