qwen-code/packages/core/src/config/config.test.ts
Jerop Kipruto 53753f0455
Add telemetry command and refactor telemetry settings (#1060)
#750 

### Telemetry Settings
Refactors telemetry configuration to use a nested `telemetry` object in `settings.json`, for example:

```json
{
  "telemetry": {
    "enabled": true,
    "target": "gcp"
    "log-prompts": "true"
  },
  "sandbox": false
}
```

The above includes
- Centralized telemetry settings under a `telemetry` object in `settings.json`.
- CLI flags for the `gemini` command to override all telemetry sub-settings:
    - `--telemetry` / `--no-telemetry`
    - `--telemetry-target <local|gcp>`
    - `--telemetry-otlp-endpoint <URL>`
    - `--telemetry-log-prompts` / `--no-telemetry-log-prompts`
- Updates `packages/cli/src/config/config.ts` and `packages/core/src/config/config.ts` to read from the new settings structure and respect the new CLI flags.
- Modifies `scripts/handle-telemetry.js`, `scripts/local_telemetry.js`, and `scripts/telemetry_utils.js` to align with the new settings structure.
- Updates `docs/core/telemetry.md` to reflect the new settings structure, CLI flags, and order of precedence.
- Renames `logUserPromptsEnabled` to `logPrompts` for brevity.

### `npm run telemetry`

Add a new `npm run telemetry` command that uses `scripts/telemetry.js`, automates the entire process of setting up a local and GCP telemetry pipelines, including configuring the necessary settings in the `.gemini/settings.json` workspace file and installing required binaries (e.g. `otelcol-contrib`).

---
```shell
$ npm run telemetry -- --target=gcp

> gemini-cli@0.1.0 telemetry
> node scripts/telemetry.js --target=gcp

⚙️  Using command-line target: gcp
🚀 Running telemetry script for target: gcp.
 Starting Local Telemetry Exporter for Google Cloud 
⚙️  Enabled telemetry in workspace settings.
🔧 Set telemetry OTLP endpoint to http://localhost:4317.
🎯 Set telemetry target to gcp.
 Workspace settings updated.
 Using Google Cloud Project ID: foo-bar

🔑 Please ensure you are authenticated with Google Cloud:
  - Run `gcloud auth application-default login` OR ensure `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid service account key.
  - The account needs "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer" roles.
 otelcol-contrib already exists at /Users/jerop/github/gemini-cli/.gemini/otel/bin/otelcol-contrib
🧹 Cleaning up old processes and logs...
 Deleted old GCP collector log.
📄 Wrote OTEL collector config to /Users/jerop/github/gemini-cli/.gemini/otel/collector-gcp.yaml
🚀 Starting OTEL collector for GCP... Logs: /Users/jerop/github/gemini-cli/.gemini/otel/collector-gcp.log
 Waiting for OTEL collector to start (PID: 17013)...
 OTEL collector started successfully on port 4317.

 Local OTEL collector for GCP is running.

🚀 To send telemetry, run the Gemini CLI in a separate terminal window.

📄 Collector logs are being written to: /Users/jerop/github/gemini-cli/.gemini/otel/collector-gcp.log

📊 View your telemetry data in Google Cloud Console:
   - Logs: https://console.cloud.google.com/logs/query;query=logName%3D%22projects%2Ffoo-bar%2Flogs%2Fgemini_cli%22?project=foo-bar
   - Metrics: https://console.cloud.google.com/monitoring/metrics-explorer?project=foo-bar
   - Traces: https://console.cloud.google.com/traces/list?project=foo-bar

Press Ctrl+C to exit.
^C
👋 Shutting down...
⚙️  Disabled telemetry in workspace settings.
🔧 Cleared telemetry OTLP endpoint.
🎯 Cleared telemetry target.
 Workspace settings updated.
🛑 Stopping otelcol-contrib (PID: 17013)...
 otelcol-contrib stopped.
```
2025-06-15 00:47:32 -04:00

232 lines
8.2 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Config, ConfigParameters } from './config.js';
import * as path from 'path';
import { setGeminiMdFilename as mockSetGeminiMdFilename } from '../tools/memoryTool.js';
import {
DEFAULT_TELEMETRY_TARGET,
DEFAULT_OTLP_ENDPOINT,
} from '../telemetry/index.js';
// Mock dependencies that might be called during Config construction or createServerConfig
vi.mock('../tools/tool-registry', () => {
const ToolRegistryMock = vi.fn();
ToolRegistryMock.prototype.registerTool = vi.fn();
ToolRegistryMock.prototype.discoverTools = vi.fn();
ToolRegistryMock.prototype.getAllTools = vi.fn(() => []); // Mock methods if needed
ToolRegistryMock.prototype.getTool = vi.fn();
ToolRegistryMock.prototype.getFunctionDeclarations = vi.fn(() => []);
return { ToolRegistry: ToolRegistryMock };
});
// Mock individual tools if their constructors are complex or have side effects
vi.mock('../tools/ls');
vi.mock('../tools/read-file');
vi.mock('../tools/grep');
vi.mock('../tools/glob');
vi.mock('../tools/edit');
vi.mock('../tools/shell');
vi.mock('../tools/write-file');
vi.mock('../tools/web-fetch');
vi.mock('../tools/read-many-files');
vi.mock('../tools/memoryTool', () => ({
MemoryTool: vi.fn(),
setGeminiMdFilename: vi.fn(),
getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'), // Mock the original filename
DEFAULT_CONTEXT_FILENAME: 'GEMINI.md',
GEMINI_CONFIG_DIR: '.gemini',
}));
vi.mock('../telemetry/index.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('../telemetry/index.js')>();
return {
...actual,
initializeTelemetry: vi.fn(),
};
});
describe('Server Config (config.ts)', () => {
const API_KEY = 'server-api-key';
const MODEL = 'gemini-pro';
const SANDBOX = false;
const TARGET_DIR = '/path/to/target';
const DEBUG_MODE = false;
const QUESTION = 'test question';
const FULL_CONTEXT = false;
const USER_MEMORY = 'Test User Memory';
const TELEMETRY_SETTINGS = { enabled: false };
const EMBEDDING_MODEL = 'gemini-embedding';
const SESSION_ID = 'test-session-id';
const baseParams: ConfigParameters = {
cwd: '/tmp',
contentGeneratorConfig: {
apiKey: API_KEY,
model: MODEL,
},
embeddingModel: EMBEDDING_MODEL,
sandbox: SANDBOX,
targetDir: TARGET_DIR,
debugMode: DEBUG_MODE,
question: QUESTION,
fullContext: FULL_CONTEXT,
userMemory: USER_MEMORY,
telemetry: TELEMETRY_SETTINGS,
sessionId: SESSION_ID,
};
beforeEach(() => {
// Reset mocks if necessary
vi.clearAllMocks();
});
it('Config constructor should store userMemory correctly', () => {
const config = new Config(baseParams);
expect(config.getUserMemory()).toBe(USER_MEMORY);
// Verify other getters if needed
expect(config.getTargetDir()).toBe(path.resolve(TARGET_DIR)); // Check resolved path
});
it('Config constructor should default userMemory to empty string if not provided', () => {
const paramsWithoutMemory: ConfigParameters = { ...baseParams };
delete paramsWithoutMemory.userMemory;
const config = new Config(paramsWithoutMemory);
expect(config.getUserMemory()).toBe('');
});
it('Config constructor should call setGeminiMdFilename with contextFileName if provided', () => {
const contextFileName = 'CUSTOM_AGENTS.md';
const paramsWithContextFile: ConfigParameters = {
...baseParams,
contextFileName,
};
new Config(paramsWithContextFile);
expect(mockSetGeminiMdFilename).toHaveBeenCalledWith(contextFileName);
});
it('Config constructor should not call setGeminiMdFilename if contextFileName is not provided', () => {
new Config(baseParams); // baseParams does not have contextFileName
expect(mockSetGeminiMdFilename).not.toHaveBeenCalled();
});
it('should set default file filtering settings when not provided', () => {
const config = new Config(baseParams);
expect(config.getFileFilteringRespectGitIgnore()).toBe(true);
});
it('should set custom file filtering settings when provided', () => {
const paramsWithFileFiltering: ConfigParameters = {
...baseParams,
fileFilteringRespectGitIgnore: false,
};
const config = new Config(paramsWithFileFiltering);
expect(config.getFileFilteringRespectGitIgnore()).toBe(false);
});
it('Config constructor should set telemetry to true when provided as true', () => {
const paramsWithTelemetry: ConfigParameters = {
...baseParams,
telemetry: { enabled: true },
};
const config = new Config(paramsWithTelemetry);
expect(config.getTelemetryEnabled()).toBe(true);
});
it('Config constructor should set telemetry to false when provided as false', () => {
const paramsWithTelemetry: ConfigParameters = {
...baseParams,
telemetry: { enabled: false },
};
const config = new Config(paramsWithTelemetry);
expect(config.getTelemetryEnabled()).toBe(false);
});
it('Config constructor should default telemetry to default value if not provided', () => {
const paramsWithoutTelemetry: ConfigParameters = { ...baseParams };
delete paramsWithoutTelemetry.telemetry;
const config = new Config(paramsWithoutTelemetry);
expect(config.getTelemetryEnabled()).toBe(TELEMETRY_SETTINGS.enabled);
});
it('should have a getFileService method that returns FileDiscoveryService', () => {
const config = new Config(baseParams);
const fileService = config.getFileService();
expect(fileService).toBeDefined();
});
describe('Telemetry Settings', () => {
it('should return default telemetry target if not provided', () => {
const params: ConfigParameters = {
...baseParams,
telemetry: { enabled: true },
};
const config = new Config(params);
expect(config.getTelemetryTarget()).toBe(DEFAULT_TELEMETRY_TARGET);
});
it('should return provided OTLP endpoint', () => {
const endpoint = 'http://custom.otel.collector:4317';
const params: ConfigParameters = {
...baseParams,
telemetry: { enabled: true, otlpEndpoint: endpoint },
};
const config = new Config(params);
expect(config.getTelemetryOtlpEndpoint()).toBe(endpoint);
});
it('should return default OTLP endpoint if not provided', () => {
const params: ConfigParameters = {
...baseParams,
telemetry: { enabled: true },
};
const config = new Config(params);
expect(config.getTelemetryOtlpEndpoint()).toBe(DEFAULT_OTLP_ENDPOINT);
});
it('should return provided logPrompts setting', () => {
const params: ConfigParameters = {
...baseParams,
telemetry: { enabled: true, logPrompts: false },
};
const config = new Config(params);
expect(config.getTelemetryLogPromptsEnabled()).toBe(false);
});
it('should return default logPrompts setting (true) if not provided', () => {
const params: ConfigParameters = {
...baseParams,
telemetry: { enabled: true },
};
const config = new Config(params);
expect(config.getTelemetryLogPromptsEnabled()).toBe(true);
});
it('should return default logPrompts setting (true) if telemetry object is not provided', () => {
const paramsWithoutTelemetry: ConfigParameters = { ...baseParams };
delete paramsWithoutTelemetry.telemetry;
const config = new Config(paramsWithoutTelemetry);
expect(config.getTelemetryLogPromptsEnabled()).toBe(true);
});
it('should return default telemetry target if telemetry object is not provided', () => {
const paramsWithoutTelemetry: ConfigParameters = { ...baseParams };
delete paramsWithoutTelemetry.telemetry;
const config = new Config(paramsWithoutTelemetry);
expect(config.getTelemetryTarget()).toBe(DEFAULT_TELEMETRY_TARGET);
});
it('should return default OTLP endpoint if telemetry object is not provided', () => {
const paramsWithoutTelemetry: ConfigParameters = { ...baseParams };
delete paramsWithoutTelemetry.telemetry;
const config = new Config(paramsWithoutTelemetry);
expect(config.getTelemetryOtlpEndpoint()).toBe(DEFAULT_OTLP_ENDPOINT);
});
});
});