From b80f4d0d85ed25d80e216134f16b0e8d841a9966 Mon Sep 17 00:00:00 2001 From: Sanjeev Sharma Date: Fri, 24 Jul 2026 09:46:59 +0530 Subject: [PATCH 1/3] feat(redact): add excludePaths to exempt fields from pattern scanning Lets callers whitelist fields (e.g. vendor reference ids) whose values coincidentally match aggressive value patterns like phone numbers. --- src/types/index.ts | 14 ++++ src/utils/__tests__/redact.utils.test.ts | 53 ++++++++++++++ src/utils/redact.utils.ts | 88 +++++++++++++----------- 3 files changed, 116 insertions(+), 39 deletions(-) diff --git a/src/types/index.ts b/src/types/index.ts index 177379a..deea9a5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -89,6 +89,20 @@ export interface RedactConfig { * @example `['req.headers.authorization', '*.password', 'user.creditCard']` */ paths?: string[]; + /** + * Dot-notation field paths exempt from `patterns` / `autoDetect` value-pattern + * scanning. Use this for internal identifiers whose *value* happens to look + * like PII to a pattern (e.g. a vendor reference id embedding an epoch + * timestamp digit-run that the aggressive phone-number pattern matches). + * + * Checked before pattern-based redaction (step 3): a matched field's value + * is passed through untouched, skipping the pattern scan entirely. This does + * NOT override `paths` — if a field matches both `paths` and `excludePaths`, + * `paths` wins (censored), since `paths` is checked first. + * + * @example `['**.vendorRefId', '**.traceId', '**.interactionId']` + */ + excludePaths?: string[]; /** * Regex patterns applied to string values — replaces matches with the censor string. * @example `[/Bearer\s+\S+/gi, /sk-[a-z0-9]{32,}/gi]` diff --git a/src/utils/__tests__/redact.utils.test.ts b/src/utils/__tests__/redact.utils.test.ts index 7157e4c..8bb304e 100644 --- a/src/utils/__tests__/redact.utils.test.ts +++ b/src/utils/__tests__/redact.utils.test.ts @@ -203,6 +203,59 @@ describe('redactObject — pattern-based redaction', () => { }); }); +// ── redactObject — excludePaths ─────────────────────────────────────────────── + +describe('redactObject — excludePaths', () => { + const PHONE_PATTERN = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g; + + it('skips pattern scanning for a field matched by excludePaths', () => { + const obj = { vendorRefId: 'N101-D1-1784797594.4885' }; + const result = redactObject(obj, { + patterns: [PHONE_PATTERN], + excludePaths: ['**.vendorRefId'], + }); + expect(result.vendorRefId).toBe('N101-D1-1784797594.4885'); + }); + + it('still applies pattern scanning to fields not matched by excludePaths', () => { + const obj = { vendorRefId: 'N101-D1-1784797594.4885', callerNumber: '4155552671' }; + const result = redactObject(obj, { + patterns: [PHONE_PATTERN], + excludePaths: ['**.vendorRefId'], + }); + expect(result.vendorRefId).toBe('N101-D1-1784797594.4885'); + expect(result.callerNumber).toBe(CENSOR); + }); + + it('excludePaths does not exempt a field also matched by paths', () => { + const obj = { vendorRefId: 'N101-D1-1784797594.4885' }; + const result = redactObject(obj, { + paths: ['**.vendorRefId'], + excludePaths: ['**.vendorRefId'], + patterns: [PHONE_PATTERN], + }); + expect(result.vendorRefId).toBe(CENSOR); + }); + + it('excludePaths applies inside nested objects', () => { + const obj = { call: { vendorRefId: 'N101-D1-1784797594.4885' } }; + const result = redactObject(obj, { + patterns: [PHONE_PATTERN], + excludePaths: ['**.vendorRefId'], + }); + expect((result.call as Record).vendorRefId).toBe('N101-D1-1784797594.4885'); + }); + + it('excludePaths applies to string array items', () => { + const obj = { vendorRefIds: ['N101-D1-1784797594.4885', 'N101-D1-1784798000.4886'] }; + const result = redactObject(obj, { + patterns: [PHONE_PATTERN], + excludePaths: ['**.vendorRefIds'], + }); + expect(result.vendorRefIds).toEqual(['N101-D1-1784797594.4885', 'N101-D1-1784798000.4886']); + }); +}); + // ── redactObject — array handling ───────────────────────────────────────────── describe('redactObject — array handling', () => { diff --git a/src/utils/redact.utils.ts b/src/utils/redact.utils.ts index 20651e6..238a34a 100644 --- a/src/utils/redact.utils.ts +++ b/src/utils/redact.utils.ts @@ -213,6 +213,54 @@ function matchesPath(fullPath: string, pattern: string): boolean { return re.test(fullPath); } +function applyPatterns(value: string, patterns: readonly RegExp[], censor: string): string { + let redacted = value; + for (const pattern of patterns) { + redacted = redacted.replace(pattern, censor); + } + return redacted; +} + +function redactArrayItem( + item: unknown, + config: RedactConfig, + fullPath: string, + skipPatternScan: boolean +): unknown { + if (isPlainObject(item)) { + return redactObject(item as Record, config, fullPath); + } + const { patterns = [], censor = DEFAULT_CENSOR } = config; + if (!skipPatternScan && typeof item === 'string' && patterns.length > 0) { + return applyPatterns(item, patterns, censor); + } + return item; +} + +function redactField(value: unknown, config: RedactConfig, fullPath: string): unknown { + const { paths = [], excludePaths = [], patterns = [], censor = DEFAULT_CENSOR } = config; + + if (paths.some((p) => matchesPath(fullPath, p))) { + return censor; + } + + if (isPlainObject(value)) { + return redactObject(value as Record, config, fullPath); + } + + const skipPatternScan = excludePaths.some((p) => matchesPath(fullPath, p)); + + if (!skipPatternScan && typeof value === 'string' && patterns.length > 0) { + return applyPatterns(value, patterns, censor); + } + + if (Array.isArray(value)) { + return value.map((item) => redactArrayItem(item, config, fullPath, skipPatternScan)); + } + + return value; +} + /** * Deep-clone and redact an object according to the given RedactConfig. * Non-objects are returned as-is (with pattern replacement on strings). @@ -224,7 +272,6 @@ export function redactObject( config: RedactConfig, _currentPath = '' ): Record { - const { paths = [], patterns = [], censor = DEFAULT_CENSOR } = config; const result: Record = {}; for (const [key, value] of Object.entries(obj)) { @@ -235,44 +282,7 @@ export function redactObject( } const fullPath = _currentPath ? `${_currentPath}.${key}` : key; - // ── 1. Path-based redaction ────────────────────────────────────────────── - if (paths.length > 0 && paths.some((p) => matchesPath(fullPath, p))) { - result[key] = censor; - continue; - } - - // ── 2. Recurse into plain objects ──────────────────────────────────────── - if (isPlainObject(value)) { - result[key] = redactObject(value as Record, config, fullPath); - continue; - } - - // ── 3. Pattern-based redaction on string values ────────────────────────── - if (typeof value === 'string' && patterns.length > 0) { - let redacted = value; - for (const pattern of patterns) { - redacted = redacted.replace(pattern, censor); - } - result[key] = redacted; - continue; - } - - // ── 4. Recurse into arrays ─────────────────────────────────────────────── - if (Array.isArray(value)) { - result[key] = value.map((item) => { - if (isPlainObject(item)) { - return redactObject(item as Record, config, fullPath); - } - if (typeof item === 'string' && patterns.length > 0) { - return patterns.reduce((s, p) => s.replace(p, censor), item); - } - return item; - }); - continue; - } - - // ── 5. Pass-through ────────────────────────────────────────────────────── - result[key] = value; + result[key] = redactField(value, config, fullPath); } return result; From b974e3c946d27bd6d483682fcc1406265c070e34 Mon Sep 17 00:00:00 2001 From: Sanjeev Sharma Date: Fri, 24 Jul 2026 11:12:19 +0530 Subject: [PATCH 2/3] chore(lint): satisfy eslint-plugin-sonarjs 4.2.0 rule set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs `npm install` against a gitignored lockfile, so it resolved the newest matching sonarjs and surfaced rules absent from the pinned 4.0.3. Pin the plugin to exact 4.2.0 and resolve the new recommended rules: - disable sonarjs/no-redundant-optional — it is purely syntactic and conflicts with exactOptionalPropertyTypes, where `?: T | undefined` is not redundant (dropping it breaks tsc across transports/builder) - rename stale slow-regex disable comments to super-linear-regex - parameterize duplicate test blocks, replace generic assertions with toBeInstanceOf/toHaveLength, add assertions to next()/done() tests, and drop a duplicate test title --- eslint.config.mjs | 5 +++ package.json | 2 +- src/__tests__/plugin.comprehensive.test.ts | 31 ++++++++++++------- src/cli/__tests__/explore.test.ts | 29 +++++------------ .../async-context.comprehensive.test.ts | 19 +++++------- .../__tests__/logitron-logger.v11.test.ts | 26 +++++----------- .../__tests__/exception.comprehensive.test.ts | 6 ++-- src/search/engines/nlp-search-engine.ts | 2 +- src/transports/file.transport.ts | 2 +- .../error.utils.comprehensive.test.ts | 6 ++-- .../sampling.utils.comprehensive.test.ts | 27 +++------------- .../trace.utils.comprehensive.test.ts | 5 ++- 12 files changed, 63 insertions(+), 97 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 31f9e86..12e6268 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -53,6 +53,11 @@ export default [ // search utility functions (highest observed: transport.manager = 49). 'sonarjs/cognitive-complexity': ['warn', 55], 'sonarjs/todo-tag': 'off', + // With `exactOptionalPropertyTypes: true` (tsconfig.json), `?: T` and + // `?: T | undefined` are NOT equivalent — the latter permits explicitly + // assigning `undefined`, which callers throughout this codebase rely on. + // This rule is purely syntactic and doesn't account for that tsconfig flag. + 'sonarjs/no-redundant-optional': 'off', }, }, diff --git a/package.json b/package.json index b87718f..ccef0b9 100644 --- a/package.json +++ b/package.json @@ -267,7 +267,7 @@ "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-sonarjs": "^4.0.2", + "eslint-plugin-sonarjs": "4.2.0", "eslint-plugin-unicorn": "^63.0.0", "express": "^5.2.1", "globals": "^17.4.0", diff --git a/src/__tests__/plugin.comprehensive.test.ts b/src/__tests__/plugin.comprehensive.test.ts index 3ccd584..7002e25 100644 --- a/src/__tests__/plugin.comprehensive.test.ts +++ b/src/__tests__/plugin.comprehensive.test.ts @@ -76,18 +76,25 @@ describe('PluginRegistry', () => { it('swallows errors thrown by async onInit (fire and forget)', async () => { // Synchronous throws from onInit ARE propagated (it's called synchronously). // Only async onInit errors are swallowed. Verify async path. - const rejected = false; - registry.register({ - name: 'bad-async-init', - onInit: async () => { - await Promise.resolve(); - throw new Error('async init failed'); - }, - }); - await Promise.resolve(); - await Promise.resolve(); - // No unhandled rejection — error was swallowed - expect(rejected).toBe(false); + let rejected = false; + const onUnhandledRejection = () => { + rejected = true; + }; + process.on('unhandledRejection', onUnhandledRejection); + try { + registry.register({ + name: 'bad-async-init', + onInit: async () => { + await Promise.resolve(); + throw new Error('async init failed'); + }, + }); + await Promise.resolve(); + await Promise.resolve(); + expect(rejected).toBe(false); + } finally { + process.off('unhandledRejection', onUnhandledRejection); + } }); it('can register multiple distinct plugins', () => { diff --git a/src/cli/__tests__/explore.test.ts b/src/cli/__tests__/explore.test.ts index 8f77b85..80b60ff 100644 --- a/src/cli/__tests__/explore.test.ts +++ b/src/cli/__tests__/explore.test.ts @@ -49,34 +49,19 @@ describe('formatTime', () => { test('truncates and pads non-ISO strings', () => { const result = formatTime('not-a-date'); - expect(result.length).toBe(12); + expect(result).toHaveLength(12); }); }); // ── syntaxColorJson ─────────────────────────────────────────────────────────── describe('syntaxColorJson', () => { - test('strips ANSI and leaves structure intact', () => { - const line = ' "level": "error",'; - const result = syntaxColorJson(line); - expect(strip(result)).toBe(line); - }); - - test('preserves numeric values in output', () => { - const line = ' "duration": 142,'; - const result = syntaxColorJson(line); - // The plain-text content must be unchanged after stripping ANSI - expect(strip(result)).toBe(line); - }); - - test('preserves boolean values in output', () => { - const line = ' "active": true'; - const result = syntaxColorJson(line); - expect(strip(result)).toBe(line); - }); - - test('handles null values', () => { - const line = ' "value": null'; + test.each([ + ['string value', ' "level": "error",'], + ['numeric value', ' "duration": 142,'], + ['boolean value', ' "active": true'], + ['null value', ' "value": null'], + ])('preserves plain-text content after stripping ANSI: %s', (_label, line) => { const result = syntaxColorJson(line); expect(strip(result)).toBe(line); }); diff --git a/src/context/__tests__/async-context.comprehensive.test.ts b/src/context/__tests__/async-context.comprehensive.test.ts index 148c905..5b55294 100644 --- a/src/context/__tests__/async-context.comprehensive.test.ts +++ b/src/context/__tests__/async-context.comprehensive.test.ts @@ -197,7 +197,10 @@ describe('createExpressContextMiddleware', () => { it('calls next()', (done) => { const mw = createExpressContextMiddleware(); const req: Record = { headers: {} }; - mw(req, {}, done); + mw(req, {}, (...args: unknown[]) => { + expect(args).toEqual([]); + done(); + }); }); it('sets a traceId in the context', (done) => { @@ -237,15 +240,6 @@ describe('createExpressContextMiddleware', () => { }); }); - it('uses custom traceIdHeader when provided', (done) => { - const mw = createExpressContextMiddleware({ traceIdHeader: 'x-my-trace-id' }); - const req: Record = { headers: { 'x-my-trace-id': 'my-trace' } }; - mw(req, {}, () => { - expect(LogixiaContext.get()?.traceId).toBe('my-trace'); - done(); - }); - }); - it('calls the enrich function and merges its result', (done) => { const mw = createExpressContextMiddleware({ enrich: (req) => ({ userId: req['userId'] as string }), @@ -273,7 +267,10 @@ describe('createExpressContextMiddleware', () => { describe('createFastifyContextHook', () => { it('calls done()', (done) => { const hook = createFastifyContextHook(); - hook({ headers: {} }, {}, done); + hook({ headers: {} }, {}, (...args: unknown[]) => { + expect(args).toEqual([]); + done(); + }); }); it('sets a traceId in the context', (done) => { diff --git a/src/core/__tests__/logitron-logger.v11.test.ts b/src/core/__tests__/logitron-logger.v11.test.ts index c015b0a..0840aba 100644 --- a/src/core/__tests__/logitron-logger.v11.test.ts +++ b/src/core/__tests__/logitron-logger.v11.test.ts @@ -103,28 +103,16 @@ afterEach(() => { // ── Feature 5: Adaptive log level ──────────────────────────────────────────── describe('Feature 5 — Adaptive log level', () => { - it('uses DEBUG level when NODE_ENV=development and no config level set', () => { - process.env['NODE_ENV'] = 'development'; + test.each([ + ['debug', 'development'], + ['warn', 'test'], + ['info', 'production'], + ])('uses %s level when NODE_ENV=%s and no config level set', (expectedLevel, nodeEnv) => { + process.env['NODE_ENV'] = nodeEnv; delete process.env['LOGIXIA_LEVEL']; const logger = new LogixiaLogger({ ...BASE_CONFIG, levelOptions: undefined }); - expect(logger.getLevel()).toBe('debug'); - }); - - it('uses WARN level when NODE_ENV=test', () => { - process.env['NODE_ENV'] = 'test'; - delete process.env['LOGIXIA_LEVEL']; - - const logger = new LogixiaLogger({ ...BASE_CONFIG, levelOptions: undefined }); - expect(logger.getLevel()).toBe('warn'); - }); - - it('uses INFO level when NODE_ENV=production', () => { - process.env['NODE_ENV'] = 'production'; - delete process.env['LOGIXIA_LEVEL']; - - const logger = new LogixiaLogger({ ...BASE_CONFIG, levelOptions: undefined }); - expect(logger.getLevel()).toBe('info'); + expect(logger.getLevel()).toBe(expectedLevel); }); it('uses INFO level when CI=true and no NODE_ENV', () => { diff --git a/src/exceptions/__tests__/exception.comprehensive.test.ts b/src/exceptions/__tests__/exception.comprehensive.test.ts index 149a8dd..3700873 100644 --- a/src/exceptions/__tests__/exception.comprehensive.test.ts +++ b/src/exceptions/__tests__/exception.comprehensive.test.ts @@ -26,12 +26,12 @@ describe('LogixiaException — construction', () => { it('is an instance of Error', () => { const ex = new LogixiaException(baseOptions); - expect(ex instanceof Error).toBe(true); + expect(ex).toBeInstanceOf(Error); }); it('is an instance of LogixiaException', () => { const ex = new LogixiaException(baseOptions); - expect(ex instanceof LogixiaException).toBe(true); + expect(ex).toBeInstanceOf(LogixiaException); }); it('has name "LogixiaException"', () => { @@ -107,7 +107,7 @@ describe('LogixiaException — optional fields', () => { details, }); expect(ex.details).toEqual(details); - expect(ex.details!.length).toBe(2); + expect(ex.details!).toHaveLength(2); expect(ex.details![0].field).toBe('email'); expect(ex.details![1].code).toBe('required'); }); diff --git a/src/search/engines/nlp-search-engine.ts b/src/search/engines/nlp-search-engine.ts index a861cc2..20850c7 100644 --- a/src/search/engines/nlp-search-engine.ts +++ b/src/search/engines/nlp-search-engine.ts @@ -153,7 +153,7 @@ export class NLPSearchEngine extends BasicSearchEngine { ['user_id', /user\s+(?:id\s+)?['"]?(\w+)['"]?/i], ['trace_id', /trace\s+(?:id\s+)?['"]?([\w-]+)['"]?/i], ['time', /(?:last|past|previous)\s+(\d+)\s+(second|minute|hour|day|week)s?/i], - // eslint-disable-next-line sonarjs/slow-regex -- bounded pattern, DoS risk is acceptable for log search + // eslint-disable-next-line sonarjs/super-linear-regex -- bounded pattern, DoS risk is acceptable for log search ['error_type', /(\w+Error|\w+Exception)/i], ]); } diff --git a/src/transports/file.transport.ts b/src/transports/file.transport.ts index 89f75af..00787f4 100644 --- a/src/transports/file.transport.ts +++ b/src/transports/file.transport.ts @@ -199,7 +199,7 @@ export class FileTransport implements ITransport, IBatchTransport { } private parseInterval(interval: string): number { - // eslint-disable-next-line sonarjs/slow-regex -- simple bounded pattern for interval parsing + // eslint-disable-next-line sonarjs/super-linear-regex -- simple bounded pattern for interval parsing const match = new RegExp(/(\d+)([hdwmy])/i).exec(interval); if (!match) return 24 * 60 * 60 * 1000; // Default 1 day diff --git a/src/utils/__tests__/error.utils.comprehensive.test.ts b/src/utils/__tests__/error.utils.comprehensive.test.ts index 78e0c41..6050d75 100644 --- a/src/utils/__tests__/error.utils.comprehensive.test.ts +++ b/src/utils/__tests__/error.utils.comprehensive.test.ts @@ -266,19 +266,19 @@ describe('normalizeError', () => { it('wraps a string into an Error', () => { const result = normalizeError('something bad'); - expect(result instanceof Error).toBe(true); + expect(result).toBeInstanceOf(Error); expect(result.message).toBe('something bad'); }); it('wraps an object with a message field', () => { const result = normalizeError({ message: 'obj error' }); - expect(result instanceof Error).toBe(true); + expect(result).toBeInstanceOf(Error); expect(result.message).toBe('obj error'); }); it('wraps an object without message as "Unknown error"', () => { const result = normalizeError({ code: 'ERR' }); - expect(result instanceof Error).toBe(true); + expect(result).toBeInstanceOf(Error); expect(result.message).toBe('Unknown error'); }); diff --git a/src/utils/__tests__/sampling.utils.comprehensive.test.ts b/src/utils/__tests__/sampling.utils.comprehensive.test.ts index 7b8d923..9703e5a 100644 --- a/src/utils/__tests__/sampling.utils.comprehensive.test.ts +++ b/src/utils/__tests__/sampling.utils.comprehensive.test.ts @@ -50,15 +50,9 @@ describe('Sampler', () => { s.destroy(); }); - it('still emits error entries (safety override)', () => { + test.each(['error', 'fatal'] as const)('still emits %s entries (safety override)', (level) => { const s = new Sampler({ rate: 0.0 }); - expect(s.shouldEmit('error')).toBe(true); - s.destroy(); - }); - - it('still emits fatal entries (safety override)', () => { - const s = new Sampler({ rate: 0.0 }); - expect(s.shouldEmit('fatal')).toBe(true); + expect(s.shouldEmit(level)).toBe(true); s.destroy(); }); }); @@ -66,24 +60,11 @@ describe('Sampler', () => { // ── Error/fatal safety bypass ────────────────────────────────────────────── describe('error / fatal safety bypass', () => { - it('error always passes with any global rate', () => { - const s = new Sampler({ rate: 0.001 }); - let allPass = true; - for (let i = 0; i < 50; i++) { - if (!s.shouldEmit('error')) { - allPass = false; - break; - } - } - expect(allPass).toBe(true); - s.destroy(); - }); - - it('fatal always passes with any global rate', () => { + test.each(['error', 'fatal'] as const)('%s always passes with any global rate', (level) => { const s = new Sampler({ rate: 0.001 }); let allPass = true; for (let i = 0; i < 50; i++) { - if (!s.shouldEmit('fatal')) { + if (!s.shouldEmit(level)) { allPass = false; break; } diff --git a/src/utils/__tests__/trace.utils.comprehensive.test.ts b/src/utils/__tests__/trace.utils.comprehensive.test.ts index 858a969..b211c54 100644 --- a/src/utils/__tests__/trace.utils.comprehensive.test.ts +++ b/src/utils/__tests__/trace.utils.comprehensive.test.ts @@ -283,7 +283,10 @@ describe('createTraceMiddleware', () => { const middleware = createTraceMiddleware({ enabled: true }); const req = { headers: {} }; const res = { setHeader: jest.fn() }; - middleware(req, res, done); + middleware(req, res, (...args: unknown[]) => { + expect(args).toEqual([]); + done(); + }); }); it('sets X-Trace-Id response header', (done) => { From 390c365f0b8dca77a3edb1b7c55e9f7d400af8b3 Mon Sep 17 00:00:00 2001 From: webcoderspeed <78198767+webcoderspeed@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:55:54 +0000 Subject: [PATCH 3/3] style: auto-fix formatting and lint [skip ci] --- README.md | 8 ++------ src/core/logitron-nestjs.service.ts | 18 ++++-------------- src/correlation.ts | 6 ++---- src/middleware/http-logger.ts | 2 +- src/search/types/search.types.ts | 15 ++------------- todo/05-performance.md | 4 +--- 6 files changed, 12 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 96a79c7..f79f07e 100644 --- a/README.md +++ b/README.md @@ -1814,9 +1814,7 @@ const logger = createLogger({ timeout: 10_000, // wait up to 10 s; force-exits after signals: ['SIGTERM', 'SIGINT', 'SIGHUP'], }, - transports: { - /* ... */ - }, + transports: {/* ... */}, }); ``` @@ -1908,9 +1906,7 @@ import { usePlugin } from 'logixia'; usePlugin(myPlugin); // All loggers created from this point forward will run myPlugin. -const logger = createLogger({ - /* ... */ -}); +const logger = createLogger({/* ... */}); ``` ### Per-logger plugins diff --git a/src/core/logitron-nestjs.service.ts b/src/core/logitron-nestjs.service.ts index 9bcff02..2c6e1cf 100644 --- a/src/core/logitron-nestjs.service.ts +++ b/src/core/logitron-nestjs.service.ts @@ -32,14 +32,7 @@ import { LogLevel } from '../types'; * duplicate / conflicting signatures. */ type _ServiceBuiltinLevels = - | 'error' - | 'warn' - | 'info' - | 'debug' - | 'verbose' - | 'trace' - | 'log' - | 'logLevel'; + 'error' | 'warn' | 'info' | 'debug' | 'verbose' | 'trace' | 'log' | 'logLevel'; /** * Mapped type that adds one method per *custom* level (i.e. every key in TLevels @@ -112,12 +105,9 @@ type _ExtractCustomLevelNames = T extends { levelOptions?: { levels?: infer L */ export type LogixiaServiceWith> = LogixiaLoggerService & { - readonly [K in T extends string - ? Exclude - : _ExtractCustomLevelNames]: ( - message: string, - data?: Record - ) => Promise; + readonly [ + K in T extends string ? Exclude : _ExtractCustomLevelNames + ]: (message: string, data?: Record) => Promise; }; import { safeToString } from '../utils/coerce.utils'; import { internalError } from '../utils/internal-log'; diff --git a/src/correlation.ts b/src/correlation.ts index 1d12931..0cd60c2 100644 --- a/src/correlation.ts +++ b/src/correlation.ts @@ -346,8 +346,7 @@ export function childFromRequest | undefined)?.['remoteAddress'] as - | string - | undefined); + string | undefined); return logger.child({ correlationId, @@ -393,8 +392,7 @@ export function extractMessageCorrelationId(message: Record): s // SQS: message.MessageAttributes const sqsAttrs = message['MessageAttributes'] as - | Record - | undefined; + Record | undefined; if (sqsAttrs) { return ( sqsAttrs['x-correlation-id']?.StringValue ?? diff --git a/src/middleware/http-logger.ts b/src/middleware/http-logger.ts index 9e01805..dabc1bb 100644 --- a/src/middleware/http-logger.ts +++ b/src/middleware/http-logger.ts @@ -22,7 +22,7 @@ * ``` */ -/* eslint-disable sonarjs/void-use -- intentional fire-and-forget in sync middleware callbacks */ + import type { IBaseLogger } from '../types'; // ── Shared types ───────────────────────────────────────────────────────────── diff --git a/src/search/types/search.types.ts b/src/search/types/search.types.ts index 7e9540a..eda1091 100644 --- a/src/search/types/search.types.ts +++ b/src/search/types/search.types.ts @@ -179,12 +179,7 @@ export interface SearchSuggestion { * Suggestion type */ export type SuggestionType = - | 'field' - | 'value' - | 'operator' - | 'query_history' - | 'pattern' - | 'filter'; + 'field' | 'value' | 'operator' | 'query_history' | 'pattern' | 'filter'; /** * Search statistics @@ -284,13 +279,7 @@ export interface QueryEntity { * Entity type */ export type EntityType = - | 'level' - | 'service' - | 'user_id' - | 'trace_id' - | 'time' - | 'error_type' - | 'field_value'; + 'level' | 'service' | 'user_id' | 'trace_id' | 'time' | 'error_type' | 'field_value'; /** * Search preset for saving common searches diff --git a/todo/05-performance.md b/todo/05-performance.md index 26853ca..d12f589 100644 --- a/todo/05-performance.md +++ b/todo/05-performance.md @@ -21,9 +21,7 @@ const engine = new BasicSearchEngine({ maxIndexSize: 500_000 }); // Seed 100k entries for (let i = 0; i < 100_000; i++) { - engine.indexLog({ - /* ... */ - }); + engine.indexLog({/* ... */}); } console.time('search-100k');