import {expect, jest, test} from '@jest/globals'; import { Logger, RootLogger, ROOT } from '../../src/logger'; import { Handler } from '../../src/handler'; import { LogRecord } from '../../src/log-record'; import { Formatter } from '../../src/formatter'; import { Manager } from '../../src/manager'; import { DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET, } from '../../src/log-level'; import { MyError } from '../../src/helper/error'; describe('Logger', () => { describe('constructor', () => { test('sets scope and default level', () => { const logger = new Logger('test'); expect(logger.scope).toBe('test'); expect(logger.level).toBe(NOTSET); }); test('accepts explicit level', () => { const logger = new Logger('test', WARNING); expect(logger.level).toBe(WARNING); }); }); describe('setLevel', () => { test('changes the level', () => { const logger = new Logger('test'); logger.setLevel(ERROR); expect(logger.level).toBe(ERROR); }); }); describe('getEffectiveLevel', () => { test('returns own level when set', () => { const logger = new Logger('test', WARNING); expect(logger.getEffectiveLevel()).toBe(WARNING); }); test('returns NOTSET when no level set and no parent', () => { const logger = new Logger('test'); expect(logger.getEffectiveLevel()).toBe(NOTSET); }); }); describe('isEnabledFor', () => { test('returns true when level meets effective level', () => { const logger = new Logger('test', DEBUG); expect(logger.isEnabledFor(WARNING)).toBe(true); expect(logger.isEnabledFor(DEBUG)).toBe(true); }); test('returns false when level is below effective level', () => { const logger = new Logger('test', WARNING); expect(logger.isEnabledFor(DEBUG)).toBe(false); expect(logger.isEnabledFor(INFO)).toBe(false); }); test('caches results for repeated calls', () => { const logger = new Logger('test', WARNING); const first = logger.isEnabledFor(DEBUG); const second = logger.isEnabledFor(DEBUG); expect(first).toBe(second); }); test('respects manager disable level', () => { const root = new RootLogger(WARNING); const manager = new Manager(root); const logger = manager.getLogger('test'); logger.setLevel(DEBUG); logger.manager = manager; manager.disable = ERROR; logger.clear(); expect(logger.isEnabledFor(DEBUG)).toBe(false); expect(logger.isEnabledFor(WARNING)).toBe(false); expect(logger.isEnabledFor(CRITICAL)).toBe(true); }); }); describe('manager property', () => { test('starts as null', () => { const logger = new Logger('test'); expect(logger.manager).toBeNull(); }); test('can be assigned once', () => { const logger = new Logger('test'); const root = new RootLogger(WARNING); const manager = new Manager(root); logger.manager = manager; expect(logger.manager).toBe(manager); }); test('throws on second assignment', () => { const logger = new Logger('test'); const root = new RootLogger(WARNING); const manager = new Manager(root); logger.manager = manager; expect(() => { logger.manager = manager }).toThrow('logger can only be assigned to manager once'); }); }); describe('addHandler / removeHandler', () => { test('adds and removes handlers', () => { const logger = new Logger('test'); const handler = new Handler(DEBUG); expect(logger.handlers.length).toBe(0); logger.addHandler(handler); expect(logger.handlers.length).toBe(1); logger.removeHandler(handler); }); test('does not add duplicate handlers', () => { const logger = new Logger('test'); const handler = new Handler(DEBUG); logger.addHandler(handler); logger.addHandler(handler); expect(logger.handlers.length).toBe(1); }); }); describe('level methods', () => { let logger: Logger; let emitted: LogRecord[]; class TestHandler extends Handler { emit(record: LogRecord) { emitted.push(record) } } beforeEach(() => { logger = new Logger('test', DEBUG); emitted = []; logger.addHandler(new TestHandler(DEBUG)); }); test('debug emits at DEBUG level', () => { logger.debug('msg'); expect(emitted.length).toBe(1); expect(emitted[0].levelno).toBe(DEBUG); }); test('info emits at INFO level', () => { logger.info('msg'); expect(emitted.length).toBe(1); expect(emitted[0].levelno).toBe(INFO); }); test('warning emits at WARNING level', () => { logger.warning('msg'); expect(emitted.length).toBe(1); expect(emitted[0].levelno).toBe(WARNING); }); test('error emits at ERROR level', () => { logger.error('msg'); expect(emitted.length).toBe(1); expect(emitted[0].levelno).toBe(ERROR); }); test('critical emits at CRITICAL level', () => { logger.critical('msg'); expect(emitted.length).toBe(1); expect(emitted[0].levelno).toBe(CRITICAL); }); test('level methods respect effective level', () => { logger.setLevel(ERROR); logger.clear(); logger.debug('no'); logger.info('no'); logger.warning('no'); expect(emitted.length).toBe(0); logger.error('yes'); logger.critical('yes'); expect(emitted.length).toBe(2); }); }); describe('makeRecord', () => { test('throws KeyError when extra overwrites existing LogRecord key', () => { const logger = new Logger('test', DEBUG); expect(() => { logger.debug('test', { excInfo: null, extra: { scope: 'override' }, stackInfo: false, stackLevel: 1, }); }).toThrow('attempt to overwrite scope in LogRecord'); }); }); }); describe('RootLogger', () => { test('has scope "root"', () => { const root = new RootLogger(WARNING); expect(root.scope).toBe('root'); }); test('accepts a level', () => { const root = new RootLogger(ERROR); expect(root.level).toBe(ERROR); }); });