feat(cli): migrate console calls to debugLogger and stdioHelpers (M3 Phase 7-9)

Route CLI console.* calls to structured logging:
- Debug/internal diagnostics → debugLogger (logfile)
- User-facing output → writeStdoutLine/writeStderrLine/clearScreen (stdioHelpers)
- Add stdioHelpers.ts with writeStdoutLine, writeStderrLine, clearScreen
- Migrate pre-session files (gemini.tsx, sandbox.ts, config.ts) to stdioHelpers
- Migrate extension/MCP commands to stdioHelpers
- Migrate non-interactive session/control to debugLogger
- Migrate UI hooks and components to debugLogger
This commit is contained in:
tanzhenxin 2026-01-26 15:02:37 +08:00
parent 45df0e8b82
commit 7995c65571
82 changed files with 606 additions and 485 deletions

View file

@ -18,10 +18,15 @@ import { ScopeSelector } from './shared/ScopeSelector.js';
import type { LoadedSettings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js';
import type { EditorType } from '@qwen-code/qwen-code-core';
import { isEditorAvailable } from '@qwen-code/qwen-code-core';
import {
createDebugLogger,
isEditorAvailable,
} from '@qwen-code/qwen-code-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { t } from '../../i18n/index.js';
const debugLogger = createDebugLogger('EDITOR_SETTINGS_DIALOG');
interface EditorDialogProps {
onSelect: (editorType: EditorType | undefined, scope: SettingScope) => void;
settings: LoadedSettings;
@ -61,7 +66,7 @@ export function EditorSettingsDialog({
)
: 0;
if (editorIndex === -1) {
console.error(`Editor is not supported: ${currentPreference}`);
debugLogger.error(`Editor is not supported: ${currentPreference}`);
editorIndex = 0;
}

View file

@ -9,11 +9,14 @@ import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js';
import { relaunchApp } from '../../utils/processUtils.js';
import { type RestartReason } from '../hooks/useIdeTrustListener.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
interface IdeTrustChangeDialogProps {
reason: RestartReason;
}
const debugLogger = createDebugLogger('IDE_TRUST_DIALOG');
export const IdeTrustChangeDialog = ({ reason }: IdeTrustChangeDialogProps) => {
useKeypress(
(key) => {
@ -27,7 +30,7 @@ export const IdeTrustChangeDialog = ({ reason }: IdeTrustChangeDialogProps) => {
let message = 'Workspace trust has changed.';
if (reason === 'NONE') {
// This should not happen, but provides a fallback and a debug log.
console.error(
debugLogger.error(
'IdeTrustChangeDialog rendered with unexpected reason "NONE"',
);
} else if (reason === 'CONNECTION_CHANGE') {

View file

@ -22,7 +22,7 @@ import { useKeypress } from '../hooks/useKeypress.js';
import { keyMatchers, Command } from '../keyMatchers.js';
import type { CommandContext, SlashCommand } from '../commands/types.js';
import type { Config } from '@qwen-code/qwen-code-core';
import { ApprovalMode } from '@qwen-code/qwen-code-core';
import { ApprovalMode, createDebugLogger } from '@qwen-code/qwen-code-core';
import {
parseInputForHighlighting,
buildSegmentsForVisualSlice,
@ -38,6 +38,8 @@ import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { FEEDBACK_DIALOG_KEYS } from '../FeedbackDialog.js';
const debugLogger = createDebugLogger('INPUT_PROMPT');
export interface InputPromptProps {
buffer: TextBuffer;
onSubmit: (value: string) => void;
@ -299,7 +301,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
}
} catch (error) {
console.error('Error handling clipboard image:', error);
debugLogger.error('Error handling clipboard image:', error);
}
}, [buffer, config]);

View file

@ -12,6 +12,7 @@ import Link from 'ink-link';
import qrcode from 'qrcode-terminal';
import { Colors } from '../colors.js';
import type { DeviceAuthorizationData } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { t } from '../../i18n/index.js';
@ -29,6 +30,8 @@ interface QwenOAuthProgressProps {
authMessage?: string | null;
}
const debugLogger = createDebugLogger('QWEN_OAUTH_PROGRESS');
/**
* Static QR Code Display Component
* Renders the QR code and URL once and doesn't re-render unless the URL changes
@ -161,7 +164,7 @@ export function QwenOAuthProgress({
},
);
} catch (error) {
console.error('Failed to generate QR code:', error);
debugLogger.error('Failed to generate QR code:', error);
setQrCodeData(null);
}
};

View file

@ -29,7 +29,7 @@ import {
} from '../../utils/settingsUtils.js';
import { updateOutputLanguageFile } from '../../utils/languageUtils.js';
import { useVimMode } from '../contexts/VimModeContext.js';
import { type Config } from '@qwen-code/qwen-code-core';
import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core';
import { useKeypress } from '../hooks/useKeypress.js';
import chalk from 'chalk';
import { cpSlice, cpLen, stripUnsafeCharacters } from '../utils/textUtils.js';
@ -46,6 +46,8 @@ interface SettingsDialogProps {
config?: Config;
}
const debugLogger = createDebugLogger('SETTINGS_DIALOG');
const maxItemsToShow = 8;
export function SettingsDialog({
@ -162,7 +164,7 @@ export function SettingsDialog({
{} as Settings,
);
console.log(
debugLogger.debug(
`[DEBUG SettingsDialog] Saving ${key} immediately with value:`,
newValue,
);
@ -177,7 +179,7 @@ export function SettingsDialog({
if (key === 'general.vimMode' && newValue !== vimEnabled) {
// Call toggleVimEnabled to sync the VimModeContext local state
toggleVimEnabled().catch((error) => {
console.error('Failed to toggle vim mode:', error);
debugLogger.error('Failed to toggle vim mode:', error);
});
}
@ -189,7 +191,7 @@ export function SettingsDialog({
try {
config?.setApprovalMode(settings.merged.tools.approvalMode);
} catch (error) {
console.error(
debugLogger.error(
'Failed to apply approval mode to current session:',
error,
);
@ -663,7 +665,7 @@ export function SettingsDialog({
try {
config?.setApprovalMode(settings.merged.tools.approvalMode);
} catch (error) {
console.error(
debugLogger.error(
'Failed to apply approval mode to current session:',
error,
);

View file

@ -9,6 +9,7 @@ import { render, Box, useApp } from 'ink';
import { getGitBranch, SessionService } from '@qwen-code/qwen-code-core';
import { KeypressProvider } from '../contexts/KeypressContext.js';
import { SessionPicker } from './SessionPicker.js';
import { writeStdoutLine } from '../../utils/stdioHelpers.js';
interface StandalonePickerScreenProps {
sessionService: SessionService;
@ -70,7 +71,7 @@ export async function showResumeSessionPicker(
const sessionService = new SessionService(cwd);
const hasSession = await sessionService.loadLastSession();
if (!hasSession) {
console.log('No sessions found. Start a new session with `qwen`.');
writeStdoutLine('No sessions found. Start a new session with `qwen`.');
return undefined;
}

View file

@ -10,8 +10,10 @@ import stringWidth from 'string-width';
import { theme } from '../../semantic-colors.js';
import { toCodePoints } from '../../utils/textUtils.js';
import { useOverflowActions } from '../../contexts/OverflowContext.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
let enableDebugLog = false;
const debugLogger = createDebugLogger('MAX_SIZED_BOX');
/**
* Minimum height for the MaxSizedBox component.
@ -28,7 +30,7 @@ function debugReportError(message: string, element: React.ReactNode) {
if (!enableDebugLog) return;
if (!React.isValidElement(element)) {
console.error(
debugLogger.error(
message,
`Invalid element: '${String(element)}' typeof=${typeof element}`,
);
@ -44,10 +46,13 @@ function debugReportError(message: string, element: React.ReactNode) {
const lineNumber = elementWithSource._source?.lineNumber;
sourceMessage = fileName ? `${fileName}:${lineNumber}` : '<Unknown file>';
} catch (error) {
console.error('Error while trying to get file name:', error);
debugLogger.error('Error while trying to get file name:', error);
}
console.error(message, `${String(element.type)}. Source: ${sourceMessage}`);
debugLogger.error(
message,
`${String(element.type)}. Source: ${sourceMessage}`,
);
}
interface MaxSizedBoxProps {
children?: React.ReactNode;

View file

@ -9,7 +9,7 @@ import fs from 'node:fs';
import os from 'node:os';
import pathMod from 'node:path';
import { useState, useCallback, useEffect, useMemo, useReducer } from 'react';
import { unescapePath } from '@qwen-code/qwen-code-core';
import { createDebugLogger, unescapePath } from '@qwen-code/qwen-code-core';
import {
toCodePoints,
cpLen,
@ -20,6 +20,8 @@ import {
import type { VimAction } from './vim-buffer-actions.js';
import { handleVimAction } from './vim-buffer-actions.js';
const debugLogger = createDebugLogger('TEXT_BUFFER');
export type Direction =
| 'left'
| 'right'
@ -1143,7 +1145,7 @@ function textBufferReducerLogic(
break;
default: {
const exhaustiveCheck: never = dir;
console.error(
debugLogger.error(
`Unknown visual movement direction: ${exhaustiveCheck}`,
);
return state;
@ -1489,7 +1491,7 @@ function textBufferReducerLogic(
default: {
const exhaustiveCheck: never = action;
console.error(`Unknown action encountered: ${exhaustiveCheck}`);
debugLogger.error(`Unknown action encountered: ${exhaustiveCheck}`);
return state;
}
}
@ -1858,7 +1860,7 @@ export function useTextBuffer({
newText = newText.replace(/\r\n?/g, '\n');
dispatch({ type: 'set_text', payload: newText, pushToUndo: false });
} catch (err) {
console.error('[useTextBuffer] external editor error', err);
debugLogger.error('[useTextBuffer] external editor error', err);
} finally {
if (wasRaw) setRawMode?.(true);
try {

View file

@ -11,12 +11,15 @@ import type {
SubagentManager,
SubagentConfig,
} from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { theme } from '../../../semantic-colors.js';
import { shouldShowColor, getColorForDisplay } from '../utils.js';
import { useLaunchEditor } from '../../../hooks/useLaunchEditor.js';
import { useKeypress } from '../../../hooks/useKeypress.js';
import { t } from '../../../../i18n/index.js';
const debugLogger = createDebugLogger('SUBAGENT_CREATION_SUMMARY');
/**
* Step 6: Final confirmation and actions.
*/
@ -87,7 +90,7 @@ export function CreationSummary({
}
} catch (error) {
// Silently handle errors in warning checks
console.warn('Error checking subagent name availability:', error);
debugLogger.warn('Error checking subagent name availability:', error);
}
// Check length warnings

View file

@ -6,6 +6,7 @@
import { Box, Text } from 'ink';
import { type SubagentConfig } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import type { StepNavigationProps } from '../types.js';
import { theme } from '../../../semantic-colors.js';
import { useKeypress } from '../../../hooks/useKeypress.js';
@ -16,6 +17,8 @@ interface AgentDeleteStepProps extends StepNavigationProps {
onDelete: (agent: SubagentConfig) => Promise<void>;
}
const debugLogger = createDebugLogger('AGENT_DELETE_STEP');
export function AgentDeleteStep({
selectedAgent,
onDelete,
@ -30,7 +33,7 @@ export function AgentDeleteStep({
await onDelete(selectedAgent);
// Navigation will be handled by the parent component after successful deletion
} catch (error) {
console.error('Failed to delete agent:', error);
debugLogger.error('Failed to delete agent:', error);
}
} else if (key.name === 'n') {
onNavigateBack();

View file

@ -17,6 +17,7 @@ import { MANAGEMENT_STEPS } from '../types.js';
import { theme } from '../../../semantic-colors.js';
import { getColorForDisplay, shouldShowColor } from '../utils.js';
import type { SubagentConfig, Config } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { useKeypress } from '../../../hooks/useKeypress.js';
import { t } from '../../../../i18n/index.js';
@ -25,6 +26,8 @@ interface AgentsManagerDialogProps {
config: Config | null;
}
const debugLogger = createDebugLogger('AGENTS_MANAGER_DIALOG');
/**
* Main orchestrator component for the agents management dialog.
*/
@ -108,7 +111,7 @@ export function AgentsManagerDialog({
setNavigationStack([MANAGEMENT_STEPS.AGENT_SELECTION]);
setSelectedAgentIndex(-1);
} catch (error) {
console.error('Failed to delete agent:', error);
debugLogger.error('Failed to delete agent:', error);
throw error; // Re-throw to let the component handle the error state
}
},
@ -253,7 +256,7 @@ export function AgentsManagerDialog({
await loadAgents();
handleNavigateBack();
} catch (error) {
console.error('Failed to save agent changes:', error);
debugLogger.error('Failed to save agent changes:', error);
}
}
}}
@ -282,7 +285,7 @@ export function AgentsManagerDialog({
await loadAgents();
handleNavigateBack();
} catch (error) {
console.error('Failed to save color changes:', error);
debugLogger.error('Failed to save color changes:', error);
}
}
}}

View file

@ -7,6 +7,9 @@
import { Box, Text } from 'ink';
import { useUIState } from '../../contexts/UIStateContext.js';
import { ExtensionUpdateState } from '../../state/extensions.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
const debugLogger = createDebugLogger('EXTENSIONS_LIST');
export const ExtensionsList = () => {
const { extensionsUpdateState, commandContext } = useUIState();
@ -47,7 +50,7 @@ export const ExtensionsList = () => {
stateColor = 'green';
break;
default:
console.error(`Unhandled ExtensionUpdateState ${state}`);
debugLogger.error(`Unhandled ExtensionUpdateState ${state}`);
break;
}