add(OTel): Add OTel logging for MalformedJsonEvent (#6912)

Co-authored-by: Shnatu <snatu@google.com>
Co-authored-by: cornmander <shikhman@google.com>
This commit is contained in:
Shardul Natu
2025-08-24 19:11:41 -07:00
committed by GitHub
parent bedd1d2c20
commit 1918f4466b
5 changed files with 62 additions and 2 deletions

View File

@@ -220,6 +220,10 @@ Logs are timestamped records of specific events. The following events are logged
- `response_text` (if applicable)
- `auth_type`
- `gemini_cli.malformed_json_response`: This event occurs when a `generateJson` response from Gemini API cannot be parsed as a json.
- **Attributes**:
- `model`
- `gemini_cli.flash_fallback`: This event occurs when Gemini CLI switches to flash as fallback.
- **Attributes**:
- `auth_type`

View File

@@ -46,13 +46,13 @@ import { ideContext } from '../ide/ideContext.js';
import {
logChatCompression,
logNextSpeakerCheck,
logMalformedJsonResponse,
} from '../telemetry/loggers.js';
import {
makeChatCompressionEvent,
MalformedJsonResponseEvent,
NextSpeakerCheckEvent,
} from '../telemetry/types.js';
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
import { IdeContext, File } from '../ide/ideContext.js';
function isThinkingSupported(model: string) {
@@ -617,7 +617,8 @@ export class GeminiClient {
const prefix = '```json';
const suffix = '```';
if (text.startsWith(prefix) && text.endsWith(suffix)) {
ClearcutLogger.getInstance(this.config)?.logMalformedJsonResponseEvent(
logMalformedJsonResponse(
this.config,
new MalformedJsonResponseEvent(modelToUse),
);
text = text

View File

@@ -17,6 +17,8 @@ export const EVENT_NEXT_SPEAKER_CHECK = 'gemini_cli.next_speaker_check';
export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command';
export const EVENT_IDE_CONNECTION = 'gemini_cli.ide_connection';
export const EVENT_CHAT_COMPRESSION = 'gemini_cli.chat_compression';
export const EVENT_MALFORMED_JSON_RESPONSE =
'gemini_cli.malformed_json_response';
export const METRIC_TOOL_CALL_COUNT = 'gemini_cli.tool.call.count';
export const METRIC_TOOL_CALL_LATENCY = 'gemini_cli.tool.call.latency';
export const METRIC_API_REQUEST_COUNT = 'gemini_cli.api.request.count';

View File

@@ -26,6 +26,7 @@ import {
EVENT_TOOL_CALL,
EVENT_USER_PROMPT,
EVENT_FLASH_FALLBACK,
EVENT_MALFORMED_JSON_RESPONSE,
} from './constants.js';
import {
logApiRequest,
@@ -35,6 +36,7 @@ import {
logToolCall,
logFlashFallback,
logChatCompression,
logMalformedJsonResponse,
} from './loggers.js';
import { ToolCallDecision } from './tool-call-decision.js';
import {
@@ -44,6 +46,7 @@ import {
ToolCallEvent,
UserPromptEvent,
FlashFallbackEvent,
MalformedJsonResponseEvent,
makeChatCompressionEvent,
} from './types.js';
import * as metrics from './metrics.js';
@@ -818,4 +821,31 @@ describe('loggers', () => {
});
});
});
describe('logMalformedJsonResponse', () => {
beforeEach(() => {
vi.spyOn(ClearcutLogger.prototype, 'logMalformedJsonResponseEvent');
});
it('logs the event to Clearcut and OTEL', () => {
const mockConfig = makeFakeConfig();
const event = new MalformedJsonResponseEvent('test-model');
logMalformedJsonResponse(mockConfig, event);
expect(
ClearcutLogger.prototype.logMalformedJsonResponseEvent,
).toHaveBeenCalledWith(event);
expect(mockLogger.emit).toHaveBeenCalledWith({
body: 'Malformed JSON response from test-model.',
attributes: {
'session.id': 'test-session-id',
'event.name': EVENT_MALFORMED_JSON_RESPONSE,
'event.timestamp': '2025-01-01T00:00:00.000Z',
model: 'test-model',
},
});
});
});
});

View File

@@ -20,6 +20,7 @@ import {
SERVICE_NAME,
EVENT_SLASH_COMMAND,
EVENT_CHAT_COMPRESSION,
EVENT_MALFORMED_JSON_RESPONSE,
} from './constants.js';
import {
ApiErrorEvent,
@@ -36,6 +37,7 @@ import {
SlashCommandEvent,
KittySequenceOverflowEvent,
ChatCompressionEvent,
MalformedJsonResponseEvent,
} from './types.js';
import {
recordApiErrorMetrics,
@@ -449,3 +451,24 @@ export function logKittySequenceOverflow(
};
logger.emit(logRecord);
}
export function logMalformedJsonResponse(
config: Config,
event: MalformedJsonResponseEvent,
): void {
ClearcutLogger.getInstance(config)?.logMalformedJsonResponseEvent(event);
if (!isTelemetrySdkInitialized()) return;
const attributes: LogAttributes = {
...getCommonAttributes(config),
...event,
'event.name': EVENT_MALFORMED_JSON_RESPONSE,
};
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: `Malformed JSON response from ${event.model}.`,
attributes,
};
logger.emit(logRecord);
}