Path: blob/main/extensions/copilot/src/platform/log/test/common/subLogger.spec.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { beforeEach, describe, expect, test } from 'vitest';6import { ILogTarget, LogLevel, LogServiceImpl, LogTarget } from '../../common/logService';7import { TestLogTarget } from './loggerHelpers';89describe('SubLogger', () => {10let logTarget: TestLogTarget;11let logService: LogServiceImpl;1213beforeEach(() => {14logTarget = new TestLogTarget();15logService = new LogServiceImpl([logTarget]);16});1718describe('prefix formatting', () => {19test('prefixes messages with single topic', () => {20const subLogger = logService.createSubLogger('Feature');21subLogger.info('test message');22logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');23});2425test('prefixes messages with array of topics', () => {26const subLogger = logService.createSubLogger(['NES', 'Feature']);27subLogger.info('test message');28logTarget.assertHasMessage(LogLevel.Info, '[NES][Feature] test message');29});3031test('handles empty array of topics', () => {32const subLogger = logService.createSubLogger([]);33subLogger.info('test message');34logTarget.assertHasMessage(LogLevel.Info, ' test message');35});36});3738describe('nested sub-loggers', () => {39test('accumulates prefixes when nesting sub-loggers', () => {40const parentLogger = logService.createSubLogger('Parent');41const childLogger = parentLogger.createSubLogger('Child');42childLogger.info('nested message');43logTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] nested message');44});4546test('supports multiple levels of nesting', () => {47const level1 = logService.createSubLogger('L1');48const level2 = level1.createSubLogger('L2');49const level3 = level2.createSubLogger('L3');50level3.info('deeply nested');51logTarget.assertHasMessage(LogLevel.Info, '[L1][L2][L3] deeply nested');52});5354test('accumulates array topics when nesting', () => {55const parentLogger = logService.createSubLogger(['A', 'B']);56const childLogger = parentLogger.createSubLogger(['C', 'D']);57childLogger.info('message');58logTarget.assertHasMessage(LogLevel.Info, '[A][B][C][D] message');59});60});6162describe('logging methods', () => {63test('trace method prefixes correctly', () => {64const subLogger = logService.createSubLogger('Test');65subLogger.trace('trace message');66logTarget.assertHasMessage(LogLevel.Trace, '[Test] trace message');67});6869test('debug method prefixes correctly', () => {70const subLogger = logService.createSubLogger('Test');71subLogger.debug('debug message');72logTarget.assertHasMessage(LogLevel.Debug, '[Test] debug message');73});7475test('info method prefixes correctly', () => {76const subLogger = logService.createSubLogger('Test');77subLogger.info('info message');78logTarget.assertHasMessage(LogLevel.Info, '[Test] info message');79});8081test('warn method prefixes correctly', () => {82const subLogger = logService.createSubLogger('Test');83subLogger.warn('warn message');84logTarget.assertHasMessage(LogLevel.Warning, '[Test] warn message');85});8687test('error method with message prefixes correctly', () => {88const subLogger = logService.createSubLogger('Test');89const error = new Error('test error');90subLogger.error(error, 'error context');91// The error method formats as: collectErrorMessages(error) + ': ' + prefixedMessage92// The 's' flag makes '.' match newlines93expect(logTarget.hasMessageMatching(LogLevel.Error, /test error.*\[Test\] error context/s)).toBe(true);94});9596test('error method without message uses prefix only', () => {97const subLogger = logService.createSubLogger('Test');98const error = new Error('test error');99subLogger.error(error);100// The error method formats as: collectErrorMessages(error) + ': ' + prefix101expect(logTarget.hasMessageMatching(LogLevel.Error, /test error.*\[Test\]/s)).toBe(true);102});103104test('error method with string error and message', () => {105const subLogger = logService.createSubLogger('Test');106subLogger.error('string error', 'error context');107// The error method formats as: error + ': ' + prefixedMessage108expect(logTarget.hasMessageMatching(LogLevel.Error, /string error.*\[Test\] error context/s)).toBe(true);109});110});111112describe('show method', () => {113test('delegates show to parent logger', () => {114let showCalled = false;115let preserveFocusValue: boolean | undefined = undefined;116117const mockTarget: TestLogTarget & { show: (preserveFocus?: boolean) => void } = Object.assign(118new TestLogTarget(),119{120show(preserveFocus?: boolean) {121showCalled = true;122preserveFocusValue = preserveFocus;123}124}125);126127const service = new LogServiceImpl([mockTarget]);128const subLogger = service.createSubLogger('Test');129subLogger.show(true);130131expect(showCalled).toBe(true);132expect(preserveFocusValue).toBe(true);133});134});135136describe('independence of sub-loggers', () => {137test('sibling sub-loggers do not affect each other', () => {138const logger1 = logService.createSubLogger('Logger1');139const logger2 = logService.createSubLogger('Logger2');140141logger1.info('message from 1');142logger2.info('message from 2');143144logTarget.assertHasMessage(LogLevel.Info, '[Logger1] message from 1');145logTarget.assertHasMessage(LogLevel.Info, '[Logger2] message from 2');146});147148test('parent and child sub-loggers work independently', () => {149const parent = logService.createSubLogger('Parent');150const child = parent.createSubLogger('Child');151152parent.info('parent message');153child.info('child message');154155logTarget.assertHasMessage(LogLevel.Info, '[Parent] parent message');156logTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] child message');157});158});159160describe('withExtraTarget', () => {161test('extra target receives log messages', () => {162const extraTarget = new TestLogTarget();163const logger = logService164.createSubLogger('Feature')165.withExtraTarget(extraTarget);166167logger.info('test message');168169// Primary target receives prefixed message170logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');171// Extra target also receives prefixed message172extraTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');173});174175test('withExtraTarget returns new immutable logger', () => {176const extraTarget = new TestLogTarget();177const original = logService.createSubLogger('Feature');178const withExtra = original.withExtraTarget(extraTarget);179180original.info('original only');181withExtra.info('with extra');182183// Extra target only receives from withExtra logger184expect(extraTarget.hasMessage(LogLevel.Info, '[Feature] original only')).toBe(false);185extraTarget.assertHasMessage(LogLevel.Info, '[Feature] with extra');186});187188test('sub-loggers inherit extra targets', () => {189const extraTarget = new TestLogTarget();190const parent = logService191.createSubLogger('Parent')192.withExtraTarget(extraTarget);193const child = parent.createSubLogger('Child');194195child.info('child message');196197extraTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] child message');198});199200test('can chain multiple extra targets', () => {201const extra1 = new TestLogTarget();202const extra2 = new TestLogTarget();203const logger = logService204.createSubLogger('Feature')205.withExtraTarget(extra1)206.withExtraTarget(extra2);207208logger.info('test');209210extra1.assertHasMessage(LogLevel.Info, '[Feature] test');211extra2.assertHasMessage(LogLevel.Info, '[Feature] test');212});213214test('extra target errors do not affect primary logging', () => {215const throwingTarget: ILogTarget = {216logIt: () => { throw new Error('Target error'); }217};218const logger = logService219.createSubLogger('Feature')220.withExtraTarget(throwingTarget);221222// Should not throw223logger.info('test message');224225// Primary target still receives the message226logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');227});228229test('extra targets receive all log levels', () => {230const extraTarget = new TestLogTarget();231const logger = logService232.createSubLogger('Test')233.withExtraTarget(extraTarget);234235logger.trace('trace msg');236logger.debug('debug msg');237logger.info('info msg');238logger.warn('warn msg');239logger.error('error msg');240241extraTarget.assertHasMessage(LogLevel.Trace, '[Test] trace msg');242extraTarget.assertHasMessage(LogLevel.Debug, '[Test] debug msg');243extraTarget.assertHasMessage(LogLevel.Info, '[Test] info msg');244extraTarget.assertHasMessage(LogLevel.Warning, '[Test] warn msg');245extraTarget.assertHasMessage(LogLevel.Error, '[Test] error msg');246});247248test('show() calls show on extra targets', () => {249let showCalled = false;250let preserveFocusValue: boolean | undefined;251const extraTarget: ILogTarget = {252logIt: () => { },253show: (preserveFocus) => {254showCalled = true;255preserveFocusValue = preserveFocus;256}257};258const logger = logService259.createSubLogger('Test')260.withExtraTarget(extraTarget);261262logger.show(true);263264expect(showCalled).toBe(true);265expect(preserveFocusValue).toBe(true);266});267268test('withExtraTarget works on LogServiceImpl directly', () => {269const extraTarget = new TestLogTarget();270const logger = logService.withExtraTarget(extraTarget);271272logger.info('direct message');273274logTarget.assertHasMessage(LogLevel.Info, 'direct message');275extraTarget.assertHasMessage(LogLevel.Info, 'direct message');276});277});278279describe('LogTarget.fromCallback', () => {280test('creates valid ILogTarget from callback', () => {281const messages: Array<{ level: LogLevel; msg: string }> = [];282const logger = logService283.createSubLogger('Feature')284.withExtraTarget(LogTarget.fromCallback((level, msg) => {285messages.push({ level, msg });286}));287288logger.warn('warning message');289290expect(messages).toHaveLength(1);291expect(messages[0]).toEqual({292level: LogLevel.Warning,293msg: '[Feature] warning message'294});295});296297test('callback receives correct log levels', () => {298const levels: LogLevel[] = [];299const logger = logService300.withExtraTarget(LogTarget.fromCallback((level) => {301levels.push(level);302}));303304logger.trace('');305logger.debug('');306logger.info('');307logger.warn('');308logger.error('');309310expect(levels).toEqual([311LogLevel.Trace,312LogLevel.Debug,313LogLevel.Info,314LogLevel.Warning,315LogLevel.Error316]);317});318});319});320321322