Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/log/test/common/subLogger.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { beforeEach, describe, expect, test } from 'vitest';
7
import { ILogTarget, LogLevel, LogServiceImpl, LogTarget } from '../../common/logService';
8
import { TestLogTarget } from './loggerHelpers';
9
10
describe('SubLogger', () => {
11
let logTarget: TestLogTarget;
12
let logService: LogServiceImpl;
13
14
beforeEach(() => {
15
logTarget = new TestLogTarget();
16
logService = new LogServiceImpl([logTarget]);
17
});
18
19
describe('prefix formatting', () => {
20
test('prefixes messages with single topic', () => {
21
const subLogger = logService.createSubLogger('Feature');
22
subLogger.info('test message');
23
logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');
24
});
25
26
test('prefixes messages with array of topics', () => {
27
const subLogger = logService.createSubLogger(['NES', 'Feature']);
28
subLogger.info('test message');
29
logTarget.assertHasMessage(LogLevel.Info, '[NES][Feature] test message');
30
});
31
32
test('handles empty array of topics', () => {
33
const subLogger = logService.createSubLogger([]);
34
subLogger.info('test message');
35
logTarget.assertHasMessage(LogLevel.Info, ' test message');
36
});
37
});
38
39
describe('nested sub-loggers', () => {
40
test('accumulates prefixes when nesting sub-loggers', () => {
41
const parentLogger = logService.createSubLogger('Parent');
42
const childLogger = parentLogger.createSubLogger('Child');
43
childLogger.info('nested message');
44
logTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] nested message');
45
});
46
47
test('supports multiple levels of nesting', () => {
48
const level1 = logService.createSubLogger('L1');
49
const level2 = level1.createSubLogger('L2');
50
const level3 = level2.createSubLogger('L3');
51
level3.info('deeply nested');
52
logTarget.assertHasMessage(LogLevel.Info, '[L1][L2][L3] deeply nested');
53
});
54
55
test('accumulates array topics when nesting', () => {
56
const parentLogger = logService.createSubLogger(['A', 'B']);
57
const childLogger = parentLogger.createSubLogger(['C', 'D']);
58
childLogger.info('message');
59
logTarget.assertHasMessage(LogLevel.Info, '[A][B][C][D] message');
60
});
61
});
62
63
describe('logging methods', () => {
64
test('trace method prefixes correctly', () => {
65
const subLogger = logService.createSubLogger('Test');
66
subLogger.trace('trace message');
67
logTarget.assertHasMessage(LogLevel.Trace, '[Test] trace message');
68
});
69
70
test('debug method prefixes correctly', () => {
71
const subLogger = logService.createSubLogger('Test');
72
subLogger.debug('debug message');
73
logTarget.assertHasMessage(LogLevel.Debug, '[Test] debug message');
74
});
75
76
test('info method prefixes correctly', () => {
77
const subLogger = logService.createSubLogger('Test');
78
subLogger.info('info message');
79
logTarget.assertHasMessage(LogLevel.Info, '[Test] info message');
80
});
81
82
test('warn method prefixes correctly', () => {
83
const subLogger = logService.createSubLogger('Test');
84
subLogger.warn('warn message');
85
logTarget.assertHasMessage(LogLevel.Warning, '[Test] warn message');
86
});
87
88
test('error method with message prefixes correctly', () => {
89
const subLogger = logService.createSubLogger('Test');
90
const error = new Error('test error');
91
subLogger.error(error, 'error context');
92
// The error method formats as: collectErrorMessages(error) + ': ' + prefixedMessage
93
// The 's' flag makes '.' match newlines
94
expect(logTarget.hasMessageMatching(LogLevel.Error, /test error.*\[Test\] error context/s)).toBe(true);
95
});
96
97
test('error method without message uses prefix only', () => {
98
const subLogger = logService.createSubLogger('Test');
99
const error = new Error('test error');
100
subLogger.error(error);
101
// The error method formats as: collectErrorMessages(error) + ': ' + prefix
102
expect(logTarget.hasMessageMatching(LogLevel.Error, /test error.*\[Test\]/s)).toBe(true);
103
});
104
105
test('error method with string error and message', () => {
106
const subLogger = logService.createSubLogger('Test');
107
subLogger.error('string error', 'error context');
108
// The error method formats as: error + ': ' + prefixedMessage
109
expect(logTarget.hasMessageMatching(LogLevel.Error, /string error.*\[Test\] error context/s)).toBe(true);
110
});
111
});
112
113
describe('show method', () => {
114
test('delegates show to parent logger', () => {
115
let showCalled = false;
116
let preserveFocusValue: boolean | undefined = undefined;
117
118
const mockTarget: TestLogTarget & { show: (preserveFocus?: boolean) => void } = Object.assign(
119
new TestLogTarget(),
120
{
121
show(preserveFocus?: boolean) {
122
showCalled = true;
123
preserveFocusValue = preserveFocus;
124
}
125
}
126
);
127
128
const service = new LogServiceImpl([mockTarget]);
129
const subLogger = service.createSubLogger('Test');
130
subLogger.show(true);
131
132
expect(showCalled).toBe(true);
133
expect(preserveFocusValue).toBe(true);
134
});
135
});
136
137
describe('independence of sub-loggers', () => {
138
test('sibling sub-loggers do not affect each other', () => {
139
const logger1 = logService.createSubLogger('Logger1');
140
const logger2 = logService.createSubLogger('Logger2');
141
142
logger1.info('message from 1');
143
logger2.info('message from 2');
144
145
logTarget.assertHasMessage(LogLevel.Info, '[Logger1] message from 1');
146
logTarget.assertHasMessage(LogLevel.Info, '[Logger2] message from 2');
147
});
148
149
test('parent and child sub-loggers work independently', () => {
150
const parent = logService.createSubLogger('Parent');
151
const child = parent.createSubLogger('Child');
152
153
parent.info('parent message');
154
child.info('child message');
155
156
logTarget.assertHasMessage(LogLevel.Info, '[Parent] parent message');
157
logTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] child message');
158
});
159
});
160
161
describe('withExtraTarget', () => {
162
test('extra target receives log messages', () => {
163
const extraTarget = new TestLogTarget();
164
const logger = logService
165
.createSubLogger('Feature')
166
.withExtraTarget(extraTarget);
167
168
logger.info('test message');
169
170
// Primary target receives prefixed message
171
logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');
172
// Extra target also receives prefixed message
173
extraTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');
174
});
175
176
test('withExtraTarget returns new immutable logger', () => {
177
const extraTarget = new TestLogTarget();
178
const original = logService.createSubLogger('Feature');
179
const withExtra = original.withExtraTarget(extraTarget);
180
181
original.info('original only');
182
withExtra.info('with extra');
183
184
// Extra target only receives from withExtra logger
185
expect(extraTarget.hasMessage(LogLevel.Info, '[Feature] original only')).toBe(false);
186
extraTarget.assertHasMessage(LogLevel.Info, '[Feature] with extra');
187
});
188
189
test('sub-loggers inherit extra targets', () => {
190
const extraTarget = new TestLogTarget();
191
const parent = logService
192
.createSubLogger('Parent')
193
.withExtraTarget(extraTarget);
194
const child = parent.createSubLogger('Child');
195
196
child.info('child message');
197
198
extraTarget.assertHasMessage(LogLevel.Info, '[Parent][Child] child message');
199
});
200
201
test('can chain multiple extra targets', () => {
202
const extra1 = new TestLogTarget();
203
const extra2 = new TestLogTarget();
204
const logger = logService
205
.createSubLogger('Feature')
206
.withExtraTarget(extra1)
207
.withExtraTarget(extra2);
208
209
logger.info('test');
210
211
extra1.assertHasMessage(LogLevel.Info, '[Feature] test');
212
extra2.assertHasMessage(LogLevel.Info, '[Feature] test');
213
});
214
215
test('extra target errors do not affect primary logging', () => {
216
const throwingTarget: ILogTarget = {
217
logIt: () => { throw new Error('Target error'); }
218
};
219
const logger = logService
220
.createSubLogger('Feature')
221
.withExtraTarget(throwingTarget);
222
223
// Should not throw
224
logger.info('test message');
225
226
// Primary target still receives the message
227
logTarget.assertHasMessage(LogLevel.Info, '[Feature] test message');
228
});
229
230
test('extra targets receive all log levels', () => {
231
const extraTarget = new TestLogTarget();
232
const logger = logService
233
.createSubLogger('Test')
234
.withExtraTarget(extraTarget);
235
236
logger.trace('trace msg');
237
logger.debug('debug msg');
238
logger.info('info msg');
239
logger.warn('warn msg');
240
logger.error('error msg');
241
242
extraTarget.assertHasMessage(LogLevel.Trace, '[Test] trace msg');
243
extraTarget.assertHasMessage(LogLevel.Debug, '[Test] debug msg');
244
extraTarget.assertHasMessage(LogLevel.Info, '[Test] info msg');
245
extraTarget.assertHasMessage(LogLevel.Warning, '[Test] warn msg');
246
extraTarget.assertHasMessage(LogLevel.Error, '[Test] error msg');
247
});
248
249
test('show() calls show on extra targets', () => {
250
let showCalled = false;
251
let preserveFocusValue: boolean | undefined;
252
const extraTarget: ILogTarget = {
253
logIt: () => { },
254
show: (preserveFocus) => {
255
showCalled = true;
256
preserveFocusValue = preserveFocus;
257
}
258
};
259
const logger = logService
260
.createSubLogger('Test')
261
.withExtraTarget(extraTarget);
262
263
logger.show(true);
264
265
expect(showCalled).toBe(true);
266
expect(preserveFocusValue).toBe(true);
267
});
268
269
test('withExtraTarget works on LogServiceImpl directly', () => {
270
const extraTarget = new TestLogTarget();
271
const logger = logService.withExtraTarget(extraTarget);
272
273
logger.info('direct message');
274
275
logTarget.assertHasMessage(LogLevel.Info, 'direct message');
276
extraTarget.assertHasMessage(LogLevel.Info, 'direct message');
277
});
278
});
279
280
describe('LogTarget.fromCallback', () => {
281
test('creates valid ILogTarget from callback', () => {
282
const messages: Array<{ level: LogLevel; msg: string }> = [];
283
const logger = logService
284
.createSubLogger('Feature')
285
.withExtraTarget(LogTarget.fromCallback((level, msg) => {
286
messages.push({ level, msg });
287
}));
288
289
logger.warn('warning message');
290
291
expect(messages).toHaveLength(1);
292
expect(messages[0]).toEqual({
293
level: LogLevel.Warning,
294
msg: '[Feature] warning message'
295
});
296
});
297
298
test('callback receives correct log levels', () => {
299
const levels: LogLevel[] = [];
300
const logger = logService
301
.withExtraTarget(LogTarget.fromCallback((level) => {
302
levels.push(level);
303
}));
304
305
logger.trace('');
306
logger.debug('');
307
logger.info('');
308
logger.warn('');
309
logger.error('');
310
311
expect(levels).toEqual([
312
LogLevel.Trace,
313
LogLevel.Debug,
314
LogLevel.Info,
315
LogLevel.Warning,
316
LogLevel.Error
317
]);
318
});
319
});
320
});
321
322