refactor(logging): Centralize console logging with debugLogger (#11590)

This commit is contained in:
Abhi
2025-10-21 16:35:22 -04:00
committed by GitHub
parent f5e07d94bd
commit b364f37655
72 changed files with 345 additions and 289 deletions
+2 -1
View File
@@ -21,6 +21,7 @@ import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
DEFAULT_GEMINI_MODEL,
type GeminiCLIExtension,
debugLogger,
} from '@google/gemini-cli-core';
import { logger } from '../utils/logger.js';
@@ -125,7 +126,7 @@ export function mergeMcpServers(
for (const extension of extensions) {
Object.entries(extension.mcpServers || {}).forEach(([key, server]) => {
if (mcpServers[key]) {
console.warn(
debugLogger.warn(
`Skipping extension MCP config for server with key "${key}" as it already exists.`,
);
return;
@@ -16,7 +16,7 @@ import type { AddressInfo } from 'node:net';
import { createApp, updateCoderAgentCardUrl } from './app.js';
import type { TaskMetadata } from '../types.js';
import { createMockConfig } from '../utils/testing_utils.js';
import type { Config } from '@google/gemini-cli-core';
import { debugLogger, type Config } from '@google/gemini-cli-core';
// Mock the logger to avoid polluting test output
// Comment out to help debug
@@ -115,7 +115,7 @@ describe('Agent Server Endpoints', () => {
try {
fs.rmSync(testWorkspace, { recursive: true, force: true });
} catch (e) {
console.warn(`Could not remove temp dir '${testWorkspace}':`, e);
debugLogger.warn(`Could not remove temp dir '${testWorkspace}':`, e);
}
}
});
@@ -5,7 +5,11 @@
*/
import { type CommandModule } from 'yargs';
import { FatalConfigError, getErrorMessage } from '@google/gemini-cli-core';
import {
debugLogger,
FatalConfigError,
getErrorMessage,
} from '@google/gemini-cli-core';
import { enableExtension } from '../../config/extension.js';
import { SettingScope } from '../../config/settings.js';
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
@@ -28,11 +32,11 @@ export function handleEnable(args: EnableArgs) {
enableExtension(args.name, SettingScope.User, extensionEnablementManager);
}
if (args.scope) {
console.log(
debugLogger.log(
`Extension "${args.name}" successfully enabled for scope "${args.scope}".`,
);
} else {
console.log(
debugLogger.log(
`Extension "${args.name}" successfully enabled in all scopes.`,
);
}
+16 -26
View File
@@ -4,20 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
vi,
describe,
it,
expect,
beforeEach,
afterEach,
type Mock,
type MockInstance,
} from 'vitest';
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
import { listMcpServers } from './list.js';
import { loadSettings } from '../../config/settings.js';
import { ExtensionStorage, loadExtensions } from '../../config/extension.js';
import { createTransport } from '@google/gemini-cli-core';
import { createTransport, debugLogger } from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
vi.mock('../../config/settings.js', () => ({
@@ -43,6 +34,12 @@ vi.mock('@google/gemini-cli-core', () => ({
})),
GEMINI_DIR: '.gemini',
getErrorMessage: (e: unknown) => (e instanceof Error ? e.message : String(e)),
debugLogger: {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
vi.mock('@modelcontextprotocol/sdk/client/index.js');
@@ -64,15 +61,12 @@ interface MockTransport {
}
describe('mcp list command', () => {
let consoleSpy: MockInstance;
let mockClient: MockClient;
let mockTransport: MockTransport;
beforeEach(() => {
vi.resetAllMocks();
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
mockTransport = { close: vi.fn() };
mockClient = {
connect: vi.fn(),
@@ -86,16 +80,12 @@ describe('mcp list command', () => {
mockedGetUserExtensionsDir.mockReturnValue('/mocked/extensions/dir');
});
afterEach(() => {
consoleSpy.mockRestore();
});
it('should display message when no servers configured', async () => {
mockedLoadSettings.mockReturnValue({ merged: { mcpServers: {} } });
await listMcpServers();
expect(consoleSpy).toHaveBeenCalledWith('No MCP servers configured.');
expect(debugLogger.log).toHaveBeenCalledWith('No MCP servers configured.');
});
it('should display different server types with connected status', async () => {
@@ -114,18 +104,18 @@ describe('mcp list command', () => {
await listMcpServers();
expect(consoleSpy).toHaveBeenCalledWith('Configured MCP servers:\n');
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith('Configured MCP servers:\n');
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'stdio-server: /path/to/server arg1 (stdio) - Connected',
),
);
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'sse-server: https://example.com/sse (sse) - Connected',
),
);
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'http-server: https://example.com/http (http) - Connected',
),
@@ -145,7 +135,7 @@ describe('mcp list command', () => {
await listMcpServers();
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'test-server: /test/server (stdio) - Disconnected',
),
@@ -171,12 +161,12 @@ describe('mcp list command', () => {
await listMcpServers();
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'config-server: /config/server (stdio) - Connected',
),
);
expect(consoleSpy).toHaveBeenCalledWith(
expect(debugLogger.log).toHaveBeenCalledWith(
expect.stringContaining(
'extension-server (from test-extension): /ext/server (stdio) - Connected',
),
+8 -4
View File
@@ -8,7 +8,11 @@
import type { CommandModule } from 'yargs';
import { loadSettings } from '../../config/settings.js';
import type { MCPServerConfig } from '@google/gemini-cli-core';
import { MCPServerStatus, createTransport } from '@google/gemini-cli-core';
import {
MCPServerStatus,
createTransport,
debugLogger,
} from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { loadExtensions } from '../../config/extension.js';
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
@@ -84,11 +88,11 @@ export async function listMcpServers(): Promise<void> {
const serverNames = Object.keys(mcpServers);
if (serverNames.length === 0) {
console.log('No MCP servers configured.');
debugLogger.log('No MCP servers configured.');
return;
}
console.log('Configured MCP servers:\n');
debugLogger.log('Configured MCP servers:\n');
for (const serverName of serverNames) {
const server = mcpServers[serverName];
@@ -125,7 +129,7 @@ export async function listMcpServers(): Promise<void> {
serverInfo += `${server.command} ${server.args?.join(' ') || ''} (stdio)`;
}
console.log(`${statusIndicator} ${serverInfo} - ${statusText}`);
debugLogger.log(`${statusIndicator} ${serverInfo} - ${statusText}`);
}
}
+3 -2
View File
@@ -7,6 +7,7 @@
// File for 'gemini mcp remove' command
import type { CommandModule } from 'yargs';
import { loadSettings, SettingScope } from '../../config/settings.js';
import { debugLogger } from '@google/gemini-cli-core';
async function removeMcpServer(
name: string,
@@ -23,7 +24,7 @@ async function removeMcpServer(
const mcpServers = existingSettings.mcpServers || {};
if (!mcpServers[name]) {
console.log(`Server "${name}" not found in ${scope} settings.`);
debugLogger.log(`Server "${name}" not found in ${scope} settings.`);
return;
}
@@ -31,7 +32,7 @@ async function removeMcpServer(
settings.setValue(settingsScope, 'mcpServers', mcpServers);
console.log(`Server "${name}" removed from ${scope} settings.`);
debugLogger.log(`Server "${name}" removed from ${scope} settings.`);
}
export const removeCommand: CommandModule = {
-2
View File
@@ -1073,8 +1073,6 @@ describe('Settings Loading and Merging', () => {
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
const e = settings.user.settings.model?.chatCompression;
console.log(e);
expect(settings.user.settings.model?.chatCompression).toEqual({
contextPercentageThreshold: 0.5,
+2 -1
View File
@@ -10,6 +10,7 @@ import { homedir, platform } from 'node:os';
import * as dotenv from 'dotenv';
import process from 'node:process';
import {
debugLogger,
FatalConfigError,
GEMINI_DIR,
getErrorMessage,
@@ -753,7 +754,7 @@ export function migrateDeprecatedSettings(
const processScope = (scope: SettingScope) => {
const settings = loadedSettings.forScope(scope).settings;
if (settings.extensions?.disabled) {
console.log(
debugLogger.log(
`Migrating deprecated extensions.disabled settings from ${scope} settings...`,
);
const extensionEnablementManager = new ExtensionEnablementManager();
+2 -1
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import type { SlashCommand } from '../ui/commands/types.js';
import type { ICommandLoader } from './types.js';
@@ -57,7 +58,7 @@ export class CommandService {
if (result.status === 'fulfilled') {
allCommands.push(...result.value);
} else {
console.debug('A command loader failed:', result.reason);
debugLogger.debug('A command loader failed:', result.reason);
}
}
+4 -3
View File
@@ -42,6 +42,7 @@ import {
AuthType,
clearCachedCredentialFile,
ShellExecutionService,
debugLogger,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
@@ -389,7 +390,7 @@ export const AppContainer = (props: AppContainerProps) => {
config.isBrowserLaunchSuppressed()
) {
await runExitCleanup();
console.log(`
debugLogger.log(`
----------------------------------------------------------------
Logging in with Google... Please restart Gemini CLI to continue.
----------------------------------------------------------------
@@ -558,7 +559,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
Date.now(),
);
if (config.getDebugMode()) {
console.log(
debugLogger.log(
`[DEBUG] Refreshed memory content in config: ${memoryContent.substring(
0,
200,
@@ -933,7 +934,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
(key: Key) => {
// Debug log keystrokes if enabled
if (settings.merged.general?.debugKeystrokeLogging) {
console.log('[DEBUG] Keystroke:', JSON.stringify(key));
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
}
if (keyMatchers[Command.QUIT](key)) {
+2 -1
View File
@@ -14,6 +14,7 @@ import { SettingScope } from '../../config/settings.js';
import {
AuthType,
clearCachedCredentialFile,
debugLogger,
type Config,
} from '@google/gemini-cli-core';
import { useKeypress } from '../hooks/useKeypress.js';
@@ -108,7 +109,7 @@ export function AuthDialog({
config.isBrowserLaunchSuppressed()
) {
runExitCleanup();
console.log(
debugLogger.log(
`
----------------------------------------------------------------
Logging in with Google... Please restart Gemini CLI to continue.
+2 -2
View File
@@ -6,7 +6,7 @@
import { useState, useEffect, useCallback } from 'react';
import type { LoadedSettings } from '../../config/settings.js';
import { AuthType, type Config } from '@google/gemini-cli-core';
import { AuthType, debugLogger, type Config } from '@google/gemini-cli-core';
import { getErrorMessage } from '@google/gemini-cli-core';
import { AuthState } from '../types.js';
import { validateAuthMethod } from '../../config/auth.js';
@@ -80,7 +80,7 @@ export const useAuthCommand = (settings: LoadedSettings, config: Config) => {
try {
await config.refreshAuth(authType);
console.log(`Authenticated via "${authType}".`);
debugLogger.log(`Authenticated via "${authType}".`);
setAuthError(null);
setAuthState(AuthState.Authenticated);
} catch (e) {
+2 -1
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import { copyToClipboard } from '../utils/commandUtils.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
@@ -45,7 +46,7 @@ export const copyCommand: SlashCommand = {
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.debug(message);
debugLogger.debug(message);
return {
type: 'message',
@@ -20,6 +20,7 @@ import {
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
import { getUrlOpenCommand } from '../../ui/utils/commandUtils.js';
import { debugLogger } from '@google/gemini-cli-core';
export const GITHUB_WORKFLOW_PATHS = [
'gemini-dispatch/gemini-dispatch.yml',
@@ -84,7 +85,7 @@ export async function updateGitignore(gitRepoRoot: string): Promise<void> {
}
}
} catch (error) {
console.debug('Failed to update .gitignore:', error);
debugLogger.debug('Failed to update .gitignore:', error);
// Continue without failing the whole command
}
}
@@ -109,7 +110,7 @@ export const setupGithubCommand: SlashCommand = {
try {
gitRepoRoot = getGitRepoRoot();
} catch (_error) {
console.debug(`Failed to get git repo root:`, _error);
debugLogger.debug(`Failed to get git repo root:`, _error);
throw new Error(
'Unable to determine the GitHub repository. /setup-github must be run from a git repository.',
);
@@ -125,7 +126,7 @@ export const setupGithubCommand: SlashCommand = {
try {
await fs.promises.mkdir(githubWorkflowsDir, { recursive: true });
} catch (_error) {
console.debug(
debugLogger.debug(
`Failed to create ${githubWorkflowsDir} directory:`,
_error,
);
@@ -37,6 +37,7 @@ import {
type SettingsValue,
TOGGLE_TYPES,
} from '../../config/settingsSchema.js';
import { debugLogger } from '@google/gemini-cli-core';
interface SettingsDialogProps {
settings: LoadedSettings;
@@ -162,7 +163,7 @@ export function SettingsDialog({
newValue,
currentScopeSettings,
);
console.log(
debugLogger.log(
`[DEBUG SettingsDialog] Saving ${key} immediately with value:`,
newValue,
);
@@ -207,7 +208,7 @@ export function SettingsDialog({
setModifiedSettings((prev) => {
const updated = new Set(prev).add(key);
const needsRestart = hasRestartRequiredSettings(updated);
console.log(
debugLogger.log(
`[DEBUG SettingsDialog] Modified settings:`,
Array.from(updated),
'Needs restart:',
@@ -9,6 +9,7 @@ import * as path from 'node:path';
import type { PartListUnion, PartUnion } from '@google/genai';
import type { AnyToolInvocation, Config } from '@google/gemini-cli-core';
import {
debugLogger,
getErrorMessage,
isNodeError,
unescapePath,
@@ -372,7 +373,7 @@ export async function handleAtCommand({
}
const message = `Ignored ${totalIgnored} files:\n${messages.join('\n')}`;
console.log(message);
debugLogger.log(message);
onDebugMessage(message);
}
@@ -222,8 +222,6 @@ export const useShellCommandProcessor = (
shellExecutionConfig,
);
console.log(terminalHeight, terminalWidth);
executionPid = pid;
if (pid) {
setActiveShellPtyId(pid);
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import { useState, useCallback } from 'react';
interface Logger {
@@ -69,7 +70,10 @@ export function useInputHistoryStore(): UseInputHistoryStoreReturn {
setIsInitialized(true);
} catch (error) {
// Start with empty history even if logger initialization fails
console.warn('Failed to initialize input history from logger:', error);
debugLogger.warn(
'Failed to initialize input history from logger:',
error,
);
setPastSessionMessages([]);
recalculateHistory([], []);
setIsInitialized(true);
@@ -20,7 +20,7 @@ import type {
Status as CoreStatus,
EditorType,
} from '@google/gemini-cli-core';
import { CoreToolScheduler } from '@google/gemini-cli-core';
import { CoreToolScheduler, debugLogger } from '@google/gemini-cli-core';
import { useCallback, useState, useMemo } from 'react';
import type {
HistoryItemToolGroup,
@@ -198,7 +198,7 @@ function mapCoreStatusToDisplayStatus(coreStatus: CoreStatus): ToolCallStatus {
return ToolCallStatus.Pending;
default: {
const exhaustiveCheck: never = coreStatus;
console.warn(`Unknown core status encountered: ${exhaustiveCheck}`);
debugLogger.warn(`Unknown core status encountered: ${exhaustiveCheck}`);
return ToolCallStatus.Error;
}
}
@@ -6,7 +6,7 @@
import type { Message } from '../types.js';
import { MessageType } from '../types.js';
import type { Config } from '@google/gemini-cli-core';
import { debugLogger, type Config } from '@google/gemini-cli-core';
import type { LoadedSettings } from '../../config/settings.js';
export function createShowMemoryAction(
@@ -27,7 +27,7 @@ export function createShowMemoryAction(
const debugMode = config.getDebugMode();
if (debugMode) {
console.log('[DEBUG] Show Memory command invoked.');
debugLogger.log('[DEBUG] Show Memory command invoked.');
}
const currentMemory = config.getUserMemory();
@@ -38,10 +38,10 @@ export function createShowMemoryAction(
: [contextFileName];
if (debugMode) {
console.log(
debugLogger.log(
`[DEBUG] Showing memory. Content from config.getUserMemory() (first 200 chars): ${currentMemory.substring(0, 200)}...`,
);
console.log(`[DEBUG] Number of context files loaded: ${fileCount}`);
debugLogger.log(`[DEBUG] Number of context files loaded: ${fileCount}`);
}
if (fileCount > 0) {
@@ -12,6 +12,7 @@ import {
type CommandContext,
type SlashCommand,
} from '../commands/types.js';
import { debugLogger } from '@google/gemini-cli-core';
// Type alias for improved type safety based on actual fzf result structure
type FzfCommandResult = {
@@ -189,7 +190,7 @@ function useCommandSuggestions(
// Safety check: ensure leafCommand and completion exist
if (!leafCommand?.completion) {
console.warn(
debugLogger.warn(
'Attempted argument completion without completion function',
);
return;
+2 -1
View File
@@ -8,6 +8,7 @@ import { useCallback, useReducer, useEffect } from 'react';
import type { Key } from './useKeypress.js';
import type { TextBuffer } from '../components/shared/text-buffer.js';
import { useVimMode } from '../contexts/VimModeContext.js';
import { debugLogger } from '@google/gemini-cli-core';
export type VimMode = 'NORMAL' | 'INSERT';
@@ -394,7 +395,7 @@ export function useVim(buffer: TextBuffer, onSubmit?: (value: string) => void) {
normalizedKey = normalizeKey(key);
} catch (error) {
// Handle malformed key inputs gracefully
console.warn('Malformed key input in vim mode:', key, error);
debugLogger.warn('Malformed key input in vim mode:', key, error);
return false;
}
+3 -1
View File
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
// Mapping from common CSS color names (lowercase) to hex codes (lowercase)
// Excludes names directly supported by Ink
export const CSS_NAME_TO_HEX_MAP: Readonly<Record<string, string>> = {
@@ -224,7 +226,7 @@ export function resolveColor(colorValue: string): string | undefined {
}
// 4. Could not resolve
console.warn(
debugLogger.warn(
`[ColorUtils] Could not resolve color "${colorValue}" to an Ink-compatible format.`,
);
return undefined;
+11 -7
View File
@@ -25,6 +25,7 @@ import { ANSI } from './ansi.js';
import { ANSILight } from './ansi-light.js';
import { NoColorTheme } from './no-color.js';
import process from 'node:process';
import { debugLogger } from '@google/gemini-cli-core';
export interface ThemeDisplay {
name: string;
@@ -75,7 +76,7 @@ class ThemeManager {
const validation = validateCustomTheme(customThemeConfig);
if (validation.isValid) {
if (validation.warning) {
console.warn(`Theme "${name}": ${validation.warning}`);
debugLogger.warn(`Theme "${name}": ${validation.warning}`);
}
const themeWithDefaults: CustomTheme = {
...DEFAULT_THEME.colors,
@@ -88,10 +89,10 @@ class ThemeManager {
const theme = createCustomTheme(themeWithDefaults);
this.customThemes.set(name, theme);
} catch (error) {
console.warn(`Failed to load custom theme "${name}":`, error);
debugLogger.warn(`Failed to load custom theme "${name}":`, error);
}
} else {
console.warn(`Invalid custom theme "${name}": ${validation.error}`);
debugLogger.warn(`Invalid custom theme "${name}": ${validation.error}`);
}
}
// If the current active theme is a custom theme, keep it if still valid
@@ -246,7 +247,7 @@ class ThemeManager {
// 2. Perform security check.
const homeDir = path.resolve(os.homedir());
if (!canonicalPath.startsWith(homeDir)) {
console.warn(
debugLogger.warn(
`Theme file at "${themePath}" is outside your home directory. ` +
`Only load themes from trusted sources.`,
);
@@ -259,14 +260,14 @@ class ThemeManager {
const validation = validateCustomTheme(customThemeConfig);
if (!validation.isValid) {
console.warn(
debugLogger.warn(
`Invalid custom theme from file "${themePath}": ${validation.error}`,
);
return undefined;
}
if (validation.warning) {
console.warn(`Theme from "${themePath}": ${validation.warning}`);
debugLogger.warn(`Theme from "${themePath}": ${validation.warning}`);
}
// 4. Create and cache the theme.
@@ -286,7 +287,10 @@ class ThemeManager {
if (
!(error instanceof Error && 'code' in error && error.code === 'ENOENT')
) {
console.warn(`Could not load theme from file "${themePath}":`, error);
debugLogger.warn(
`Could not load theme from file "${themePath}":`,
error,
);
}
return undefined;
}
+2 -1
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import type { SpawnOptions } from 'node:child_process';
import { spawn } from 'node:child_process';
@@ -165,7 +166,7 @@ export const getUrlOpenCommand = (): string => {
default:
// Default to xdg-open, which appears to be supported for the less popular operating systems.
openCmd = 'xdg-open';
console.warn(
debugLogger.warn(
`Unknown platform: ${process.platform}. Attempting to open URLs with: ${openCmd}.`,
);
break;
+3 -2
View File
@@ -30,6 +30,7 @@ import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { isKittyProtocolEnabled } from './kittyProtocolDetector.js';
import { VSCODE_SHIFT_ENTER_SEQUENCE } from './platformConstants.js';
import { debugLogger } from '@google/gemini-cli-core';
const execAsync = promisify(exec);
@@ -88,7 +89,7 @@ async function detectTerminal(): Promise<SupportedTerminal | null> {
return 'vscode';
} catch (error) {
// Continue detection even if process check fails
console.debug('Parent process detection failed:', error);
debugLogger.debug('Parent process detection failed:', error);
}
}
@@ -103,7 +104,7 @@ async function backupFile(filePath: string): Promise<void> {
await fs.copyFile(filePath, backupPath);
} catch (error) {
// Log backup errors but continue with operation
console.warn(`Failed to create backup of ${filePath}:`, error);
debugLogger.warn(`Failed to create backup of ${filePath}:`, error);
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ import updateNotifier from 'update-notifier';
import semver from 'semver';
import { getPackageJson } from '../../utils/package.js';
import type { LoadedSettings } from '../../config/settings.js';
import { debugLogger } from '@google/gemini-cli-core';
export const FETCH_TIMEOUT_MS = 2000;
@@ -101,7 +102,7 @@ export async function checkForUpdates(
return null;
} catch (e) {
console.warn('Failed to check for updates: ' + e);
debugLogger.warn('Failed to check for updates: ' + e);
return null;
}
}
+6 -2
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
import { execSync } from 'node:child_process';
import { ProxyAgent } from 'undici';
@@ -24,7 +25,7 @@ export const isGitHubRepository = (): boolean => {
return pattern.test(remotes);
} catch (_error) {
// If any filesystem error occurs, assume not a git repo
console.debug(`Failed to get git remote:`, _error);
debugLogger.debug(`Failed to get git remote:`, _error);
return false;
}
};
@@ -83,7 +84,10 @@ export const getLatestGitHubRelease = async (
}
return releaseTag;
} catch (_error) {
console.debug(`Failed to determine latest run-gemini-cli release:`, _error);
debugLogger.debug(
`Failed to determine latest run-gemini-cli release:`,
_error,
);
throw new Error(
`Unable to determine the latest run-gemini-cli release on GitHub.`,
);
@@ -9,10 +9,16 @@ import { getInstallationInfo, PackageManager } from './installationInfo.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as childProcess from 'node:child_process';
import { isGitRepository } from '@google/gemini-cli-core';
import { isGitRepository, debugLogger } from '@google/gemini-cli-core';
vi.mock('@google/gemini-cli-core', () => ({
isGitRepository: vi.fn(),
debugLogger: {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
vi.mock('fs', async (importOriginal) => {
@@ -59,7 +65,6 @@ describe('getInstallationInfo', () => {
});
it('should return UNKNOWN and log error if realpathSync fails', () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
process.argv[1] = '/path/to/cli';
const error = new Error('realpath failed');
mockedRealPathSync.mockImplementation(() => {
@@ -69,8 +74,7 @@ describe('getInstallationInfo', () => {
const info = getInstallationInfo(projectRoot, false);
expect(info.packageManager).toBe(PackageManager.UNKNOWN);
expect(consoleSpy).toHaveBeenCalledWith(error);
consoleSpy.mockRestore();
expect(debugLogger.log).toHaveBeenCalledWith(error);
});
it('should detect running from a local git clone', () => {
+2 -2
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { isGitRepository } from '@google/gemini-cli-core';
import { debugLogger, isGitRepository } from '@google/gemini-cli-core';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as childProcess from 'node:child_process';
@@ -174,7 +174,7 @@ export function getInstallationInfo(
: 'Installed with npm. Attempting to automatically update now...',
};
} catch (error) {
console.log(error);
debugLogger.log(error);
return { packageManager: PackageManager.UNKNOWN, isGlobal: false };
}
}
+3 -1
View File
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { debugLogger } from '@google/gemini-cli-core';
export async function readStdin(): Promise<string> {
const MAX_STDIN_SIZE = 8 * 1024 * 1024; // 8MB
return new Promise((resolve, reject) => {
@@ -30,7 +32,7 @@ export async function readStdin(): Promise<string> {
if (totalSize + chunk.length > MAX_STDIN_SIZE) {
const remainingSize = MAX_STDIN_SIZE - totalSize;
data += chunk.slice(0, remainingSize);
console.warn(
debugLogger.warn(
`Warning: stdin input truncated to ${MAX_STDIN_SIZE} bytes.`,
);
process.stdin.destroy(); // Stop reading further
+17 -13
View File
@@ -14,7 +14,11 @@ import { quote, parse } from 'shell-quote';
import { USER_SETTINGS_DIR } from '../config/settings.js';
import { promisify } from 'node:util';
import type { Config, SandboxConfig } from '@google/gemini-cli-core';
import { FatalSandboxError, GEMINI_DIR } from '@google/gemini-cli-core';
import {
debugLogger,
FatalSandboxError,
GEMINI_DIR,
} from '@google/gemini-cli-core';
import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
import { randomBytes } from 'node:crypto';
@@ -92,7 +96,7 @@ async function shouldUseCurrentUserInSandbox(): Promise<boolean> {
} catch (_err) {
// Silently ignore if /etc/os-release is not found or unreadable.
// The default (false) will be applied in this case.
console.warn(
debugLogger.warn(
'Warning: Could not read /etc/os-release to auto-detect Debian/Ubuntu for UID/GID default.',
);
}
@@ -301,7 +305,7 @@ export async function start_sandbox(
});
// install handlers to stop proxy on exit/signal
const stopProxy = () => {
console.log('stopping proxy ...');
debugLogger.log('stopping proxy ...');
if (proxyProcess?.pid) {
process.kill(-proxyProcess.pid, 'SIGTERM');
}
@@ -325,7 +329,7 @@ export async function start_sandbox(
`Proxy command '${proxyCommand}' exited with code ${code}, signal ${signal}`,
);
});
console.log('waiting for proxy to start ...');
debugLogger.log('waiting for proxy to start ...');
await execAsync(
`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`,
);
@@ -559,7 +563,7 @@ export async function start_sandbox(
containerName = `gemini-cli-integration-test-${randomBytes(4).toString(
'hex',
)}`;
console.log(`ContainerName: ${containerName}`);
debugLogger.log(`ContainerName: ${containerName}`);
} else {
let index = 0;
const containerNameCheck = execSync(
@@ -571,7 +575,7 @@ export async function start_sandbox(
index++;
}
containerName = `${imageName}-${index}`;
console.log(`ContainerName (regular): ${containerName}`);
debugLogger.log(`ContainerName (regular): ${containerName}`);
}
args.push('--name', containerName, '--hostname', containerName);
@@ -773,7 +777,7 @@ export async function start_sandbox(
});
// install handlers to stop proxy on exit/signal
const stopProxy = () => {
console.log('stopping proxy container ...');
debugLogger.log('stopping proxy container ...');
execSync(`${config.command} rm -f ${SANDBOX_PROXY_NAME}`);
};
process.on('exit', stopProxy);
@@ -795,7 +799,7 @@ export async function start_sandbox(
`Proxy container command '${proxyContainerCommand}' exited with code ${code}, signal ${signal}`,
);
});
console.log('waiting for proxy to start ...');
debugLogger.log('waiting for proxy to start ...');
await execAsync(
`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`,
);
@@ -821,7 +825,7 @@ export async function start_sandbox(
sandboxProcess?.on('close', (code, signal) => {
process.stdin.resume();
if (code !== 0 && code !== null) {
console.log(
debugLogger.log(
`Sandbox process exited with code: ${code}, signal: ${signal}`,
);
}
@@ -847,7 +851,7 @@ async function imageExists(sandbox: string, image: string): Promise<boolean> {
}
checkProcess.on('error', (err) => {
console.warn(
debugLogger.warn(
`Failed to start '${sandbox}' command for image check: ${err.message}`,
);
resolve(false);
@@ -882,7 +886,7 @@ async function pullImage(sandbox: string, image: string): Promise<boolean> {
};
const onError = (err: Error) => {
console.warn(
debugLogger.warn(
`Failed to start '${sandbox} pull ${image}' command: ${err.message}`,
);
cleanup();
@@ -895,7 +899,7 @@ async function pullImage(sandbox: string, image: string): Promise<boolean> {
cleanup();
resolve(true);
} else {
console.warn(
debugLogger.warn(
`Failed to pull image ${image}. '${sandbox} pull ${image}' exited with code ${code}.`,
);
if (stderrData.trim()) {
@@ -953,7 +957,7 @@ async function ensureSandboxImageIsPresent(
console.info(`Sandbox image ${image} is now available after pulling.`);
return true;
} else {
console.warn(
debugLogger.warn(
`Sandbox image ${image} still not found after a pull attempt. This might indicate an issue with the image name or registry, or the pull command reported success but failed to make the image available.`,
);
return false;
+4 -4
View File
@@ -6,7 +6,7 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { type Config } from '@google/gemini-cli-core';
import { debugLogger, type Config } from '@google/gemini-cli-core';
import type { Settings, SessionRetentionSettings } from '../config/settings.js';
import { getAllSessionFiles, type SessionFileEntry } from './sessionUtils.js';
@@ -88,11 +88,11 @@ export async function cleanupExpiredSessions(
if (config.getDebugMode()) {
if (sessionToDelete.sessionInfo === null) {
console.debug(
debugLogger.debug(
`Deleted corrupted session file: ${sessionToDelete.fileName}`,
);
} else {
console.debug(
debugLogger.debug(
`Deleted expired session: ${sessionToDelete.sessionInfo.id} (${sessionToDelete.sessionInfo.lastUpdated})`,
);
}
@@ -125,7 +125,7 @@ export async function cleanupExpiredSessions(
result.skipped = result.scanned - result.deleted - result.failed;
if (config.getDebugMode() && result.deleted > 0) {
console.debug(
debugLogger.debug(
`Session cleanup: deleted ${result.deleted}, skipped ${result.skipped}, failed ${result.failed}`,
);
}
@@ -53,10 +53,7 @@ describe('validateNonInterActiveAuth', () => {
throw new Error(`process.exit(${code}) called`);
});
vi.spyOn(auth, 'validateAuthMethod').mockReturnValue(null);
refreshAuthMock = vi.fn().mockImplementation(async () => {
console.log('DEBUG: refreshAuthMock called');
return 'refreshed';
});
refreshAuthMock = vi.fn().mockImplementation(async () => 'refreshed');
mockSettings = {
system: { path: '', settings: {} },
systemDefaults: { path: '', settings: {} },
@@ -30,6 +30,7 @@ import {
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
DEFAULT_GEMINI_FLASH_MODEL,
debugLogger,
} from '@google/gemini-cli-core';
import * as acp from './acp.js';
import { AcpFileSystemService } from './fileSystemService.js';
@@ -592,7 +593,7 @@ class Session {
const reason = respectGitIgnore
? 'git-ignored and will be skipped'
: 'ignored by custom patterns';
console.warn(`Path ${pathName} is ${reason}.`);
debugLogger.warn(`Path ${pathName} is ${reason}.`);
continue;
}
let currentPathSpec = pathName;
@@ -739,7 +740,7 @@ class Session {
if (pathSpecsToRead.length === 0 && embeddedContext.length === 0) {
// Fallback for lone "@" or completely invalid @-commands resulting in empty initialQueryText
console.warn('No valid file paths found in @ commands to read.');
debugLogger.warn('No valid file paths found in @ commands to read.');
return [{ text: initialQueryText }];
}
@@ -802,7 +803,7 @@ class Session {
}
}
} else {
console.warn(
debugLogger.warn(
'read_many_files tool returned no content or empty content.',
);
}
@@ -855,7 +856,7 @@ class Session {
debug(msg: string) {
if (this.config.getDebugMode()) {
console.warn(msg);
debugLogger.warn(msg);
}
}
}
+2 -1
View File
@@ -43,6 +43,7 @@ import { templateString } from './utils.js';
import { parseThought } from '../utils/thoughtUtils.js';
import { type z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { debugLogger } from '../utils/debugLogger.js';
/** A callback function to report on agent activity. */
export type ActivityCallback = (activity: SubagentActivityEvent) => void;
@@ -506,7 +507,7 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
if (!allowedToolNames.has(functionCall.name as string)) {
const error = `Unauthorized tool call: '${functionCall.name}' is not available to this agent.`;
console.warn(`[AgentExecutor] Blocked call: ${error}`);
debugLogger.warn(`[AgentExecutor] Blocked call: ${error}`);
syncResponseParts.push({
functionResponse: {
+4 -3
View File
@@ -8,6 +8,7 @@ import type { Config } from '../config/config.js';
import type { AgentDefinition } from './types.js';
import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
import { type z } from 'zod';
import { debugLogger } from '../utils/debugLogger.js';
/**
* Manages the discovery, loading, validation, and registration of
@@ -26,7 +27,7 @@ export class AgentRegistry {
this.loadBuiltInAgents();
if (this.config.getDebugMode()) {
console.log(
debugLogger.log(
`[AgentRegistry] Initialized with ${this.agents.size} agents.`,
);
}
@@ -72,14 +73,14 @@ export class AgentRegistry {
): void {
// Basic validation
if (!definition.name || !definition.description) {
console.warn(
debugLogger.warn(
`[AgentRegistry] Skipping invalid agent definition. Missing name or description.`,
);
return;
}
if (this.agents.has(definition.name) && this.config.getDebugMode()) {
console.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
}
this.agents.set(definition.name, definition);
+15 -12
View File
@@ -25,6 +25,7 @@ import readline from 'node:readline';
import { Storage } from '../config/storage.js';
import { OAuthCredentialStorage } from './oauth-credential-storage.js';
import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
import { debugLogger } from '../utils/debugLogger.js';
const userAccountManager = new UserAccountManager();
@@ -110,10 +111,10 @@ async function initOauthClient(
await fetchAndCacheUserInfo(client);
} catch (error) {
// Non-fatal, continue with existing auth.
console.warn('Failed to fetch user info:', getErrorMessage(error));
debugLogger.warn('Failed to fetch user info:', getErrorMessage(error));
}
}
console.log('Loaded cached credentials.');
debugLogger.log('Loaded cached credentials.');
return client;
}
@@ -122,13 +123,13 @@ async function initOauthClient(
// the identity of the user logged into Cloud Shell.
if (authType === AuthType.CLOUD_SHELL) {
try {
console.log("Attempting to authenticate via Cloud Shell VM's ADC.");
debugLogger.log("Attempting to authenticate via Cloud Shell VM's ADC.");
const computeClient = new Compute({
// We can leave this empty, since the metadata server will provide
// the service account email.
});
await computeClient.getAccessToken();
console.log('Authentication successful.');
debugLogger.log('Authentication successful.');
// Do not cache creds in this case; note that Compute client will handle its own refresh
return computeClient;
@@ -161,7 +162,7 @@ async function initOauthClient(
} else {
const webLogin = await authWithWeb(client);
console.log(
debugLogger.log(
`\n\nCode Assist login required.\n` +
`Attempting to open authentication page in your browser.\n` +
`Otherwise navigate to:\n\n${webLogin.authUrl}\n\n`,
@@ -193,7 +194,7 @@ async function initOauthClient(
`Failed to open browser: ${getErrorMessage(err)}`,
);
}
console.log('Waiting for authentication...');
debugLogger.log('Waiting for authentication...');
// Add timeout to prevent infinite waiting when browser tab gets stuck
const authTimeout = 5 * 60 * 1000; // 5 minutes timeout
@@ -236,10 +237,12 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
code_challenge: codeVerifier.codeChallenge,
state,
});
console.log('Please visit the following URL to authorize the application:');
console.log('');
console.log(authUrl);
console.log('');
debugLogger.log(
'Please visit the following URL to authorize the application:',
);
debugLogger.log('');
debugLogger.log(authUrl);
debugLogger.log('');
const code = await new Promise<string>((resolve) => {
const rl = readline.createInterface({
@@ -337,7 +340,7 @@ async function authWithWeb(client: OAuth2Client): Promise<OauthWebLogin> {
try {
await fetchAndCacheUserInfo(client);
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to retrieve Google Account ID during authentication:',
getErrorMessage(error),
);
@@ -462,7 +465,7 @@ async function loadCachedCredentials(client: OAuth2Client): Promise<boolean> {
return true;
} catch (error) {
// Log specific error for debugging, but continue trying other paths
console.debug(
debugLogger.debug(
`Failed to load credentials from ${keyFile}:`,
getErrorMessage(error),
);
+4 -3
View File
@@ -157,6 +157,7 @@ import {
DEFAULT_FILE_FILTERING_OPTIONS,
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
} from './constants.js';
import { debugLogger } from '../utils/debugLogger.js';
export type { FileFilteringOptions };
export {
@@ -1123,7 +1124,7 @@ export class Config {
// the tool registry.
const messageBusEnabled = this.getEnableMessageBusIntegration();
if (this.debugMode && messageBusEnabled) {
console.log(
debugLogger.log(
`[DEBUG] enableMessageBusIntegration setting: ${messageBusEnabled}`,
);
}
@@ -1131,7 +1132,7 @@ export class Config {
? [...args, this.getMessageBus()]
: args;
if (this.debugMode && messageBusEnabled) {
console.log(
debugLogger.log(
`[DEBUG] Registering ${className} with messageBus: ${messageBusEnabled ? 'YES' : 'NO'}`,
);
}
@@ -1206,7 +1207,7 @@ export class Config {
);
}
} else if (this.getDebugMode()) {
console.log(
debugLogger.log(
`[Config] Skipping registration of agent '${definition.name}' due to allow/exclude configuration.`,
);
}
-28
View File
@@ -1385,34 +1385,11 @@ ${JSON.stringify(
// Assert
expect(finalResult).toBeInstanceOf(Turn);
// Debug: Check how many times checkNextSpeaker was called
const callCount = mockCheckNextSpeaker.mock.calls.length;
// If infinite loop protection is working, checkNextSpeaker should be called many times
// but stop at MAX_TURNS (100). Since each recursive call should trigger checkNextSpeaker,
// we expect it to be called multiple times before hitting the limit
expect(mockCheckNextSpeaker).toHaveBeenCalled();
// The test should demonstrate that the infinite loop protection works:
// - If checkNextSpeaker is called many times (close to MAX_TURNS), it shows the loop was happening
// - If it's only called once, the recursive behavior might not be triggered
if (callCount === 0) {
throw new Error(
'checkNextSpeaker was never called - the recursive condition was not met',
);
} else if (callCount === 1) {
// This might be expected behavior if the turn has pending tool calls or other conditions prevent recursion
console.log(
'checkNextSpeaker called only once - no infinite loop occurred',
);
} else {
console.log(
`checkNextSpeaker called ${callCount} times - infinite loop protection worked`,
);
// If called multiple times, we expect it to be stopped before MAX_TURNS
expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS
}
// The stream should produce events and eventually terminate
expect(eventCount).toBeGreaterThanOrEqual(1);
expect(eventCount).toBeLessThan(200); // Should not exceed our safety limit
@@ -1537,11 +1514,6 @@ ${JSON.stringify(
// the loop should stop at MAX_TURNS (100)
expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS
expect(eventCount).toBeLessThanOrEqual(200); // Should have reasonable number of events
console.log(
`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
`${eventCount} events generated (properly bounded by MAX_TURNS)`,
);
});
it('should yield ContextWindowWillOverflow when the context window is about to overflow', async () => {
+3 -2
View File
@@ -52,6 +52,7 @@ import type { IdeContext, File } from '../ide/types.js';
import { handleFallback } from '../fallback/handler.js';
import type { RoutingContext } from '../routing/routingStrategy.js';
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
import { debugLogger } from '../utils/debugLogger.js';
export function isThinkingSupported(model: string) {
return model.startsWith('gemini-2.5') || model === DEFAULT_GEMINI_MODEL_AUTO;
@@ -341,7 +342,7 @@ My setup is complete. I will provide my first command in the next turn.
];
if (this.config.getDebugMode()) {
console.log(contextParts.join('\n'));
debugLogger.log(contextParts.join('\n'));
}
return {
contextParts,
@@ -451,7 +452,7 @@ My setup is complete. I will provide my first command in the next turn.
];
if (this.config.getDebugMode()) {
console.log(contextParts.join('\n'));
debugLogger.log(contextParts.join('\n'));
}
return {
contextParts,
+11 -10
View File
@@ -8,6 +8,7 @@ import path from 'node:path';
import { promises as fs } from 'node:fs';
import type { Content } from '@google/genai';
import type { Storage } from '../config/storage.js';
import { debugLogger } from '../utils/debugLogger.js';
const LOG_FILE_NAME = 'logs.json';
@@ -82,7 +83,7 @@ export class Logger {
const fileContent = await fs.readFile(this.logFilePath, 'utf-8');
const parsedLogs = JSON.parse(fileContent);
if (!Array.isArray(parsedLogs)) {
console.debug(
debugLogger.debug(
`Log file at ${this.logFilePath} is not a valid JSON array. Starting with empty logs.`,
);
await this._backupCorruptedLogFile('malformed_array');
@@ -102,14 +103,14 @@ export class Logger {
return [];
}
if (error instanceof SyntaxError) {
console.debug(
debugLogger.debug(
`Invalid JSON in log file ${this.logFilePath}. Backing up and starting fresh.`,
error,
);
await this._backupCorruptedLogFile('invalid_json');
return [];
}
console.debug(
debugLogger.debug(
`Failed to read or parse log file ${this.logFilePath}:`,
error,
);
@@ -122,7 +123,7 @@ export class Logger {
const backupPath = `${this.logFilePath}.${reason}.${Date.now()}.bak`;
try {
await fs.rename(this.logFilePath, backupPath);
console.debug(`Backed up corrupted log file to ${backupPath}`);
debugLogger.debug(`Backed up corrupted log file to ${backupPath}`);
} catch (_backupError) {
// If rename fails (e.g. file doesn't exist), no need to log an error here as the primary error (e.g. invalid JSON) is already handled.
}
@@ -166,7 +167,7 @@ export class Logger {
entryToAppend: LogEntry,
): Promise<LogEntry | null> {
if (!this.logFilePath) {
console.debug('Log file path not set. Cannot persist log entry.');
debugLogger.debug('Log file path not set. Cannot persist log entry.');
throw new Error('Log file path not set during update attempt.');
}
@@ -174,7 +175,7 @@ export class Logger {
try {
currentLogsOnDisk = await this._readLogFile();
} catch (readError) {
console.debug(
debugLogger.debug(
'Critical error reading log file before append:',
readError,
);
@@ -205,7 +206,7 @@ export class Logger {
);
if (entryExists) {
console.debug(
debugLogger.debug(
`Duplicate log entry detected and skipped: session ${entryToAppend.sessionId}, messageId ${entryToAppend.messageId}`,
);
this.logs = currentLogsOnDisk; // Ensure in-memory is synced with disk
@@ -223,7 +224,7 @@ export class Logger {
this.logs = currentLogsOnDisk;
return entryToAppend; // Return the successfully appended entry
} catch (error) {
console.debug('Error writing to log file:', error);
debugLogger.debug('Error writing to log file:', error);
throw error;
}
}
@@ -242,7 +243,7 @@ export class Logger {
async logMessage(type: MessageSenderType, message: string): Promise<void> {
if (!this.initialized || this.sessionId === undefined) {
console.debug(
debugLogger.debug(
'Logger not initialized or session ID missing. Cannot log message.',
);
return;
@@ -341,7 +342,7 @@ export class Logger {
const fileContent = await fs.readFile(path, 'utf-8');
const parsedContent = JSON.parse(fileContent);
if (!Array.isArray(parsedContent)) {
console.warn(
debugLogger.warn(
`Checkpoint file at ${path} is not a valid JSON array. Returning empty checkpoint.`,
);
return [];
+2 -1
View File
@@ -22,6 +22,7 @@ import { isGitRepository } from '../utils/gitUtils.js';
import { CodebaseInvestigatorAgent } from '../agents/codebase-investigator.js';
import type { Config } from '../config/config.js';
import { GEMINI_DIR } from '../utils/paths.js';
import { debugLogger } from '../utils/debugLogger.js';
export function resolvePathFromEnv(envVar?: string): {
isSwitch: boolean;
@@ -56,7 +57,7 @@ export function resolvePathFromEnv(envVar?: string): {
}
} catch (error) {
// If os.homedir() fails, we catch the error instead of crashing.
console.warn(
debugLogger.warn(
`Could not resolve home directory for path: ${trimmedEnvVar}`,
error,
);
+2 -1
View File
@@ -24,10 +24,11 @@ import * as path from 'node:path';
import { EnvHttpProxyAgent } from 'undici';
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js';
import { IDE_REQUEST_TIMEOUT_MS } from './constants.js';
import { debugLogger } from '../utils/debugLogger.js';
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...args: any[]) => console.debug('[DEBUG] [IDEClient]', ...args),
debug: (...args: any[]) => debugLogger.debug('[DEBUG] [IDEClient]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: (...args: any[]) => console.error('[ERROR] [IDEClient]', ...args),
};
+27 -20
View File
@@ -13,6 +13,7 @@ import type { OAuthToken } from './token-storage/types.js';
import { MCPOAuthTokenStorage } from './oauth-token-storage.js';
import { getErrorMessage } from '../utils/errors.js';
import { OAuthUtils } from './oauth-utils.js';
import { debugLogger } from '../utils/debugLogger.js';
export const OAUTH_DISPLAY_MESSAGE_EVENT = 'oauth-display-message' as const;
@@ -258,7 +259,9 @@ export class MCPOAuthProvider {
server.on('error', reject);
server.listen(REDIRECT_PORT, () => {
console.log(`OAuth callback server listening on port ${REDIRECT_PORT}`);
debugLogger.log(
`OAuth callback server listening on port ${REDIRECT_PORT}`,
);
});
// Timeout after 5 minutes
@@ -314,7 +317,7 @@ export class MCPOAuthProvider {
OAuthUtils.buildResourceParameter(mcpServerUrl),
);
} catch (error) {
console.warn(
debugLogger.warn(
`Could not add resource parameter: ${getErrorMessage(error)}`,
);
}
@@ -371,7 +374,7 @@ export class MCPOAuthProvider {
OAuthUtils.buildResourceParameter(resourceUrl),
);
} catch (error) {
console.warn(
debugLogger.warn(
`Could not add resource parameter: ${getErrorMessage(error)}`,
);
}
@@ -413,7 +416,7 @@ export class MCPOAuthProvider {
!contentType.includes('application/json') &&
!contentType.includes('application/x-www-form-urlencoded')
) {
console.warn(
debugLogger.warn(
`Token endpoint returned unexpected content-type: ${contentType}. ` +
`Expected application/json or application/x-www-form-urlencoded. ` +
`Will attempt to parse response.`,
@@ -493,7 +496,7 @@ export class MCPOAuthProvider {
OAuthUtils.buildResourceParameter(mcpServerUrl),
);
} catch (error) {
console.warn(
debugLogger.warn(
`Could not add resource parameter: ${getErrorMessage(error)}`,
);
}
@@ -535,7 +538,7 @@ export class MCPOAuthProvider {
!contentType.includes('application/json') &&
!contentType.includes('application/x-www-form-urlencoded')
) {
console.warn(
debugLogger.warn(
`Token refresh endpoint returned unexpected content-type: ${contentType}. ` +
`Expected application/json or application/x-www-form-urlencoded. ` +
`Will attempt to parse response.`,
@@ -593,13 +596,13 @@ export class MCPOAuthProvider {
if (events) {
events.emit(OAUTH_DISPLAY_MESSAGE_EVENT, message);
} else {
console.log(message);
debugLogger.log(message);
}
};
// If no authorization URL is provided, try to discover OAuth configuration
if (!config.authorizationUrl && mcpServerUrl) {
console.debug(`Starting OAuth for MCP server "${serverName}"…
debugLogger.debug(`Starting OAuth for MCP server "${serverName}"…
No authorization URL; using OAuth discovery`);
// First check if the server requires authentication via WWW-Authenticate header
@@ -636,7 +639,7 @@ export class MCPOAuthProvider {
}
}
} catch (error) {
console.debug(
debugLogger.debug(
`Failed to check endpoint for authentication requirements: ${getErrorMessage(error)}`,
);
}
@@ -681,7 +684,7 @@ export class MCPOAuthProvider {
const authUrl = new URL(config.authorizationUrl);
const serverUrl = `${authUrl.protocol}//${authUrl.host}`;
console.debug('→ Attempting dynamic client registration...');
debugLogger.debug('→ Attempting dynamic client registration...');
// Get the authorization server metadata for registration
const authServerMetadata =
@@ -707,7 +710,7 @@ export class MCPOAuthProvider {
config.clientSecret = clientRegistration.client_secret;
}
console.debug('✓ Dynamic client registration successful');
debugLogger.debug('✓ Dynamic client registration successful');
} else {
throw new Error(
'No client ID provided and dynamic registration not supported',
@@ -747,7 +750,7 @@ ${authUrl}
try {
await openBrowserSecurely(authUrl);
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to open browser automatically:',
getErrorMessage(error),
);
@@ -756,7 +759,9 @@ ${authUrl}
// Wait for callback
const { code } = await callbackPromise;
console.debug('✓ Authorization code received, exchanging for tokens...');
debugLogger.debug(
'✓ Authorization code received, exchanging for tokens...',
);
// Exchange code for tokens
const tokenResponse = await this.exchangeCodeForToken(
@@ -791,7 +796,7 @@ ${authUrl}
config.tokenUrl,
mcpServerUrl,
);
console.debug('✓ Authentication successful! Token saved.');
debugLogger.debug('✓ Authentication successful! Token saved.');
// Verify token was saved
const savedToken = await this.tokenStorage.getCredentials(serverName);
@@ -802,7 +807,7 @@ ${authUrl}
.update(savedToken.token.accessToken)
.digest('hex')
.slice(0, 8);
console.debug(
debugLogger.debug(
`✓ Token verification successful (fingerprint: ${tokenFingerprint})`,
);
} else {
@@ -829,29 +834,31 @@ ${authUrl}
serverName: string,
config: MCPOAuthConfig,
): Promise<string | null> {
console.debug(`Getting valid token for server: ${serverName}`);
debugLogger.debug(`Getting valid token for server: ${serverName}`);
const credentials = await this.tokenStorage.getCredentials(serverName);
if (!credentials) {
console.debug(`No credentials found for server: ${serverName}`);
debugLogger.debug(`No credentials found for server: ${serverName}`);
return null;
}
const { token } = credentials;
console.debug(
debugLogger.debug(
`Found token for server: ${serverName}, expired: ${this.tokenStorage.isTokenExpired(token)}`,
);
// Check if token is expired
if (!this.tokenStorage.isTokenExpired(token)) {
console.debug(`Returning valid token for server: ${serverName}`);
debugLogger.debug(`Returning valid token for server: ${serverName}`);
return token.accessToken;
}
// Try to refresh if we have a refresh token
if (token.refreshToken && config.clientId && credentials.tokenUrl) {
try {
console.log(`Refreshing expired token for MCP server: ${serverName}`);
debugLogger.log(
`Refreshing expired token for MCP server: ${serverName}`,
);
const newTokenResponse = await this.refreshAccessToken(
config,
+8 -7
View File
@@ -6,6 +6,7 @@
import type { MCPOAuthConfig } from './oauth-provider.js';
import { getErrorMessage } from '../utils/errors.js';
import { debugLogger } from '../utils/debugLogger.js';
/**
* OAuth authorization server metadata as per RFC 8414.
@@ -94,7 +95,7 @@ export class OAuthUtils {
}
return (await response.json()) as OAuthProtectedResourceMetadata;
} catch (error) {
console.debug(
debugLogger.debug(
`Failed to fetch protected resource metadata from ${resourceMetadataUrl}: ${getErrorMessage(error)}`,
);
return null;
@@ -117,7 +118,7 @@ export class OAuthUtils {
}
return (await response.json()) as OAuthAuthorizationServerMetadata;
} catch (error) {
console.debug(
debugLogger.debug(
`Failed to fetch authorization server metadata from ${authServerMetadataUrl}: ${getErrorMessage(error)}`,
);
return null;
@@ -205,7 +206,7 @@ export class OAuthUtils {
}
}
console.debug(
debugLogger.debug(
`Metadata discovery failed for authorization server ${authServerUrl}`,
);
return null;
@@ -249,7 +250,7 @@ export class OAuthUtils {
if (authServerMetadata) {
const config = this.metadataToOAuthConfig(authServerMetadata);
if (authServerMetadata.registration_endpoint) {
console.log(
debugLogger.log(
'Dynamic client registration is supported at:',
authServerMetadata.registration_endpoint,
);
@@ -259,14 +260,14 @@ export class OAuthUtils {
}
// Fallback: try well-known endpoints at the base URL
console.debug(`Trying OAuth discovery fallback at ${serverUrl}`);
debugLogger.debug(`Trying OAuth discovery fallback at ${serverUrl}`);
const authServerMetadata =
await this.discoverAuthorizationServerMetadata(serverUrl);
if (authServerMetadata) {
const config = this.metadataToOAuthConfig(authServerMetadata);
if (authServerMetadata.registration_endpoint) {
console.log(
debugLogger.log(
'Dynamic client registration is supported at:',
authServerMetadata.registration_endpoint,
);
@@ -276,7 +277,7 @@ export class OAuthUtils {
return null;
} catch (error) {
console.debug(
debugLogger.debug(
`Failed to discover OAuth configuration: ${getErrorMessage(error)}`,
);
return null;
+2 -1
View File
@@ -5,6 +5,7 @@
*/
import type { DiscoveredMCPPrompt } from '../tools/mcp-client.js';
import { debugLogger } from '../utils/debugLogger.js';
export class PromptRegistry {
private prompts: Map<string, DiscoveredMCPPrompt> = new Map();
@@ -16,7 +17,7 @@ export class PromptRegistry {
registerPrompt(prompt: DiscoveredMCPPrompt): void {
if (this.prompts.has(prompt.name)) {
const newName = `${prompt.serverName}_${prompt.name}`;
console.warn(
debugLogger.warn(
`Prompt with name "${prompt.name}" is already registered. Renaming to "${newName}".`,
);
this.prompts.set(newName, { ...prompt, name: newName });
@@ -27,6 +27,7 @@ import {
isFunctionCall,
isFunctionResponse,
} from '../../utils/messageInspectors.js';
import { debugLogger } from '../../utils/debugLogger.js';
const CLASSIFIER_GENERATION_CONFIG: GenerateContentConfig = {
temperature: 0,
@@ -153,7 +154,7 @@ export class ClassifierStrategy implements RoutingStrategy {
promptId = `classifier-router-fallback-${Date.now()}-${Math.random()
.toString(16)
.slice(2)}`;
console.warn(
debugLogger.warn(
`Could not find promptId in context. This is unexpected. Using a fallback ID: ${promptId}`,
);
}
@@ -206,7 +207,7 @@ export class ClassifierStrategy implements RoutingStrategy {
} catch (error) {
// If the classifier fails for any reason (API error, parsing error, etc.),
// we log it and return null to allow the composite strategy to proceed.
console.warn(`[Routing] ClassifierStrategy failed:`, error);
debugLogger.warn(`[Routing] ClassifierStrategy failed:`, error);
return null;
}
}
@@ -23,6 +23,7 @@ import {
isFunctionCall,
isFunctionResponse,
} from '../utils/messageInspectors.js';
import { debugLogger } from '../utils/debugLogger.js';
const TOOL_CALL_LOOP_THRESHOLD = 5;
const CONTENT_LOOP_THRESHOLD = 10;
@@ -431,14 +432,14 @@ export class LoopDetectionService {
});
} catch (e) {
// Do nothing, treat it as a non-loop.
this.config.getDebugMode() ? console.error(e) : console.debug(e);
this.config.getDebugMode() ? console.error(e) : debugLogger.debug(e);
return false;
}
if (typeof result['confidence'] === 'number') {
if (result['confidence'] > 0.9) {
if (typeof result['reasoning'] === 'string' && result['reasoning']) {
console.warn(result['reasoning']);
debugLogger.warn(result['reasoning']);
}
logLoopDetected(
this.config,
@@ -8,6 +8,7 @@ import type { Config } from '../config/config.js';
import { isPerformanceMonitoringActive } from './metrics.js';
import { getMemoryMonitor } from './memory-monitor.js';
import { ActivityType } from './activity-types.js';
import { debugLogger } from '../utils/debugLogger.js';
/**
* Activity event data structure
@@ -152,7 +153,7 @@ export class ActivityMonitor {
listener(event);
} catch (error) {
// Silently catch listener errors to avoid disrupting the application
console.debug('ActivityMonitor listener error:', error);
debugLogger.debug('ActivityMonitor listener error:', error);
}
});
}
@@ -53,6 +53,7 @@ import {
detectIdeFromEnv,
isCloudShell,
} from '../../ide/detect-ide.js';
import { debugLogger } from '../../utils/debugLogger.js';
export enum EventNames {
START_SESSION = 'start_session',
@@ -238,7 +239,7 @@ export class ClearcutLogger {
]);
if (wasAtCapacity && this.config?.getDebugMode()) {
console.debug(
debugLogger.debug(
`ClearcutLogger: Dropped old event to prevent memory leak (queue size: ${this.events.size})`,
);
}
@@ -282,14 +283,14 @@ export class ClearcutLogger {
}
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
async flushToClearcut(): Promise<LogResponse> {
if (this.flushing) {
if (this.config?.getDebugMode()) {
console.debug(
debugLogger.debug(
'ClearcutLogger: Flush already in progress, marking pending flush.',
);
}
@@ -299,7 +300,7 @@ export class ClearcutLogger {
this.flushing = true;
if (this.config?.getDebugMode()) {
console.log('Flushing log events to Clearcut.');
debugLogger.log('Flushing log events to Clearcut.');
}
const eventsToSend = this.events.toArray() as LogEventEntry[][];
this.events.clear();
@@ -359,7 +360,7 @@ export class ClearcutLogger {
// Fire and forget the pending flush
this.flushToClearcut().catch((error) => {
if (this.config?.getDebugMode()) {
console.debug('Error in pending flush to Clearcut:', error);
debugLogger.debug('Error in pending flush to Clearcut:', error);
}
});
}
@@ -451,7 +452,7 @@ export class ClearcutLogger {
// Flush start event immediately
this.enqueueLogEvent(this.createLogEvent(EventNames.START_SESSION, data));
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -668,14 +669,14 @@ export class ClearcutLogger {
logFlashFallbackEvent(): void {
this.enqueueLogEvent(this.createLogEvent(EventNames.FLASH_FALLBACK, []));
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
logRipgrepFallbackEvent(): void {
this.enqueueLogEvent(this.createLogEvent(EventNames.RIPGREP_FALLBACK, []));
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -811,7 +812,7 @@ export class ClearcutLogger {
// Flush immediately on session end.
this.enqueueLogEvent(this.createLogEvent(EventNames.END_SESSION, []));
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -910,7 +911,7 @@ export class ClearcutLogger {
this.createLogEvent(EventNames.EXTENSION_INSTALL, data),
);
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -930,7 +931,7 @@ export class ClearcutLogger {
this.createLogEvent(EventNames.EXTENSION_UNINSTALL, data),
);
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -962,7 +963,7 @@ export class ClearcutLogger {
this.createLogEvent(EventNames.EXTENSION_UPDATE, data),
);
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -1047,7 +1048,7 @@ export class ClearcutLogger {
this.createLogEvent(EventNames.EXTENSION_ENABLE, data),
);
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -1082,7 +1083,7 @@ export class ClearcutLogger {
this.createLogEvent(EventNames.EXTENSION_DISABLE, data),
);
this.flushToClearcut().catch((error) => {
console.debug('Error flushing to Clearcut:', error);
debugLogger.debug('Error flushing to Clearcut:', error);
});
}
@@ -1240,7 +1241,7 @@ export class ClearcutLogger {
getConfigJson() {
const configJson = safeJsonStringifyBooleanValuesOnly(this.config);
console.debug(configJson);
debugLogger.debug(configJson);
return safeJsonStringifyBooleanValuesOnly(this.config);
}
@@ -1254,7 +1255,7 @@ export class ClearcutLogger {
// Log a warning if we're dropping events
if (eventsToSend.length > MAX_RETRY_EVENTS && this.config?.getDebugMode()) {
console.warn(
debugLogger.warn(
`ClearcutLogger: Dropping ${
eventsToSend.length - MAX_RETRY_EVENTS
} events due to retry queue limit. Total events: ${
@@ -1269,7 +1270,7 @@ export class ClearcutLogger {
if (numEventsToRequeue === 0) {
if (this.config?.getDebugMode()) {
console.debug(
debugLogger.debug(
`ClearcutLogger: No events re-queued (queue size: ${this.events.size})`,
);
}
@@ -1292,7 +1293,7 @@ export class ClearcutLogger {
}
if (this.config?.getDebugMode()) {
console.debug(
debugLogger.debug(
`ClearcutLogger: Re-queued ${numEventsToRequeue} events for retry (queue size: ${this.events.size})`,
);
}
+3 -2
View File
@@ -43,6 +43,7 @@ import {
GcpLogExporter,
} from './gcp-exporters.js';
import { TelemetryTarget } from './index.js';
import { debugLogger } from '../utils/debugLogger.js';
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
@@ -184,7 +185,7 @@ export function initializeTelemetry(config: Config): void {
try {
sdk.start();
if (config.getDebugMode()) {
console.log('OpenTelemetry SDK started successfully.');
debugLogger.log('OpenTelemetry SDK started successfully.');
}
telemetryInitialized = true;
initializeMetrics(config);
@@ -211,7 +212,7 @@ export async function shutdownTelemetry(config: Config): Promise<void> {
ClearcutLogger.getInstance()?.shutdown();
await sdk.shutdown();
if (config.getDebugMode()) {
console.log('OpenTelemetry SDK shut down successfully.');
debugLogger.log('OpenTelemetry SDK shut down successfully.');
}
} catch (error) {
console.error('Error shutting down SDK:', error);
+3 -2
View File
@@ -34,6 +34,7 @@ import type {
import { IdeClient } from '../ide/ide-client.js';
import { safeLiteralReplace } from '../utils/textUtils.js';
import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
export function applyReplacement(
currentContent: string | null,
@@ -255,12 +256,12 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
throw error;
}
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`Error preparing edit: ${errorMsg}`);
debugLogger.log(`Error preparing edit: ${errorMsg}`);
return false;
}
if (editData.error) {
console.log(`Error: ${editData.error.display}`);
debugLogger.log(`Error: ${editData.error.display}`);
return false;
}
+5 -5
View File
@@ -388,7 +388,7 @@ class GrepToolInvocation extends BaseToolInvocation<
});
return this.parseGrepOutput(output, absolutePath);
} catch (gitError: unknown) {
console.debug(
debugLogger.debug(
`GrepLogic: git grep failed: ${getErrorMessage(
gitError,
)}. Falling back...`,
@@ -397,7 +397,7 @@ class GrepToolInvocation extends BaseToolInvocation<
}
// --- Strategy 2: System grep ---
console.debug(
debugLogger.debug(
'GrepLogic: System grep is being considered as fallback strategy.',
);
@@ -494,7 +494,7 @@ class GrepToolInvocation extends BaseToolInvocation<
});
return this.parseGrepOutput(output, absolutePath);
} catch (grepError: unknown) {
console.debug(
debugLogger.debug(
`GrepLogic: System grep failed: ${getErrorMessage(
grepError,
)}. Falling back...`,
@@ -503,7 +503,7 @@ class GrepToolInvocation extends BaseToolInvocation<
}
// --- Strategy 3: Pure JavaScript Fallback ---
console.debug(
debugLogger.debug(
'GrepLogic: Falling back to JavaScript grep implementation.',
);
strategyUsed = 'javascript fallback';
@@ -541,7 +541,7 @@ class GrepToolInvocation extends BaseToolInvocation<
} catch (readError: unknown) {
// Ignore errors like permission denied or file gone during read
if (!isNodeError(readError) || readError.code !== 'ENOENT') {
console.debug(
debugLogger.debug(
`GrepLogic: Could not read/process ${fileAbsolutePath}: ${getErrorMessage(
readError,
)}`,
+26 -18
View File
@@ -41,6 +41,7 @@ import type {
WorkspaceContext,
} from '../utils/workspaceContext.js';
import type { ToolRegistry } from './tool-registry.js';
import { debugLogger } from '../utils/debugLogger.js';
export const MCP_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000; // default to 10 minutes
@@ -321,7 +322,7 @@ async function handleAutomaticOAuth(
wwwAuthenticate: string,
): Promise<boolean> {
try {
console.log(`🔐 '${mcpServerName}' requires OAuth authentication`);
debugLogger.log(`🔐 '${mcpServerName}' requires OAuth authentication`);
// Always try to parse the resource metadata URI from the www-authenticate header
let oauthConfig;
@@ -358,13 +359,13 @@ async function handleAutomaticOAuth(
// Perform OAuth authentication
// Pass the server URL for proper discovery
const serverUrl = mcpServerConfig.httpUrl || mcpServerConfig.url;
console.log(
debugLogger.log(
`Starting OAuth authentication for server '${mcpServerName}'...`,
);
const authProvider = new MCPOAuthProvider(new MCPOAuthTokenStorage());
await authProvider.authenticate(mcpServerName, oauthAuthConfig, serverUrl);
console.log(
debugLogger.log(
`OAuth authentication successful for server '${mcpServerName}'`,
);
return true;
@@ -840,12 +841,12 @@ export async function connectToMcpServer(
},
);
if (hasStoredTokens) {
console.log(
debugLogger.log(
`Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` +
`Please re-authenticate using: /mcp auth ${mcpServerName}`,
);
} else {
console.log(
debugLogger.log(
`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
`Please authenticate using: /mcp auth ${mcpServerName}`,
);
@@ -862,7 +863,7 @@ export async function connectToMcpServer(
// If we didn't get the header from the error string, try to get it from the server
if (!wwwAuthenticate && hasNetworkTransport(mcpServerConfig)) {
console.log(
debugLogger.log(
`No www-authenticate header in error, trying to fetch it from server...`,
);
try {
@@ -880,13 +881,13 @@ export async function connectToMcpServer(
if (response.status === 401) {
wwwAuthenticate = response.headers.get('www-authenticate');
if (wwwAuthenticate) {
console.log(
debugLogger.log(
`Found www-authenticate header from server: ${wwwAuthenticate}`,
);
}
}
} catch (fetchError) {
console.debug(
debugLogger.debug(
`Failed to fetch www-authenticate header: ${getErrorMessage(
fetchError,
)}`,
@@ -895,7 +896,7 @@ export async function connectToMcpServer(
}
if (wwwAuthenticate) {
console.log(
debugLogger.log(
`Received 401 with www-authenticate header: ${wwwAuthenticate}`,
);
@@ -907,7 +908,7 @@ export async function connectToMcpServer(
);
if (oauthSuccess) {
// Retry connection with OAuth token
console.log(
debugLogger.log(
`Retrying connection to '${mcpServerName}' with OAuth token...`,
);
@@ -1000,12 +1001,12 @@ export async function connectToMcpServer(
},
);
if (hasStoredTokens) {
console.log(
debugLogger.log(
`Stored OAuth token for SSE server '${mcpServerName}' was rejected. ` +
`Please re-authenticate using: /mcp auth ${mcpServerName}`,
);
} else {
console.log(
debugLogger.log(
`401 error received for SSE server '${mcpServerName}' without OAuth configuration. ` +
`Please authenticate using: /mcp auth ${mcpServerName}`,
);
@@ -1018,7 +1019,9 @@ export async function connectToMcpServer(
}
// For SSE/HTTP servers, try to discover OAuth configuration from the base URL
console.log(`🔍 Attempting OAuth discovery for '${mcpServerName}'...`);
debugLogger.log(
`🔍 Attempting OAuth discovery for '${mcpServerName}'...`,
);
if (hasNetworkTransport(mcpServerConfig)) {
const serverUrl = new URL(
@@ -1030,7 +1033,7 @@ export async function connectToMcpServer(
// Try to discover OAuth configuration from the base URL
const oauthConfig = await OAuthUtils.discoverOAuthConfig(baseUrl);
if (oauthConfig) {
console.log(
debugLogger.log(
`Discovered OAuth configuration from base URL for server '${mcpServerName}'`,
);
@@ -1046,7 +1049,7 @@ export async function connectToMcpServer(
// Pass the server URL for proper discovery
const authServerUrl =
mcpServerConfig.httpUrl || mcpServerConfig.url;
console.log(
debugLogger.log(
`Starting OAuth authentication for server '${mcpServerName}'...`,
);
const authProvider = new MCPOAuthProvider(
@@ -1257,7 +1260,9 @@ export async function createTransport(
if (accessToken) {
hasOAuthConfig = true;
console.log(`Found stored OAuth token for server '${mcpServerName}'`);
debugLogger.log(
`Found stored OAuth token for server '${mcpServerName}'`,
);
}
}
}
@@ -1322,7 +1327,10 @@ export async function createTransport(
if (debugMode) {
transport.stderr!.on('data', (data) => {
const stderrStr = data.toString().trim();
console.debug(`[DEBUG] [MCP STDERR (${mcpServerName})]: `, stderrStr);
debugLogger.debug(
`[DEBUG] [MCP STDERR (${mcpServerName})]: `,
stderrStr,
);
});
}
return transport;
@@ -1340,7 +1348,7 @@ export function isEnabled(
mcpServerConfig: MCPServerConfig,
): boolean {
if (!funcDecl.name) {
console.warn(
debugLogger.warn(
`Discovered a function declaration without a name from MCP server '${mcpServerName}'. Skipping.`,
);
return false;
@@ -733,8 +733,6 @@ Content of file[1]
const invocation = tool.build({ paths: files });
await invocation.execute(new AbortController().signal);
console.log('Execution order:', executionOrder);
// Verify concurrent execution pattern
// In parallel execution: all "start:" events should come before all "end:" events
// In sequential execution: "start:file1", "end:file1", "start:file2", "end:file2", etc.
+2 -1
View File
@@ -19,6 +19,7 @@ import type { Config } from '../config/config.js';
import { fileExists } from '../utils/fileUtils.js';
import { Storage } from '../config/storage.js';
import { GREP_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
const DEFAULT_TOTAL_MAX_MATCHES = 20000;
@@ -179,7 +180,7 @@ class GrepToolInvocation extends BaseToolInvocation<
const totalMaxMatches = DEFAULT_TOTAL_MAX_MATCHES;
if (this.config.getDebugMode()) {
console.log(`[GrepTool] Total result limit: ${totalMaxMatches}`);
debugLogger.log(`[GrepTool] Total result limit: ${totalMaxMatches}`);
}
for (const searchDir of searchDirectories) {
+3 -2
View File
@@ -39,6 +39,7 @@ import { logSmartEditCorrectionEvent } from '../telemetry/loggers.js';
import { correctPath } from '../utils/pathCorrector.js';
import { EDIT_TOOL_NAME, READ_FILE_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
interface ReplacementContext {
params: EditToolParams;
currentContent: string;
@@ -616,12 +617,12 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
throw error;
}
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`Error preparing edit: ${errorMsg}`);
debugLogger.log(`Error preparing edit: ${errorMsg}`);
return false;
}
if (editData.error) {
console.log(`Error: ${editData.error.display}`);
debugLogger.log(`Error: ${editData.error.display}`);
return false;
}
+3 -2
View File
@@ -21,6 +21,7 @@ import { parse } from 'shell-quote';
import { ToolErrorType } from './tool-error.js';
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
import type { EventEmitter } from 'node:events';
import { debugLogger } from '../utils/debugLogger.js';
type ToolParams = Record<string, unknown>;
@@ -187,7 +188,7 @@ export class ToolRegistry {
tool = tool.asFullyQualifiedTool();
} else {
// Decide on behavior: throw error, log warning, or allow overwrite
console.warn(
debugLogger.warn(
`Tool with name "${tool.name}" is already registered. Overwriting.`,
);
}
@@ -379,7 +380,7 @@ export class ToolRegistry {
// register each function as a tool
for (const func of functions) {
if (!func.name) {
console.warn('Discovered a tool with no name. Skipping.');
debugLogger.warn('Discovered a tool with no name. Skipping.');
continue;
}
const parameters =
+3 -2
View File
@@ -32,6 +32,7 @@ import {
WebFetchFallbackAttemptEvent,
} from '../telemetry/index.js';
import { WEB_FETCH_TOOL_NAME } from './tool-names.js';
import { debugLogger } from '../utils/debugLogger.js';
const URL_FETCH_TIMEOUT_MS = 10000;
const MAX_CONTENT_LENGTH = 100000;
@@ -274,7 +275,7 @@ ${textContent}
DEFAULT_GEMINI_FLASH_MODEL,
);
console.debug(
debugLogger.debug(
`[WebFetchTool] Full response for prompt "${userPrompt.substring(
0,
50,
@@ -367,7 +368,7 @@ ${sourceListFormatted.join('\n')}`;
const llmContent = responseText;
console.debug(
debugLogger.debug(
`[WebFetchTool] Formatted tool response for prompt "${userPrompt}:\n\n":`,
llmContent,
);
+4 -2
View File
@@ -8,11 +8,13 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import type { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import type { FileFilteringOptions } from '../config/constants.js';
import { debugLogger } from './debugLogger.js';
// Simple console logger for now.
// TODO: Integrate with a more robust server-side logger.
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...args: any[]) => console.debug('[DEBUG] [BfsFileSearch]', ...args),
debug: (...args: any[]) =>
debugLogger.debug('[DEBUG] [BfsFileSearch]', ...args),
};
interface BfsFileSearchOptions {
@@ -84,7 +86,7 @@ export async function bfsFileSearch(
} catch (error) {
// Warn user that a directory could not be read, as this affects search results.
const message = (error as Error)?.message ?? 'Unknown error';
console.warn(
debugLogger.warn(
`[WARN] Skipping unreadable directory: ${currentDir} (${message})`,
);
if (debug) {
+3 -2
View File
@@ -14,6 +14,7 @@ import type { FileSystemService } from '../services/fileSystemService.js';
import { ToolErrorType } from '../tools/tool-error.js';
import { BINARY_EXTENSIONS } from './ignorePatterns.js';
import { createRequire as createModuleRequire } from 'node:module';
import { debugLogger } from './debugLogger.js';
const requireModule = createModuleRequire(import.meta.url);
@@ -260,7 +261,7 @@ export async function isBinaryFile(filePath: string): Promise<boolean> {
// If >30% non-printable characters, consider it binary
return nonPrintableCount / bytesRead > 0.3;
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to check if file is binary: ${filePath}`,
error instanceof Error ? error.message : String(error),
);
@@ -270,7 +271,7 @@ export async function isBinaryFile(filePath: string): Promise<boolean> {
try {
await fh.close();
} catch (closeError) {
console.warn(
debugLogger.warn(
`Failed to close file handle for: ${filePath}`,
closeError instanceof Error ? closeError.message : String(closeError),
);
@@ -11,6 +11,7 @@ import { getErrorMessage, isNodeError } from './errors.js';
import type { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import type { FileFilteringOptions } from '../config/constants.js';
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
import { debugLogger } from './debugLogger.js';
const MAX_ITEMS = 200;
const TRUNCATION_INDICATOR = '...';
@@ -104,7 +105,7 @@ async function readFullStructure(
isNodeError(error) &&
(error.code === 'EACCES' || error.code === 'ENOENT')
) {
console.warn(
debugLogger.warn(
`Warning: Could not read directory ${currentPath}: ${error.message}`,
);
if (currentPath === rootPath && error.code === 'ENOENT') {
+2 -1
View File
@@ -10,6 +10,7 @@ import { type BaseLlmClient } from '../core/baseLlmClient.js';
import { LruCache } from './LruCache.js';
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
import { promptIdContext } from './promptIdContext.js';
import { debugLogger } from './debugLogger.js';
const MAX_CACHE_SIZE = 50;
@@ -112,7 +113,7 @@ export async function FixLLMEditWithInstruction(
let promptId = promptIdContext.getStore();
if (!promptId) {
promptId = `llm-fixer-fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
console.warn(
debugLogger.warn(
`Could not find promptId in context. This is unexpected. Using a fallback ID: ${promptId}`,
);
}
+4 -2
View File
@@ -16,15 +16,17 @@ import type { FileFilteringOptions } from '../config/constants.js';
import { DEFAULT_MEMORY_FILE_FILTERING_OPTIONS } from '../config/constants.js';
import { GEMINI_DIR } from './paths.js';
import type { GeminiCLIExtension } from '../config/config.js';
import { debugLogger } from './debugLogger.js';
// Simple console logger, similar to the one previously in CLI's config.ts
// TODO: Integrate with a more robust server-side logger if available/appropriate.
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...args: any[]) =>
console.debug('[DEBUG] [MemoryDiscovery]', ...args),
debugLogger.debug('[DEBUG] [MemoryDiscovery]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
warn: (...args: any[]) => console.warn('[WARN] [MemoryDiscovery]', ...args),
warn: (...args: any[]) =>
debugLogger.warn('[WARN] [MemoryDiscovery]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: (...args: any[]) =>
console.error('[ERROR] [MemoryDiscovery]', ...args),
@@ -8,14 +8,16 @@ import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { isSubpath } from './paths.js';
import { marked, type Token } from 'marked';
import { debugLogger } from './debugLogger.js';
// Simple console logger for import processing
const logger = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
debug: (...args: any[]) =>
console.debug('[DEBUG] [ImportProcessor]', ...args),
debugLogger.debug('[DEBUG] [ImportProcessor]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
warn: (...args: any[]) => console.warn('[WARN] [ImportProcessor]', ...args),
warn: (...args: any[]) =>
debugLogger.warn('[WARN] [ImportProcessor]', ...args),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: (...args: any[]) =>
console.error('[ERROR] [ImportProcessor]', ...args),
@@ -9,6 +9,7 @@ import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
import type { BaseLlmClient } from '../core/baseLlmClient.js';
import type { GeminiChat } from '../core/geminiChat.js';
import { isFunctionResponse } from './messageInspectors.js';
import { debugLogger } from './debugLogger.js';
const CHECK_PROMPT = `Analyze *only* the content and structure of your immediately preceding response (your last turn in the conversation history). Based *strictly* on that response, determine who should logically speak next: the 'user' or the 'model' (you).
**Decision Rules (apply in order):**
@@ -126,7 +127,7 @@ export async function checkNextSpeaker(
}
return null;
} catch (error) {
console.warn(
debugLogger.warn(
'Failed to talk to Gemini endpoint when seeing if conversation should continue.',
error,
);
+2 -1
View File
@@ -10,6 +10,7 @@ import { ApiError } from '@google/genai';
import type { HttpError } from './retry.js';
import { retryWithBackoff } from './retry.js';
import { setSimulate429 } from './testUtils.js';
import { debugLogger } from './debugLogger.js';
// Helper to create a mock function that fails a certain number of times
const createFailingFunction = (
@@ -43,7 +44,7 @@ describe('retryWithBackoff', () => {
// Disable 429 simulation for tests
setSimulate429(false);
// Suppress unhandled promise rejection warnings for tests that expect errors
console.warn = vi.fn();
debugLogger.warn = vi.fn();
});
afterEach(() => {
+9 -8
View File
@@ -12,6 +12,7 @@ import {
isGenericQuotaExceededError,
} from './quotaErrorDetection.js';
import { delay, createAbortError } from './delay.js';
import { debugLogger } from './debugLogger.js';
const FETCH_FAILED_MESSAGE =
'exception TypeError: fetch failed sending request';
@@ -168,7 +169,7 @@ export async function retryWithBackoff<T>(
}
} catch (fallbackError) {
// If fallback fails, continue with original error
console.warn('Fallback to Flash model failed:', fallbackError);
debugLogger.warn('Fallback to Flash model failed:', fallbackError);
}
}
@@ -195,7 +196,7 @@ export async function retryWithBackoff<T>(
}
} catch (fallbackError) {
// If fallback fails, continue with original error
console.warn('Fallback to Flash model failed:', fallbackError);
debugLogger.warn('Fallback to Flash model failed:', fallbackError);
}
}
@@ -227,7 +228,7 @@ export async function retryWithBackoff<T>(
}
} catch (fallbackError) {
// If fallback fails, continue with original error
console.warn('Fallback to Flash model failed:', fallbackError);
debugLogger.warn('Fallback to Flash model failed:', fallbackError);
}
}
@@ -244,7 +245,7 @@ export async function retryWithBackoff<T>(
if (delayDurationMs > 0) {
// Respect Retry-After header if present and parsed
console.warn(
debugLogger.warn(
`Attempt ${attempt} failed with status ${delayErrorStatus ?? 'unknown'}. Retrying after explicit delay of ${delayDurationMs}ms...`,
error,
);
@@ -367,13 +368,13 @@ function logRetryAttempt(
}
if (errorStatus === 429) {
console.warn(message, error);
debugLogger.warn(message, error);
} else if (errorStatus && errorStatus >= 500 && errorStatus < 600) {
console.error(message, error);
} else if (error instanceof Error) {
// Fallback for errors that might not have a status but have a message
if (error.message.includes('429')) {
console.warn(
debugLogger.warn(
`Attempt ${attempt} failed with 429 error (no Retry-After header). Retrying with backoff...`,
error,
);
@@ -383,9 +384,9 @@ function logRetryAttempt(
error,
);
} else {
console.warn(message, error); // Default to warn for other errors
debugLogger.warn(message, error); // Default to warn for other errors
}
} else {
console.warn(message, error); // Default to warn if error type is unknown
debugLogger.warn(message, error); // Default to warn if error type is unknown
}
}
+5 -4
View File
@@ -7,6 +7,7 @@
import { execSync } from 'node:child_process';
import os from 'node:os';
import { detect as chardetDetect } from 'chardet';
import { debugLogger } from './debugLogger.js';
// Cache for system encoding to avoid repeated detection
// Use undefined to indicate "not yet checked" vs null meaning "checked but failed"
@@ -66,7 +67,7 @@ export function getSystemEncoding(): string | null {
`Unable to parse Windows code page from 'chcp' output "${output.trim()}". `,
);
} catch (error) {
console.warn(
debugLogger.warn(
`Failed to get Windows code page using 'chcp' command: ${error instanceof Error ? error.message : String(error)}. ` +
`Will attempt to detect encoding from command output instead.`,
);
@@ -88,7 +89,7 @@ export function getSystemEncoding(): string | null {
.toString()
.trim();
} catch (_e) {
console.warn('Failed to get locale charmap.');
debugLogger.warn('Failed to get locale charmap.');
return null;
}
}
@@ -141,7 +142,7 @@ export function windowsCodePageToEncoding(cp: number): string | null {
return map[cp];
}
console.warn(`Unable to determine encoding for windows code page ${cp}.`);
debugLogger.warn(`Unable to determine encoding for windows code page ${cp}.`);
return null; // Return null if no mapping found
}
@@ -159,7 +160,7 @@ export function detectEncodingFromBuffer(buffer: Buffer): string | null {
return detected.toLowerCase();
}
} catch (error) {
console.warn('Failed to detect encoding with chardet:', error);
debugLogger.warn('Failed to detect encoding with chardet:', error);
}
return null;
@@ -7,6 +7,7 @@
import path from 'node:path';
import { promises as fsp, readFileSync } from 'node:fs';
import { Storage } from '../config/storage.js';
import { debugLogger } from './debugLogger.js';
interface UserAccounts {
active: string | null;
@@ -33,7 +34,7 @@ export class UserAccountManager {
// Inlined validation logic
if (typeof parsed !== 'object' || parsed === null) {
console.log('Invalid accounts file schema, starting fresh.');
debugLogger.log('Invalid accounts file schema, starting fresh.');
return defaultState;
}
const { active, old } = parsed as Partial<UserAccounts>;
@@ -43,7 +44,7 @@ export class UserAccountManager {
(Array.isArray(old) && old.every((i) => typeof i === 'string')));
if (!isValid) {
console.log('Invalid accounts file schema, starting fresh.');
debugLogger.log('Invalid accounts file schema, starting fresh.');
return defaultState;
}
@@ -66,7 +67,10 @@ export class UserAccountManager {
) {
return defaultState;
}
console.log('Error during sync read of accounts, starting fresh.', error);
debugLogger.log(
'Error during sync read of accounts, starting fresh.',
error,
);
return defaultState;
}
}
@@ -84,7 +88,7 @@ export class UserAccountManager {
) {
return defaultState;
}
console.log('Could not parse accounts file, starting fresh.', error);
debugLogger.log('Could not parse accounts file, starting fresh.', error);
return defaultState;
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import { isNodeError } from '../utils/errors.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as process from 'node:process';
import { debugLogger } from './debugLogger.js';
export type Unsubscribe = () => void;
@@ -72,7 +73,7 @@ export class WorkspaceContext {
this.directories.add(resolved);
this.notifyDirectoriesChanged();
} catch (err) {
console.warn(
debugLogger.warn(
`[WARN] Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`,
);
}