summaryrefslogtreecommitdiff
path: root/packages/core/src
diff options
context:
space:
mode:
authorjerop <[email protected]>2025-06-11 17:47:21 +0000
committerJerop Kipruto <[email protected]>2025-06-11 14:18:16 -0400
commit03bc1f314121c381ac4fe19f031fe90fbcf95179 (patch)
tree11ba8311ff2394b0c70adafc85760cde6db444e8 /packages/core/src
parent9237e95f113d0114ec492d6ed852d01b2c9d4d24 (diff)
feat(telemetry): Update API response in telemetry
Adds the text content of the API response to the telemetry event. This provides more context for debugging and analysis without logging the entire, potentially large, response object. - Adds an optional field to the type. - Updates to include the field in the logged attributes. - Modifies the to extract the response text using and pass it to the logger. - Adds a new test file for the telemetry loggers, including tests for the function to verify the new functionality.
Diffstat (limited to 'packages/core/src')
-rw-r--r--packages/core/src/core/client.ts1
-rw-r--r--packages/core/src/telemetry/loggers.test.ts119
-rw-r--r--packages/core/src/telemetry/loggers.ts3
-rw-r--r--packages/core/src/telemetry/types.ts1
4 files changed, 124 insertions, 0 deletions
diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts
index a2deca4e..596ddcd7 100644
--- a/packages/core/src/core/client.ts
+++ b/packages/core/src/core/client.ts
@@ -250,6 +250,7 @@ export class GeminiClient {
response.usageMetadata?.cachedContentTokenCount ?? 0,
thoughts_token_count: response.usageMetadata?.thoughtsTokenCount ?? 0,
tool_token_count: response.usageMetadata?.toolUsePromptTokenCount ?? 0,
+ response_text: getResponseText(response),
});
}
diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts
new file mode 100644
index 00000000..3493dc49
--- /dev/null
+++ b/packages/core/src/telemetry/loggers.test.ts
@@ -0,0 +1,119 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { logs } from '@opentelemetry/api-logs';
+import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
+import { Config } from '../config/config.js';
+import { EVENT_API_RESPONSE } from './constants.js';
+import { logApiResponse } from './loggers.js';
+import * as metrics from './metrics.js';
+import * as sdk from './sdk.js';
+import { vi, describe, beforeEach, it, expect } from 'vitest';
+
+describe('logApiResponse', () => {
+ const mockConfig = {
+ getSessionId: () => 'test-session-id',
+ } as Config;
+
+ const mockLogger = {
+ emit: vi.fn(),
+ };
+
+ const mockMetrics = {
+ recordApiResponseMetrics: vi.fn(),
+ recordTokenUsageMetrics: vi.fn(),
+ };
+
+ beforeEach(() => {
+ vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(true);
+ vi.spyOn(logs, 'getLogger').mockReturnValue(mockLogger);
+ vi.spyOn(metrics, 'recordApiResponseMetrics').mockImplementation(
+ mockMetrics.recordApiResponseMetrics,
+ );
+ vi.spyOn(metrics, 'recordTokenUsageMetrics').mockImplementation(
+ mockMetrics.recordTokenUsageMetrics,
+ );
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
+ });
+
+ it('should log an API response with all fields', () => {
+ const event = {
+ model: 'test-model',
+ status_code: 200,
+ duration_ms: 100,
+ attempt: 1,
+ output_token_count: 50,
+ cached_content_token_count: 10,
+ thoughts_token_count: 5,
+ tool_token_count: 2,
+ response_text: 'test-response',
+ };
+
+ logApiResponse(mockConfig, event);
+
+ expect(mockLogger.emit).toHaveBeenCalledWith({
+ body: 'API response from test-model. Status: 200. Duration: 100ms.',
+ attributes: {
+ 'session.id': 'test-session-id',
+ 'event.name': EVENT_API_RESPONSE,
+ 'event.timestamp': '2025-01-01T00:00:00.000Z',
+ [SemanticAttributes.HTTP_STATUS_CODE]: 200,
+ model: 'test-model',
+ status_code: 200,
+ duration_ms: 100,
+ attempt: 1,
+ output_token_count: 50,
+ cached_content_token_count: 10,
+ thoughts_token_count: 5,
+ tool_token_count: 2,
+ response_text: 'test-response',
+ },
+ });
+
+ expect(mockMetrics.recordApiResponseMetrics).toHaveBeenCalledWith(
+ mockConfig,
+ 'test-model',
+ 100,
+ 200,
+ undefined,
+ );
+
+ expect(mockMetrics.recordTokenUsageMetrics).toHaveBeenCalledWith(
+ mockConfig,
+ 'test-model',
+ 50,
+ 'output',
+ );
+ });
+
+ it('should log an API response with an error', () => {
+ const event = {
+ model: 'test-model',
+ duration_ms: 100,
+ attempt: 1,
+ error: 'test-error',
+ output_token_count: 50,
+ cached_content_token_count: 10,
+ thoughts_token_count: 5,
+ tool_token_count: 2,
+ response_text: 'test-response',
+ };
+
+ logApiResponse(mockConfig, event);
+
+ expect(mockLogger.emit).toHaveBeenCalledWith({
+ body: 'API response from test-model. Status: N/A. Duration: 100ms.',
+ attributes: {
+ 'session.id': 'test-session-id',
+ ...event,
+ 'event.name': EVENT_API_RESPONSE,
+ 'event.timestamp': '2025-01-01T00:00:00.000Z',
+ 'error.message': 'test-error',
+ },
+ });
+ });
+});
diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts
index d2b01f65..48275829 100644
--- a/packages/core/src/telemetry/loggers.ts
+++ b/packages/core/src/telemetry/loggers.ts
@@ -195,6 +195,9 @@ export function logApiResponse(
'event.name': EVENT_API_RESPONSE,
'event.timestamp': new Date().toISOString(),
};
+ if (event.response_text) {
+ attributes.response_text = event.response_text;
+ }
if (event.error) {
attributes['error.message'] = event.error;
} else if (event.status_code) {
diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts
index 4e2933a0..f62bd23e 100644
--- a/packages/core/src/telemetry/types.ts
+++ b/packages/core/src/telemetry/types.ts
@@ -53,6 +53,7 @@ export interface ApiResponseEvent {
cached_content_token_count: number;
thoughts_token_count: number;
tool_token_count: number;
+ response_text?: string;
}
export interface CliConfigEvent {