Skip to content

Commit 3f17cf4

Browse files
jatgargJatin Garg
andauthored
Add minLogLevel calculation logic to MultiSinkLogger (#17148)
## Description Add minLogLevel calculation logic to MultiSinkLogger. Currently we wrap the loggers we provide to loader etc in multisink and therefore we need to add the logic here so that the events are properly filtered and the childLogger can access the loglevel of the multisinklogger. --------- Co-authored-by: Jatin Garg <[email protected]>
1 parent 1b6c2da commit 3f17cf4

File tree

10 files changed

+182
-49
lines changed

10 files changed

+182
-49
lines changed

api-report/core-interfaces.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,11 +474,11 @@ export interface IUsageError extends IErrorBase {
474474
// @public
475475
export const enum LogLevel {
476476
// (undocumented)
477-
default = 10,
477+
default = 20,
478478
// (undocumented)
479-
error = 20,
479+
error = 30,
480480
// (undocumented)
481-
verbose = 0
481+
verbose = 10
482482
}
483483

484484
// @public

api-report/telemetry-utils.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export function mixinMonitoringContext<L extends ITelemetryBaseLogger = ITelemet
273273

274274
// @public
275275
export class MockLogger implements ITelemetryBaseLogger {
276+
constructor(minLogLevel?: LogLevel | undefined);
276277
assertMatch(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string, inlineDetailsProp?: boolean): void;
277278
assertMatchAny(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string, inlineDetailsProp?: boolean): void;
278279
assertMatchNone(disallowedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string, inlineDetailsProp?: boolean): void;
@@ -285,6 +286,8 @@ export class MockLogger implements ITelemetryBaseLogger {
285286
matchEvents(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], inlineDetailsProp?: boolean): boolean;
286287
matchEventStrict(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], inlineDetailsProp?: boolean): boolean;
287288
// (undocumented)
289+
readonly minLogLevel?: LogLevel | undefined;
290+
// (undocumented)
288291
send(event: ITelemetryBaseEvent): void;
289292
// (undocumented)
290293
toTelemetryLogger(): ITelemetryLoggerExt;

packages/common/core-interfaces/src/logger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ export interface ITelemetryBaseEvent extends ITelemetryProperties {
4949
* Enum to specify a level to the log to filter out logs based on the level.
5050
*/
5151
export const enum LogLevel {
52-
verbose = 0, // To log any verbose event for example when you are debugging something.
53-
default = 10, // Default log level
54-
error = 20, // To log errors.
52+
verbose = 10, // To log any verbose event for example when you are debugging something.
53+
default = 20, // Default log level
54+
error = 30, // To log errors.
5555
}
5656

5757
/**

packages/test/test-service-load/src/runner.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,15 @@ async function main() {
9292
// this makes runners repeatable, but ensures each runner
9393
// will get its own set of randoms
9494
const random = makeRandom(seed, runId);
95-
const logger = await createLogger({
96-
runId,
97-
driverType: driver,
98-
driverEndpointName: endpoint,
99-
profile: profileName,
100-
});
95+
const logger = await createLogger(
96+
{
97+
runId,
98+
driverType: driver,
99+
driverEndpointName: endpoint,
100+
profile: profileName,
101+
},
102+
random.pick([LogLevel.verbose, LogLevel.default]),
103+
);
101104

102105
// this will enabling capturing the full stack for errors
103106
// since this is test capturing the full stack is worth it
@@ -201,7 +204,6 @@ async function runnerProcess(
201204
() => new FaultInjectionDocumentServiceFactory(testDriver.createDocumentServiceFactory()),
202205
);
203206

204-
const loggerLogLevelOptions = [LogLevel.verbose, LogLevel.default];
205207
let done = false;
206208
// Reset the workload once, on the first iteration
207209
let reset = true;
@@ -216,8 +218,6 @@ async function runnerProcess(
216218
const { documentServiceFactory, headers } = nextFactoryPermutation.value;
217219

218220
// Construct the loader
219-
runConfig.logger.minLogLevel =
220-
loggerLogLevelOptions[runConfig.runId % loggerLogLevelOptions.length];
221221
runConfig.loaderConfig = loaderOptions[runConfig.runId % loaderOptions.length];
222222
runConfig.logger.sendTelemetryEvent({
223223
eventName: "RunConfigOptions",

packages/test/test-service-load/src/utils.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,40 +40,48 @@ import { ILoadTestConfig, ITestConfig } from "./testConfigFile";
4040
const packageName = `${pkgName}@${pkgVersion}`;
4141

4242
class FileLogger implements ITelemetryBufferedLogger {
43-
private static readonly loggerP = new LazyPromise<FileLogger>(async () => {
44-
if (process.env.FLUID_TEST_LOGGER_PKG_PATH !== undefined) {
45-
await import(process.env.FLUID_TEST_LOGGER_PKG_PATH);
46-
const logger = getTestLogger?.();
47-
assert(logger !== undefined, "Expected getTestLogger to return something");
48-
return new FileLogger(logger);
49-
} else {
50-
return new FileLogger();
51-
}
52-
});
43+
private static readonly loggerP = (minLogLevel?: LogLevel) =>
44+
new LazyPromise<FileLogger>(async () => {
45+
if (process.env.FLUID_TEST_LOGGER_PKG_PATH !== undefined) {
46+
await import(process.env.FLUID_TEST_LOGGER_PKG_PATH);
47+
const logger = getTestLogger?.();
48+
assert(logger !== undefined, "Expected getTestLogger to return something");
49+
return new FileLogger(logger, minLogLevel);
50+
} else {
51+
return new FileLogger(undefined, minLogLevel);
52+
}
53+
});
5354

54-
public static async createLogger(dimensions: {
55-
driverType: string;
56-
driverEndpointName: string | undefined;
57-
profile: string;
58-
runId: number | undefined;
59-
}) {
55+
public static async createLogger(
56+
dimensions: {
57+
driverType: string;
58+
driverEndpointName: string | undefined;
59+
profile: string;
60+
runId: number | undefined;
61+
},
62+
minLogLevel: LogLevel = LogLevel.default,
63+
) {
64+
const logger = await this.loggerP(minLogLevel);
6065
return createChildLogger({
61-
logger: await this.loggerP,
66+
logger,
6267
properties: {
6368
all: dimensions,
6469
},
6570
});
6671
}
6772

6873
public static async flushLogger(runInfo?: { url: string; runId?: number }) {
69-
await (await this.loggerP).flush(runInfo);
74+
await (await this.loggerP()).flush(runInfo);
7075
}
7176

7277
private error: boolean = false;
7378
private readonly schema = new Map<string, number>();
7479
private logs: ITelemetryBaseEvent[] = [];
7580

76-
private constructor(private readonly baseLogger?: ITelemetryBufferedLogger) {}
81+
private constructor(
82+
private readonly baseLogger?: ITelemetryBufferedLogger,
83+
public readonly minLogLevel?: LogLevel,
84+
) {}
7785

7886
async flush(runInfo?: { url: string; runId?: number }): Promise<void> {
7987
const baseFlushP = this.baseLogger?.flush();
@@ -174,21 +182,24 @@ export async function initialize(
174182
generateConfigurations(seed, optionsOverride?.configurations),
175183
);
176184

177-
const logger = await createLogger({
178-
driverType: testDriver.type,
179-
driverEndpointName: testDriver.endpointName,
180-
profile: profileName,
181-
runId: undefined,
182-
});
183-
logger.minLogLevel = random.pick([LogLevel.verbose, LogLevel.default]);
185+
const minLogLevel = random.pick([LogLevel.verbose, LogLevel.default]);
186+
const logger = await createLogger(
187+
{
188+
driverType: testDriver.type,
189+
driverEndpointName: testDriver.endpointName,
190+
profile: profileName,
191+
runId: undefined,
192+
},
193+
minLogLevel,
194+
);
184195

185196
logger.sendTelemetryEvent({
186197
eventName: "RunConfigOptions",
187198
details: JSON.stringify({
188199
loaderOptions,
189200
containerOptions,
190201
configurations,
191-
logLevel: logger.minLogLevel,
202+
logLevel: minLogLevel,
192203
}),
193204
});
194205

packages/utils/telemetry-utils/src/logger.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
ITelemetryGenericEventExt,
3030
ITelemetryLoggerExt,
3131
ITelemetryPerformanceEventExt,
32-
ITelemetryPropertiesExt,
3332
TelemetryEventPropertyTypeExt,
3433
} from "./telemetryTypes";
3534

@@ -424,7 +423,11 @@ export class ChildLogger extends TelemetryLogger {
424423
}
425424
}
426425

427-
private shouldFilterOutEvent(event: ITelemetryPropertiesExt, logLevel?: LogLevel): boolean {
426+
public get minLogLevel(): LogLevel | undefined {
427+
return this.baseLogger.minLogLevel;
428+
}
429+
430+
private shouldFilterOutEvent(event: ITelemetryBaseEvent, logLevel?: LogLevel): boolean {
428431
const eventLogLevel = logLevel ?? LogLevel.default;
429432
const configLogLevel = this.baseLogger.minLogLevel ?? LogLevel.default;
430433
// Filter out in case event log level is below what is wanted in config.
@@ -469,6 +472,9 @@ export function createMultiSinkLogger(props: {
469472
*/
470473
export class MultiSinkLogger extends TelemetryLogger {
471474
protected loggers: ITelemetryBaseLogger[];
475+
// This is minimum of minLlogLevel of all loggers.
476+
private _minLogLevelOfAllLoggers: LogLevel;
477+
472478
/**
473479
* Create multiple sink logger (i.e. logger that sends events to multiple sinks)
474480
* @param namespace - Telemetry event name prefix to add to all events
@@ -500,6 +506,22 @@ export class MultiSinkLogger extends TelemetryLogger {
500506

501507
super(namespace, realProperties);
502508
this.loggers = loggers;
509+
this._minLogLevelOfAllLoggers = LogLevel.default;
510+
this.calculateMinLogLevel();
511+
}
512+
513+
public get minLogLevel(): LogLevel {
514+
return this._minLogLevelOfAllLoggers;
515+
}
516+
517+
private calculateMinLogLevel(): void {
518+
if (this.loggers.length > 0) {
519+
const logLevels: number[] = [];
520+
for (const logger of this.loggers) {
521+
logLevels.push(logger.minLogLevel ?? LogLevel.default);
522+
}
523+
this._minLogLevelOfAllLoggers = Math.min(...logLevels);
524+
}
503525
}
504526

505527
/**
@@ -509,6 +531,8 @@ export class MultiSinkLogger extends TelemetryLogger {
509531
public addLogger(logger?: ITelemetryBaseLogger): void {
510532
if (logger !== undefined && logger !== null) {
511533
this.loggers.push(logger);
534+
// Update in case the logLevel of added logger is less than the current.
535+
this.calculateMinLogLevel();
512536
}
513537
}
514538

packages/utils/telemetry-utils/src/mockLogger.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import { ITelemetryBaseEvent, ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
6+
import {
7+
ITelemetryBaseEvent,
8+
ITelemetryBaseLogger,
9+
LogLevel,
10+
} from "@fluidframework/core-interfaces";
711
import { assert } from "@fluidframework/common-utils";
812
import { ITelemetryLoggerExt, ITelemetryPropertiesExt } from "./telemetryTypes";
913
import { createChildLogger } from "./logger";
@@ -15,6 +19,8 @@ import { createChildLogger } from "./logger";
1519
export class MockLogger implements ITelemetryBaseLogger {
1620
events: ITelemetryBaseEvent[] = [];
1721

22+
constructor(public readonly minLogLevel?: LogLevel) {}
23+
1824
clear(): void {
1925
this.events = [];
2026
}

packages/utils/telemetry-utils/src/test/childLogger.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
ITelemetryBaseLogger,
1010
LogLevel,
1111
} from "@fluidframework/core-interfaces";
12-
import { ChildLogger, createChildLogger } from "../logger";
12+
import { ChildLogger, createChildLogger, createMultiSinkLogger } from "../logger";
13+
import { MockLogger } from "../mockLogger";
1314

1415
describe("ChildLogger", () => {
1516
it("Properties & Getters Propagate", () => {
@@ -244,4 +245,29 @@ describe("ChildLogger", () => {
244245
childLogger1.send({ category: "generic", eventName: "testEvent" }, LogLevel.verbose);
245246
assert(!sent, "event should not be sent");
246247
});
248+
249+
it("should be able to send events correctly according to loglevel if multisink logger is used inside childlogger", () => {
250+
let sent = false;
251+
const logger1: ITelemetryBaseLogger = {
252+
send(event: ITelemetryBaseEvent): void {
253+
if (event.eventName !== "testEvent") {
254+
throw new Error("unexpected event");
255+
}
256+
sent = true;
257+
},
258+
minLogLevel: LogLevel.default,
259+
};
260+
const multiSinkLogger = createMultiSinkLogger({
261+
loggers: [logger1, new MockLogger(LogLevel.error)],
262+
});
263+
const childLogger1 = createChildLogger({
264+
logger: multiSinkLogger,
265+
});
266+
267+
childLogger1.send({ category: "generic", eventName: "testEvent" }, LogLevel.verbose);
268+
assert(!sent, "verbose event should not be sent");
269+
270+
childLogger1.send({ category: "generic", eventName: "testEvent" }, LogLevel.default);
271+
assert(sent, "verbose event should be sent");
272+
});
247273
});

packages/utils/telemetry-utils/src/test/config.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
*/
55

66
import { strict as assert } from "assert";
7-
import { MockLogger } from "@fluidframework/telemetry-utils-previous";
87
import {
98
CachedConfigProvider,
109
ConfigTypes,
1110
IConfigProviderBase,
1211
inMemoryConfigProvider,
1312
} from "../config";
1413
import { TelemetryDataTag } from "../logger";
14+
import { MockLogger } from "../mockLogger";
1515

1616
const getMockStore = (settings: Record<string, string>): Storage => {
1717
const ops: string[] = [];

0 commit comments

Comments
 (0)