|
5 | 5 |
|
6 | 6 | import { strict as assert } from "node:assert"; |
7 | 7 |
|
8 | | -import type { ITelemetryBaseEvent, Tagged } from "@fluidframework/core-interfaces"; |
| 8 | +import type { |
| 9 | + ITelemetryBaseEvent, |
| 10 | + ITelemetryBaseLogger, |
| 11 | + Tagged, |
| 12 | +} from "@fluidframework/core-interfaces"; |
9 | 13 |
|
10 | 14 | import { |
11 | 15 | type ITelemetryLoggerPropertyBag, |
12 | 16 | type ITelemetryLoggerPropertyBags, |
13 | 17 | LogLevelValue, |
14 | 18 | TelemetryLogger, |
15 | 19 | convertToBasePropertyType, |
| 20 | + createChildLogger, |
16 | 21 | } from "../logger.js"; |
17 | 22 | import type { TelemetryEventPropertyTypeExt } from "../telemetryTypes.js"; |
18 | 23 |
|
@@ -432,88 +437,82 @@ describe("convertToBasePropertyType", () => { |
432 | 437 | }); |
433 | 438 | }); |
434 | 439 |
|
435 | | -describe("LogLevelValue-based sampling", () => { |
436 | | - it("marks event as sampleEvent when logLevel value is verbose", () => { |
437 | | - const logger = new TestTelemetryLogger(); |
| 440 | +describe.only("LogLevelValue-based sampling", () => { |
| 441 | + /** |
| 442 | + * Example implementation of ITelemetryBaseLogger that uses LogLevelValue |
| 443 | + * to filter events. Events with a logLevel below the configured sampling |
| 444 | + * threshold are dropped; the rest are kept. |
| 445 | + */ |
| 446 | + class ExampleSamplingLogger implements ITelemetryBaseLogger { |
| 447 | + public events: ITelemetryBaseEvent[] = []; |
438 | 448 |
|
439 | | - logger.sendTelemetryEvent({ |
440 | | - eventName: "VerboseMetric", |
441 | | - logLevel: LogLevelValue.verbose, |
442 | | - }); |
| 449 | + public constructor( |
| 450 | + private readonly samplingThreshold: LogLevelValue = LogLevelValue.essential, |
| 451 | + ) {} |
443 | 452 |
|
444 | | - assert.strictEqual(logger.events.length, 1, "One event should be logged"); |
445 | | - const event = logger.events[0]; |
446 | | - |
447 | | - // Simulate what a consumer's send() would do: |
448 | | - // if the event's logLevel value is below essential, mark as sampleEvent |
449 | | - const eventLogLevel = event.logLevel as number; |
450 | | - if (eventLogLevel < LogLevelValue.essential) { |
451 | | - event.sampleEvent = true; |
| 453 | + public send(event: ITelemetryBaseEvent): void { |
| 454 | + const eventLogLevel = (event.logLevel as number) ?? LogLevelValue.essential; |
| 455 | + if (eventLogLevel >= this.samplingThreshold) { |
| 456 | + this.events.push(event); |
| 457 | + } |
452 | 458 | } |
| 459 | + } |
453 | 460 |
|
454 | | - assert.strictEqual( |
455 | | - event.sampleEvent, |
456 | | - true, |
457 | | - "Verbose event should be marked as sampleEvent", |
458 | | - ); |
459 | | - }); |
460 | | - |
461 | | - it("marks event as sampleEvent when logLevel value is info", () => { |
462 | | - const logger = new TestTelemetryLogger(); |
| 461 | + it("filters events by logLevel when using the default (essential) threshold", () => { |
| 462 | + const baseLogger = new ExampleSamplingLogger(); |
| 463 | + const childLogger = createChildLogger({ logger: baseLogger }); |
463 | 464 |
|
464 | | - logger.sendTelemetryEvent({ |
465 | | - eventName: "InfoMetric", |
| 465 | + // Send events at each log level, plus one with no level specified |
| 466 | + childLogger.sendTelemetryEvent({ |
| 467 | + eventName: "VerboseEvent", |
| 468 | + logLevel: LogLevelValue.verbose, |
| 469 | + }); |
| 470 | + childLogger.sendTelemetryEvent({ |
| 471 | + eventName: "InfoEvent", |
466 | 472 | logLevel: LogLevelValue.info, |
467 | 473 | }); |
468 | | - |
469 | | - assert.strictEqual(logger.events.length, 1, "One event should be logged"); |
470 | | - const event = logger.events[0]; |
471 | | - |
472 | | - // Simulate what a consumer's send() would do: |
473 | | - // if the event's logLevel value is below essential, mark as sampleEvent |
474 | | - const eventLogLevel = event.logLevel as number; |
475 | | - if (eventLogLevel < LogLevelValue.essential) { |
476 | | - event.sampleEvent = true; |
477 | | - } |
478 | | - |
479 | | - assert.strictEqual(event.sampleEvent, true, "Info event should be marked as sampleEvent"); |
480 | | - }); |
481 | | - |
482 | | - it("does not mark event as sampleEvent when logLevel value is essential", () => { |
483 | | - const logger = new TestTelemetryLogger(); |
484 | | - |
485 | | - logger.sendTelemetryEvent({ |
486 | | - eventName: "EssentialMetric", |
| 474 | + childLogger.sendTelemetryEvent({ |
| 475 | + eventName: "EssentialEvent", |
487 | 476 | logLevel: LogLevelValue.essential, |
488 | 477 | }); |
| 478 | + childLogger.sendTelemetryEvent({ eventName: "DefaultLevelEvent" }); |
489 | 479 |
|
490 | | - assert.strictEqual(logger.events.length, 1, "One event should be logged"); |
491 | | - const event = logger.events[0]; |
492 | | - |
493 | | - // Simulate what a consumer's send() would do: |
494 | | - // if the event's logLevel value is below essential, mark as sampleEvent |
495 | | - const eventLogLevel = event.logLevel as number; |
496 | | - if (eventLogLevel < LogLevelValue.essential) { |
497 | | - event.sampleEvent = true; |
498 | | - } |
499 | | - |
| 480 | + // With the default threshold (essential), only essential-and-above events are kept |
500 | 481 | assert.strictEqual( |
501 | | - event.sampleEvent, |
502 | | - undefined, |
503 | | - "Essential event should not be marked as sampleEvent", |
| 482 | + baseLogger.events.length, |
| 483 | + 2, |
| 484 | + "Only essential-level events should be logged", |
504 | 485 | ); |
| 486 | + assert.strictEqual(baseLogger.events[0].eventName, "EssentialEvent"); |
| 487 | + assert.strictEqual(baseLogger.events[1].eventName, "DefaultLevelEvent"); |
505 | 488 | }); |
506 | 489 |
|
507 | | - it("defaults to 'essential' logLevel value on the event when none is specified", () => { |
508 | | - const logger = new TestTelemetryLogger(); |
| 490 | + it("allows more events through with a lower sampling threshold", () => { |
| 491 | + const baseLogger = new ExampleSamplingLogger(LogLevelValue.info); |
| 492 | + const childLogger = createChildLogger({ logger: baseLogger }); |
509 | 493 |
|
510 | | - logger.sendTelemetryEvent({ eventName: "DefaultLevelEvent" }); |
| 494 | + childLogger.sendTelemetryEvent({ |
| 495 | + eventName: "VerboseEvent", |
| 496 | + logLevel: LogLevelValue.verbose, |
| 497 | + }); |
| 498 | + childLogger.sendTelemetryEvent({ |
| 499 | + eventName: "InfoEvent", |
| 500 | + logLevel: LogLevelValue.info, |
| 501 | + }); |
| 502 | + childLogger.sendTelemetryEvent({ |
| 503 | + eventName: "EssentialEvent", |
| 504 | + logLevel: LogLevelValue.essential, |
| 505 | + }); |
| 506 | + childLogger.sendTelemetryEvent({ eventName: "DefaultLevelEvent" }); |
511 | 507 |
|
512 | | - assert.strictEqual(logger.events.length, 1, "One event should be logged"); |
| 508 | + // With an info threshold, verbose is dropped but info and essential are kept |
513 | 509 | assert.strictEqual( |
514 | | - logger.events[0].logLevel, |
515 | | - LogLevelValue.essential, |
516 | | - "Default logLevel on event should be essential", |
| 510 | + baseLogger.events.length, |
| 511 | + 3, |
| 512 | + "Info-level and above events should be logged", |
517 | 513 | ); |
| 514 | + assert.strictEqual(baseLogger.events[0].eventName, "InfoEvent"); |
| 515 | + assert.strictEqual(baseLogger.events[1].eventName, "EssentialEvent"); |
| 516 | + assert.strictEqual(baseLogger.events[2].eventName, "DefaultLevelEvent"); |
518 | 517 | }); |
519 | 518 | }); |
0 commit comments