From a91da03d463f1432c0012f27b1b63d1aecca1add Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Mon, 27 Nov 2023 15:45:52 +0200 Subject: [PATCH] feat: useLogger API --- src/decorator/Bunyamin.ts | 32 ++++++++-- src/decorator/StackTraceError.ts | 14 +++++ src/index.ts | 3 +- src/is-debug/index.ts | 2 + src/realm.ts | 30 ++++++--- .../options/TraceEventStreamOptions.ts | 2 +- .../options/normalizeOptions.ts | 2 +- src/thread-groups/ThreadGroups.test.ts | 62 +++++++++++++++++++ src/thread-groups/ThreadGroups.ts | 40 ++++++++++++ src/thread-groups/index.ts | 1 + 10 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/decorator/StackTraceError.ts create mode 100644 src/thread-groups/ThreadGroups.test.ts create mode 100644 src/thread-groups/ThreadGroups.ts create mode 100644 src/thread-groups/index.ts diff --git a/src/decorator/Bunyamin.ts b/src/decorator/Bunyamin.ts index e3323bc..e0b68ab 100644 --- a/src/decorator/Bunyamin.ts +++ b/src/decorator/Bunyamin.ts @@ -1,7 +1,8 @@ import { deflateCategories, mergeCategories } from './categories'; -import { isActionable, isError, isObject, isPromiseLike } from '../utils'; +import { isSelfDebug } from '../is-debug'; import type { ThreadGroupConfig } from '../streams'; import type { ThreadID } from '../types'; +import { isActionable, isError, isObject, isPromiseLike } from '../utils'; import type { BunyaminLogMethod, BunyaminConfig, @@ -10,6 +11,7 @@ import type { BunyanLogLevel, } from './types'; import { MessageStack } from './message-stack'; +import { StackTraceError } from './StackTraceError'; export class Bunyamin { public readonly fatal = this.#setupLogMethod('fatal'); @@ -33,7 +35,7 @@ export class Bunyamin { this.#fields = undefined; this.#shared = { ...config, - threadGroups: config.threadGroups ?? [], + loggerPriority: 0, messageStack: new MessageStack({ noBeginMessage: config.noBeginMessage, }), @@ -44,8 +46,9 @@ export class Bunyamin { } } + /** @deprecated */ get threadGroups(): ThreadGroupConfig[] { - return this.#shared.threadGroups!; + return []; } get logger(): Logger { @@ -53,6 +56,10 @@ export class Bunyamin { } set logger(logger: Logger) { + this.useLogger(logger); + } + + useLogger(logger: Logger, priority = 0): void { if (this.#shared.immutable) { throw new Error('Cannot change a logger of an immutable instance'); } @@ -61,7 +68,23 @@ export class Bunyamin { throw new Error('Cannot change a logger of a child instance'); } - this.#shared.logger = logger; + const { stack } = isSelfDebug() ? new StackTraceError() : StackTraceError.empty(); + const currentPriority = this.#shared.loggerPriority; + if (priority >= currentPriority) { + this.#shared.loggerPriority = priority; + this.#shared.logger = logger; + stack && + this.#shared.logger.trace( + { cat: 'bunyamin' }, + `bunyamin logger changed (${priority} >= ${currentPriority}), caller was:\n${stack}`, + ); + } else { + stack && + this.#shared.logger.trace( + { cat: 'bunyamin' }, + `bunyamin logger not changed (${priority} < ${currentPriority}), caller was:\n${stack}`, + ); + } } child(overrides?: UserFields): Bunyamin { @@ -226,5 +249,6 @@ type ResolvedFields = UserFields & { }; type SharedBunyaminConfig = BunyaminConfig & { + loggerPriority: number; messageStack: MessageStack; }; diff --git a/src/decorator/StackTraceError.ts b/src/decorator/StackTraceError.ts new file mode 100644 index 0000000..373c065 --- /dev/null +++ b/src/decorator/StackTraceError.ts @@ -0,0 +1,14 @@ +export class StackTraceError extends Error { + constructor() { + super('Providing stack trace below:'); + // eslint-disable-next-line unicorn/custom-error-definition + this.name = 'StackTrace'; + } + + static empty() { + return { + message: '', + stack: '', + }; + } +} diff --git a/src/index.ts b/src/index.ts index 0db26e9..ff9503c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,10 @@ export * from './noopLogger'; export * from './traceEventStream'; export * from './uniteTraceEvents'; export * from './wrapLogger'; -export * from './is-debug'; +export { isDebug } from './is-debug'; export const bunyamin = realm.bunyamin; export const nobunyamin = realm.nobunyamin; +export const threadGroups = realm.threadGroups; export default bunyamin; diff --git a/src/is-debug/index.ts b/src/is-debug/index.ts index 58e57e3..80030b1 100644 --- a/src/is-debug/index.ts +++ b/src/is-debug/index.ts @@ -1,3 +1,5 @@ import { createIsDebug } from './createIsDebug'; export const isDebug = createIsDebug(process.env.DEBUG || ''); + +export const isSelfDebug = () => isDebug('bunyamin'); diff --git a/src/realm.ts b/src/realm.ts index c38d2cb..c6e7c51 100644 --- a/src/realm.ts +++ b/src/realm.ts @@ -1,26 +1,38 @@ -import type { BunyanLikeLogger } from './decorator'; import { Bunyamin } from './decorator'; import { noopLogger } from './noopLogger'; +import { isSelfDebug } from './is-debug'; +import { ThreadGroups } from './thread-groups'; type Realm = { - bunyamin: Bunyamin; - nobunyamin: Bunyamin; + bunyamin: Bunyamin; + nobunyamin: Bunyamin; + threadGroups: ThreadGroups; }; function create() { - const threadGroups: any[] = []; - const bunyamin = new Bunyamin({ logger: noopLogger(), threadGroups }); - const nobunyamin = new Bunyamin({ + const selfDebug = isSelfDebug(); + const bunyamin = new Bunyamin({ logger: noopLogger() }); + const nobunyamin = new Bunyamin({ logger: noopLogger(), - threadGroups, immutable: true, }); + const threadGroups = new ThreadGroups(bunyamin); - return { bunyamin, nobunyamin }; + if (selfDebug) { + bunyamin.trace({ cat: 'bunyamin' }, 'bunyamin global instance created'); + } + + return { bunyamin, nobunyamin, threadGroups }; } function getCached(): Realm | undefined { - return (globalThis as any).__BUNYAMIN__; + const result = (globalThis as any).__BUNYAMIN__; + + if (isSelfDebug() && result) { + result.bunyamin.trace({ cat: 'bunyamin' }, 'bunyamin global instance retrieved from cache'); + } + + return result; } function setCached(realm: Realm) { diff --git a/src/streams/bunyan-trace-event/options/TraceEventStreamOptions.ts b/src/streams/bunyan-trace-event/options/TraceEventStreamOptions.ts index 9fd3268..7b605aa 100644 --- a/src/streams/bunyan-trace-event/options/TraceEventStreamOptions.ts +++ b/src/streams/bunyan-trace-event/options/TraceEventStreamOptions.ts @@ -11,7 +11,7 @@ export type TraceEventStreamOptions = { * running in parallel, and you want to group them together in the trace * viewer under the same thread name and keep the thread IDs together. */ - threadGroups?: (string | ThreadGroupConfig)[]; + threadGroups?: Iterable; /** * Default maximum number of concurrent threads in each thread group. * Must be a positive integer. diff --git a/src/streams/bunyan-trace-event/options/normalizeOptions.ts b/src/streams/bunyan-trace-event/options/normalizeOptions.ts index e42dd7e..50f1765 100644 --- a/src/streams/bunyan-trace-event/options/normalizeOptions.ts +++ b/src/streams/bunyan-trace-event/options/normalizeOptions.ts @@ -8,7 +8,7 @@ export function normalizeOptions( options.defaultThreadName = options.defaultThreadName ?? 'Main Thread'; options.maxConcurrency = options.maxConcurrency ?? 100; options.strict = options.strict ?? false; - options.threadGroups = (options.threadGroups ?? []).map((threadGroup, index) => + options.threadGroups = [...(options.threadGroups ?? [])].map((threadGroup, index) => typeof threadGroup === 'string' ? { id: threadGroup, diff --git a/src/thread-groups/ThreadGroups.test.ts b/src/thread-groups/ThreadGroups.test.ts new file mode 100644 index 0000000..79b7a84 --- /dev/null +++ b/src/thread-groups/ThreadGroups.test.ts @@ -0,0 +1,62 @@ +import { beforeEach, describe, expect, jest, it } from '@jest/globals'; +import type { ThreadGroups } from './ThreadGroups'; +import { wrapLogger } from '../wrapLogger'; +import type { Bunyamin } from '../decorator'; + +describe('ThreadGroups', () => { + let ThreadGroups: new (logger: Bunyamin) => ThreadGroups; + let threadGroups: ThreadGroups; + let isDebug: jest.Mocked; + let logger: Bunyamin; + + beforeEach(() => { + jest.mock('../is-debug'); + isDebug = jest.requireMock('../is-debug'); + ThreadGroups = jest.requireActual('./ThreadGroups').ThreadGroups; + logger = wrapLogger({ + trace: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + }); + }); + + describe('in regular mode', () => { + beforeEach(() => { + isDebug.isSelfDebug.mockReturnValue(false); + threadGroups = new ThreadGroups(logger); + }); + + it('should be empty by default', () => { + expect([...threadGroups]).toEqual([]); + }); + + it('should add a thread group', () => { + const group = { id: 'foo', displayName: 'Foo' }; + threadGroups.add(group); + expect([...threadGroups]).toEqual([group]); + }); + + it('should not call logger.trace', () => { + expect(logger.logger.trace).not.toHaveBeenCalled(); + }); + }); + + describe('in debug mode', () => { + beforeEach(() => { + isDebug.isSelfDebug.mockReturnValue(true); + threadGroups = new ThreadGroups(logger); + }); + + it('should call logger.trace upon addition', () => { + const group = { id: 'foo', displayName: 'Foo' }; + threadGroups.add(group); + expect(logger.logger.trace).toHaveBeenCalledWith( + { cat: 'bunyamin' }, + expect.stringContaining(__filename), + ); + }); + }); +}); diff --git a/src/thread-groups/ThreadGroups.ts b/src/thread-groups/ThreadGroups.ts new file mode 100644 index 0000000..595525c --- /dev/null +++ b/src/thread-groups/ThreadGroups.ts @@ -0,0 +1,40 @@ +import type { Bunyamin } from '../decorator'; +import type { ThreadGroupConfig } from '../streams'; +import { isSelfDebug } from '../is-debug'; +import { StackTraceError } from '../decorator/StackTraceError'; + +export class ThreadGroups { + readonly #bunyamin: Bunyamin; + readonly #debugMode = isSelfDebug(); + readonly #groups = new Map(); + + constructor(bunyamin: Bunyamin) { + this.#bunyamin = bunyamin; + this.#groups = new Map(); + } + + add(group: ThreadGroupConfig) { + if (this.#debugMode) { + if (this.#groups.has(group.id)) { + this.#logAddition(group, 'overwritten'); + } else { + this.#logAddition(group, 'added'); + } + } + + this.#groups.set(group.id, group); + return this; + } + + [Symbol.iterator]() { + return this.#groups.values(); + } + + #logAddition(group: ThreadGroupConfig, action: string) { + const { stack } = new StackTraceError(); + this.#bunyamin.trace( + { cat: 'bunyamin' }, + `thread group ${action}: ${group.id} (${group.displayName})\n\n${stack}`, + ); + } +} diff --git a/src/thread-groups/index.ts b/src/thread-groups/index.ts new file mode 100644 index 0000000..04ab38b --- /dev/null +++ b/src/thread-groups/index.ts @@ -0,0 +1 @@ +export * from './ThreadGroups';