mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-15 13:57:45 -07:00
feat(core): add robustness tests, logging, and metrics for CodeAssistServer SSE parsing (#21013)
Co-authored-by: Yuna Seol <yunaseol@google.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
logFlashFallback,
|
||||
logChatCompression,
|
||||
logMalformedJsonResponse,
|
||||
logInvalidChunk,
|
||||
logFileOperation,
|
||||
logRipgrepFallback,
|
||||
logToolOutputTruncated,
|
||||
@@ -68,6 +69,7 @@ import {
|
||||
EVENT_AGENT_START,
|
||||
EVENT_AGENT_FINISH,
|
||||
EVENT_WEB_FETCH_FALLBACK_ATTEMPT,
|
||||
EVENT_INVALID_CHUNK,
|
||||
ApiErrorEvent,
|
||||
ApiRequestEvent,
|
||||
ApiResponseEvent,
|
||||
@@ -77,6 +79,7 @@ import {
|
||||
FlashFallbackEvent,
|
||||
RipgrepFallbackEvent,
|
||||
MalformedJsonResponseEvent,
|
||||
InvalidChunkEvent,
|
||||
makeChatCompressionEvent,
|
||||
FileOperationEvent,
|
||||
ToolOutputTruncatedEvent,
|
||||
@@ -1736,6 +1739,39 @@ describe('loggers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('logInvalidChunk', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(ClearcutLogger.prototype, 'logInvalidChunkEvent');
|
||||
vi.spyOn(metrics, 'recordInvalidChunk');
|
||||
});
|
||||
|
||||
it('logs the event to Clearcut and OTEL', () => {
|
||||
const mockConfig = makeFakeConfig();
|
||||
const event = new InvalidChunkEvent('Unexpected token');
|
||||
|
||||
logInvalidChunk(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logInvalidChunkEvent,
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Invalid chunk received from stream.',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'installation.id': 'test-installation-id',
|
||||
'event.name': EVENT_INVALID_CHUNK,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
interactive: false,
|
||||
'error.message': 'Unexpected token',
|
||||
},
|
||||
});
|
||||
|
||||
expect(metrics.recordInvalidChunk).toHaveBeenCalledWith(mockConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logFileOperation', () => {
|
||||
const mockConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
type ConversationFinishedEvent,
|
||||
type ChatCompressionEvent,
|
||||
type MalformedJsonResponseEvent,
|
||||
type InvalidChunkEvent,
|
||||
type ContentRetryEvent,
|
||||
type ContentRetryFailureEvent,
|
||||
type RipgrepFallbackEvent,
|
||||
@@ -75,6 +76,7 @@ import {
|
||||
recordPlanExecution,
|
||||
recordKeychainAvailability,
|
||||
recordTokenStorageInitialization,
|
||||
recordInvalidChunk,
|
||||
} from './metrics.js';
|
||||
import { bufferTelemetryEvent } from './sdk.js';
|
||||
import { uiTelemetryService, type UiEvent } from './uiTelemetry.js';
|
||||
@@ -467,6 +469,22 @@ export function logMalformedJsonResponse(
|
||||
});
|
||||
}
|
||||
|
||||
export function logInvalidChunk(
|
||||
config: Config,
|
||||
event: InvalidChunkEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logInvalidChunkEvent(event);
|
||||
bufferTelemetryEvent(() => {
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: event.toLogBody(),
|
||||
attributes: event.toOpenTelemetryAttributes(config),
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
recordInvalidChunk(config);
|
||||
});
|
||||
}
|
||||
|
||||
export function logContentRetry(
|
||||
config: Config,
|
||||
event: ContentRetryEvent,
|
||||
|
||||
@@ -105,6 +105,7 @@ describe('Telemetry Metrics', () => {
|
||||
let recordPlanExecutionModule: typeof import('./metrics.js').recordPlanExecution;
|
||||
let recordKeychainAvailabilityModule: typeof import('./metrics.js').recordKeychainAvailability;
|
||||
let recordTokenStorageInitializationModule: typeof import('./metrics.js').recordTokenStorageInitialization;
|
||||
let recordInvalidChunkModule: typeof import('./metrics.js').recordInvalidChunk;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
@@ -154,6 +155,7 @@ describe('Telemetry Metrics', () => {
|
||||
metricsJsModule.recordKeychainAvailability;
|
||||
recordTokenStorageInitializationModule =
|
||||
metricsJsModule.recordTokenStorageInitialization;
|
||||
recordInvalidChunkModule = metricsJsModule.recordInvalidChunk;
|
||||
|
||||
const otelApiModule = await import('@opentelemetry/api');
|
||||
|
||||
@@ -1555,5 +1557,27 @@ describe('Telemetry Metrics', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordInvalidChunk', () => {
|
||||
it('should not record metrics if not initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
recordInvalidChunkModule(config);
|
||||
expect(mockCounterAddFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record invalid chunk when initialized', () => {
|
||||
const config = makeFakeConfig({});
|
||||
initializeMetricsModule(config);
|
||||
mockCounterAddFn.mockClear();
|
||||
|
||||
recordInvalidChunkModule(config);
|
||||
|
||||
expect(mockCounterAddFn).toHaveBeenCalledWith(1, {
|
||||
'session.id': 'test-session-id',
|
||||
'installation.id': 'test-installation-id',
|
||||
'user.email': 'test@example.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user