summaryrefslogtreecommitdiff
path: root/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts')
-rw-r--r--packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts181
1 files changed, 129 insertions, 52 deletions
diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts
index f2ce4d19..39596616 100644
--- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts
+++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import 'vitest';
import {
vi,
describe,
@@ -13,8 +14,13 @@ import {
beforeAll,
afterAll,
} from 'vitest';
-
-import { ClearcutLogger, LogEventEntry, TEST_ONLY } from './clearcut-logger.js';
+import {
+ ClearcutLogger,
+ LogEvent,
+ LogEventEntry,
+ EventNames,
+ TEST_ONLY,
+} from './clearcut-logger.js';
import { ConfigParameters } from '../../config/config.js';
import * as userAccount from '../../utils/user_account.js';
import * as userId from '../../utils/user_id.js';
@@ -22,6 +28,48 @@ import { EventMetadataKey } from './event-metadata-key.js';
import { makeFakeConfig } from '../../test-utils/config.js';
import { http, HttpResponse } from 'msw';
import { server } from '../../mocks/msw.js';
+import { makeChatCompressionEvent } from '../types.js';
+
+interface CustomMatchers<R = unknown> {
+ toHaveMetadataValue: ([key, value]: [EventMetadataKey, string]) => R;
+ toHaveEventName: (name: EventNames) => R;
+}
+
+declare module 'vitest' {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
+ interface Matchers<T = any> extends CustomMatchers<T> {}
+}
+
+expect.extend({
+ toHaveEventName(received: LogEventEntry[], name: EventNames) {
+ const { isNot } = this;
+ const event = JSON.parse(received[0].source_extension_json) as LogEvent;
+ const pass = event.event_name === (name as unknown as string);
+ return {
+ pass,
+ message: () =>
+ `event name ${event.event_name} does${isNot ? ' not ' : ''} match ${name}}`,
+ };
+ },
+
+ toHaveMetadataValue(
+ received: LogEventEntry[],
+ [key, value]: [EventMetadataKey, string],
+ ) {
+ const { isNot } = this;
+ const event = JSON.parse(received[0].source_extension_json) as LogEvent;
+ const metadata = event['event_metadata'][0];
+ const data = metadata.find((m) => m.gemini_cli_key === key)?.value;
+
+ const pass = data !== undefined && data === value;
+
+ return {
+ pass,
+ message: () =>
+ `event ${received} does${isNot ? ' not' : ''} have ${value}}`,
+ };
+ },
+});
vi.mock('../../utils/user_account');
vi.mock('../../utils/user_id');
@@ -47,6 +95,7 @@ describe('ClearcutLogger', () => {
const CLEARCUT_URL = 'https://play.googleapis.com/log';
const MOCK_DATE = new Date('2025-01-02T00:00:00.000Z');
const EXAMPLE_RESPONSE = `["${NEXT_WAIT_MS}",null,[[["ANDROID_BACKUP",0],["BATTERY_STATS",0],["SMART_SETUP",0],["TRON",0]],-3334737594024971225],[]]`;
+
// A helper to get the internal events array for testing
const getEvents = (l: ClearcutLogger): LogEventEntry[][] =>
l['events'].toArray() as LogEventEntry[][];
@@ -130,7 +179,7 @@ describe('ClearcutLogger', () => {
lifetimeGoogleAccounts: 9001,
});
- const event = logger?.createLogEvent('abc', []);
+ const event = logger?.createLogEvent(EventNames.API_ERROR, []);
expect(event?.event_metadata[0][0]).toEqual({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_GOOGLE_ACCOUNTS_COUNT,
@@ -138,26 +187,13 @@ describe('ClearcutLogger', () => {
});
});
- it('logs the current surface from a github action', () => {
- const { logger } = setup({});
-
- vi.stubEnv('GITHUB_SHA', '8675309');
-
- const event = logger?.createLogEvent('abc', []);
-
- expect(event?.event_metadata[0][1]).toEqual({
- gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
- value: 'GitHub',
- });
- });
-
- it('honors the value from env.SURFACE over all others', () => {
+ it('logs the current surface', () => {
const { logger } = setup({});
vi.stubEnv('TERM_PROGRAM', 'vscode');
vi.stubEnv('SURFACE', 'ide-1234');
- const event = logger?.createLogEvent('abc', []);
+ const event = logger?.createLogEvent(EventNames.API_ERROR, []);
expect(event?.event_metadata[0][1]).toEqual({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
@@ -209,7 +245,7 @@ describe('ClearcutLogger', () => {
vi.stubEnv(key, value);
}
vi.stubEnv('TERM_PROGRAM', 'vscode');
- const event = logger?.createLogEvent('abc', []);
+ const event = logger?.createLogEvent(EventNames.API_ERROR, []);
expect(event?.event_metadata[0][1]).toEqual({
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
value: expectedValue,
@@ -218,10 +254,34 @@ describe('ClearcutLogger', () => {
);
});
+ describe('logChatCompressionEvent', () => {
+ it('logs an event with proper fields', () => {
+ const { logger } = setup();
+ logger?.logChatCompressionEvent(
+ makeChatCompressionEvent({
+ tokens_before: 9001,
+ tokens_after: 8000,
+ }),
+ );
+
+ const events = getEvents(logger!);
+ expect(events.length).toBe(1);
+ expect(events[0]).toHaveEventName(EventNames.CHAT_COMPRESSION);
+ expect(events[0]).toHaveMetadataValue([
+ EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_BEFORE,
+ '9001',
+ ]);
+ expect(events[0]).toHaveMetadataValue([
+ EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_AFTER,
+ '8000',
+ ]);
+ });
+ });
+
describe('enqueueLogEvent', () => {
it('should add events to the queue', () => {
const { logger } = setup();
- logger!.enqueueLogEvent({ test: 'event1' });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
expect(getEventsSize(logger!)).toBe(1);
});
@@ -229,27 +289,43 @@ describe('ClearcutLogger', () => {
const { logger } = setup();
for (let i = 0; i < TEST_ONLY.MAX_EVENTS; i++) {
- logger!.enqueueLogEvent({ event_id: i });
+ logger!.enqueueLogEvent(
+ logger!.createLogEvent(EventNames.API_ERROR, [
+ {
+ gemini_cli_key: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
+ value: `${i}`,
+ },
+ ]),
+ );
}
- expect(getEventsSize(logger!)).toBe(TEST_ONLY.MAX_EVENTS);
- const firstEvent = JSON.parse(
- getEvents(logger!)[0][0].source_extension_json,
- );
- expect(firstEvent.event_id).toBe(0);
+ let events = getEvents(logger!);
+ expect(events.length).toBe(TEST_ONLY.MAX_EVENTS);
+ expect(events[0]).toHaveMetadataValue([
+ EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
+ '0',
+ ]);
// This should push out the first event
- logger!.enqueueLogEvent({ event_id: TEST_ONLY.MAX_EVENTS });
-
- expect(getEventsSize(logger!)).toBe(TEST_ONLY.MAX_EVENTS);
- const newFirstEvent = JSON.parse(
- getEvents(logger!)[0][0].source_extension_json,
- );
- expect(newFirstEvent.event_id).toBe(1);
- const lastEvent = JSON.parse(
- getEvents(logger!)[TEST_ONLY.MAX_EVENTS - 1][0].source_extension_json,
+ logger!.enqueueLogEvent(
+ logger!.createLogEvent(EventNames.API_ERROR, [
+ {
+ gemini_cli_key: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
+ value: `${TEST_ONLY.MAX_EVENTS}`,
+ },
+ ]),
);
- expect(lastEvent.event_id).toBe(TEST_ONLY.MAX_EVENTS);
+ events = getEvents(logger!);
+ expect(events.length).toBe(TEST_ONLY.MAX_EVENTS);
+ expect(events[0]).toHaveMetadataValue([
+ EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
+ '1',
+ ]);
+
+ expect(events.at(TEST_ONLY.MAX_EVENTS - 1)).toHaveMetadataValue([
+ EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
+ `${TEST_ONLY.MAX_EVENTS}`,
+ ]);
});
});
@@ -261,7 +337,7 @@ describe('ClearcutLogger', () => {
},
});
- logger!.enqueueLogEvent({ event_id: 1 });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
const response = await logger!.flushToClearcut();
@@ -271,7 +347,7 @@ describe('ClearcutLogger', () => {
it('should clear events on successful flush', async () => {
const { logger } = setup();
- logger!.enqueueLogEvent({ event_id: 1 });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
const response = await logger!.flushToClearcut();
expect(getEvents(logger!)).toEqual([]);
@@ -282,8 +358,8 @@ describe('ClearcutLogger', () => {
const { logger } = setup();
server.resetHandlers(http.post(CLEARCUT_URL, () => HttpResponse.error()));
- logger!.enqueueLogEvent({ event_id: 1 });
- logger!.enqueueLogEvent({ event_id: 2 });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_REQUEST));
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
expect(getEventsSize(logger!)).toBe(2);
const x = logger!.flushToClearcut();
@@ -291,7 +367,9 @@ describe('ClearcutLogger', () => {
expect(getEventsSize(logger!)).toBe(2);
const events = getEvents(logger!);
- expect(JSON.parse(events[0][0].source_extension_json).event_id).toBe(1);
+
+ expect(events.length).toBe(2);
+ expect(events[0]).toHaveEventName(EventNames.API_REQUEST);
});
it('should handle an HTTP error and requeue events', async () => {
@@ -310,23 +388,22 @@ describe('ClearcutLogger', () => {
),
);
- logger!.enqueueLogEvent({ event_id: 1 });
- logger!.enqueueLogEvent({ event_id: 2 });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_REQUEST));
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
expect(getEvents(logger!).length).toBe(2);
await logger!.flushToClearcut();
- expect(getEvents(logger!).length).toBe(2);
const events = getEvents(logger!);
- expect(JSON.parse(events[0][0].source_extension_json).event_id).toBe(1);
+
+ expect(events[0]).toHaveEventName(EventNames.API_REQUEST);
});
});
describe('requeueFailedEvents logic', () => {
it('should limit the number of requeued events to max_retry_events', () => {
const { logger } = setup();
- const maxRetryEvents = TEST_ONLY.MAX_RETRY_EVENTS;
- const eventsToLogCount = maxRetryEvents + 5;
+ const eventsToLogCount = TEST_ONLY.MAX_RETRY_EVENTS + 5;
const eventsToSend: LogEventEntry[][] = [];
for (let i = 0; i < eventsToLogCount; i++) {
eventsToSend.push([
@@ -339,13 +416,13 @@ describe('ClearcutLogger', () => {
requeueFailedEvents(logger!, eventsToSend);
- expect(getEventsSize(logger!)).toBe(maxRetryEvents);
+ expect(getEventsSize(logger!)).toBe(TEST_ONLY.MAX_RETRY_EVENTS);
const firstRequeuedEvent = JSON.parse(
getEvents(logger!)[0][0].source_extension_json,
- );
+ ) as { event_id: string };
// The last `maxRetryEvents` are kept. The oldest of those is at index `eventsToLogCount - maxRetryEvents`.
expect(firstRequeuedEvent.event_id).toBe(
- eventsToLogCount - maxRetryEvents,
+ eventsToLogCount - TEST_ONLY.MAX_RETRY_EVENTS,
);
});
@@ -355,7 +432,7 @@ describe('ClearcutLogger', () => {
const spaceToLeave = 5;
const initialEventCount = maxEvents - spaceToLeave;
for (let i = 0; i < initialEventCount; i++) {
- logger!.enqueueLogEvent({ event_id: `initial_${i}` });
+ logger!.enqueueLogEvent(logger!.createLogEvent(EventNames.API_ERROR));
}
expect(getEventsSize(logger!)).toBe(initialEventCount);
@@ -382,7 +459,7 @@ describe('ClearcutLogger', () => {
// The first element in the deque is the one with id 'failed_5'.
const firstRequeuedEvent = JSON.parse(
getEvents(logger!)[0][0].source_extension_json,
- );
+ ) as { event_id: string };
expect(firstRequeuedEvent.event_id).toBe('failed_5');
});
});