feat: standardize logging across UI components

- Replaced console.log and console.error statements with logger methods from @automaker/utils in various UI components, ensuring consistent log formatting and improved readability.
- Enhanced error handling by utilizing logger methods to provide clearer context for issues encountered during operations.
- Updated multiple views and hooks to integrate the new logging system, improving maintainability and debugging capabilities.

This update significantly enhances the observability of UI components, facilitating easier troubleshooting and monitoring.
This commit is contained in:
Shirone
2026-01-02 17:25:13 +01:00
parent 96a999817f
commit 69f3ba9724
86 changed files with 1079 additions and 677 deletions

View File

@@ -1,9 +1,12 @@
import { useEffect, useCallback, useMemo } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { createLogger } from '@automaker/utils/logger';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import type { AutoModeEvent } from '@/types/electron';
const logger = createLogger('AutoMode');
// Type guard for plan_approval_required event
function isPlanApprovalEvent(
event: AutoModeEvent
@@ -67,7 +70,7 @@ export function useAutoMode() {
if (!api?.autoMode) return;
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
console.log('[AutoMode Event]', event);
logger.info('Event:', event);
// Events include projectPath from backend - use it to look up project ID
// Fall back to current projectId if not provided in event
@@ -84,7 +87,7 @@ export function useAutoMode() {
// Skip event if we couldn't determine the project
if (!eventProjectId) {
console.warn('[AutoMode] Could not determine project for event:', event);
logger.warn('Could not determine project for event:', event);
return;
}
@@ -103,7 +106,7 @@ export function useAutoMode() {
case 'auto_mode_feature_complete':
// Feature completed - remove from running tasks and UI will reload features on its own
if (event.featureId) {
console.log('[AutoMode] Feature completed:', event.featureId, 'passes:', event.passes);
logger.info('Feature completed:', event.featureId, 'passes:', event.passes);
removeRunningTask(eventProjectId, event.featureId);
addAutoModeActivity({
featureId: event.featureId,
@@ -121,7 +124,7 @@ export function useAutoMode() {
// Check if this is a user-initiated cancellation or abort (not a real error)
if (event.errorType === 'cancellation' || event.errorType === 'abort') {
// User cancelled/aborted the feature - just log as info, not an error
console.log('[AutoMode] Feature cancelled/aborted:', event.error);
logger.info('Feature cancelled/aborted:', event.error);
// Remove from running tasks
if (eventProjectId) {
removeRunningTask(eventProjectId, event.featureId);
@@ -130,7 +133,7 @@ export function useAutoMode() {
}
// Real error - log and show to user
console.error('[AutoMode Error]', event.error);
logger.error('Error:', event.error);
// Check for authentication errors and provide a more helpful message
const isAuthError =
@@ -182,7 +185,7 @@ export function useAutoMode() {
case 'auto_mode_phase':
// Log phase transitions (Planning, Action, Verification)
if (event.featureId && event.phase && event.message) {
console.log(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
logger.debug(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: event.phase,
@@ -195,7 +198,7 @@ export function useAutoMode() {
case 'plan_approval_required':
// Plan requires user approval before proceeding
if (isPlanApprovalEvent(event)) {
console.log(`[AutoMode] Plan approval required for ${event.featureId}`);
logger.debug(`[AutoMode] Plan approval required for ${event.featureId}`);
setPendingPlanApproval({
featureId: event.featureId,
projectPath: event.projectPath || currentProject?.path || '',
@@ -208,7 +211,7 @@ export function useAutoMode() {
case 'planning_started':
// Log when planning phase begins
if (event.featureId && event.mode && event.message) {
console.log(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
logger.debug(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'planning',
@@ -221,7 +224,7 @@ export function useAutoMode() {
case 'plan_approved':
// Log when plan is approved by user
if (event.featureId) {
console.log(`[AutoMode] Plan approved for ${event.featureId}`);
logger.debug(`[AutoMode] Plan approved for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'action',
@@ -236,7 +239,7 @@ export function useAutoMode() {
case 'plan_auto_approved':
// Log when plan is auto-approved (requirePlanApproval=false)
if (event.featureId) {
console.log(`[AutoMode] Plan auto-approved for ${event.featureId}`);
logger.debug(`[AutoMode] Plan auto-approved for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'action',
@@ -253,7 +256,7 @@ export function useAutoMode() {
AutoModeEvent,
{ type: 'plan_revision_requested' }
>;
console.log(
logger.debug(
`[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})`
);
addAutoModeActivity({
@@ -269,7 +272,7 @@ export function useAutoMode() {
// Task started - show which task is being worked on
if (event.featureId && 'taskId' in event && 'taskDescription' in event) {
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_started' }>;
console.log(
logger.debug(
`[AutoMode] Task ${taskEvent.taskId} started for ${event.featureId}: ${taskEvent.taskDescription}`
);
addAutoModeActivity({
@@ -284,7 +287,7 @@ export function useAutoMode() {
// Task completed - show progress
if (event.featureId && 'taskId' in event) {
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_complete' }>;
console.log(
logger.debug(
`[AutoMode] Task ${taskEvent.taskId} completed for ${event.featureId} (${taskEvent.tasksCompleted}/${taskEvent.tasksTotal})`
);
addAutoModeActivity({
@@ -302,7 +305,7 @@ export function useAutoMode() {
AutoModeEvent,
{ type: 'auto_mode_phase_complete' }
>;
console.log(
logger.debug(
`[AutoMode] Phase ${phaseEvent.phaseNumber} completed for ${event.featureId}`
);
addAutoModeActivity({
@@ -330,18 +333,18 @@ export function useAutoMode() {
// Start auto mode - UI only, feature pickup is handled in board-view.tsx
const start = useCallback(() => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
setAutoModeRunning(currentProject.id, true);
console.log(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
logger.debug(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
}, [currentProject, setAutoModeRunning, maxConcurrency]);
// Stop auto mode - UI only, running tasks continue until natural completion
const stop = useCallback(() => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
@@ -350,14 +353,14 @@ export function useAutoMode() {
// Stopping auto mode only turns off the toggle to prevent new features
// from being picked up. Running tasks will complete naturally and be
// removed via the auto_mode_feature_complete event.
console.log('[AutoMode] Stopped - running tasks will continue');
logger.info('Stopped - running tasks will continue');
}, [currentProject, setAutoModeRunning]);
// Stop a specific feature
const stopFeature = useCallback(
async (featureId: string) => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
@@ -371,7 +374,7 @@ export function useAutoMode() {
if (result.success) {
removeRunningTask(currentProject.id, featureId);
console.log('[AutoMode] Feature stopped successfully:', featureId);
logger.info('Feature stopped successfully:', featureId);
addAutoModeActivity({
featureId,
type: 'complete',
@@ -379,11 +382,11 @@ export function useAutoMode() {
passes: false,
});
} else {
console.error('[AutoMode] Failed to stop feature:', result.error);
logger.error('Failed to stop feature:', result.error);
throw new Error(result.error || 'Failed to stop feature');
}
} catch (error) {
console.error('[AutoMode] Error stopping feature:', error);
logger.error('Error stopping feature:', error);
throw error;
}
},

View File

@@ -1,8 +1,11 @@
import { useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger';
import { useAppStore } from '@/store/app-store';
import { getHttpApiClient } from '@/lib/http-api-client';
import { toast } from 'sonner';
const logger = createLogger('BoardBackground');
/**
* Hook for managing board background settings with automatic persistence to server
*/
@@ -19,11 +22,11 @@ export function useBoardBackgroundSettings() {
});
if (!result.success) {
console.error('Failed to persist settings:', result.error);
logger.error('Failed to persist settings:', result.error);
toast.error('Failed to save settings');
}
} catch (error) {
console.error('Failed to persist settings:', error);
logger.error('Failed to persist settings:', error);
toast.error('Failed to save settings');
}
},

View File

@@ -4,6 +4,9 @@ import { useMessageQueue } from './use-message-queue';
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import { sanitizeFilename } from '@/lib/image-utils';
import { createLogger } from '@automaker/utils/logger';
const logger = createLogger('ElectronAgent');
interface UseElectronAgentOptions {
sessionId: string;
@@ -93,7 +96,7 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Sending message directly', {
logger.info('Sending message directly', {
hasImages: images && images.length > 0,
imageCount: images?.length || 0,
hasTextFiles: textFiles && textFiles.length > 0,
@@ -123,9 +126,9 @@ export function useElectronAgent({
);
if (result.success && result.path) {
imagePaths.push(result.path);
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
logger.info('Saved image to .automaker/images:', result.path);
} else {
console.error('[useElectronAgent] Failed to save image:', result.error);
logger.error('Failed to save image:', result.error);
}
}
}
@@ -146,7 +149,7 @@ export function useElectronAgent({
// Note: We don't set isProcessing to false here because
// it will be set by the "complete" or "error" stream event
} catch (err) {
console.error('[useElectronAgent] Failed to send message:', err);
logger.error('Failed to send message:', err);
setError(err instanceof Error ? err.message : 'Failed to send message');
setIsProcessing(false);
throw err;
@@ -191,13 +194,13 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Starting session:', sessionId);
logger.info('Starting session:', sessionId);
const result = await api.agent!.start(sessionId, workingDirectory);
if (!mounted) return;
if (result.success && result.messages) {
console.log('[useElectronAgent] Loaded', result.messages.length, 'messages');
logger.info('Loaded', result.messages.length, 'messages');
setMessages(result.messages);
setIsConnected(true);
@@ -205,7 +208,7 @@ export function useElectronAgent({
const historyResult = await api.agent!.getHistory(sessionId);
if (mounted && historyResult.success) {
const isRunning = historyResult.isRunning || false;
console.log('[useElectronAgent] Session running state:', isRunning);
logger.info('Session running state:', isRunning);
setIsProcessing(isRunning);
}
} else {
@@ -214,7 +217,7 @@ export function useElectronAgent({
}
} catch (err) {
if (!mounted) return;
console.error('[useElectronAgent] Failed to initialize:', err);
logger.error('Failed to initialize:', err);
setError(err instanceof Error ? err.message : 'Failed to initialize');
setIsProcessing(false);
}
@@ -230,7 +233,7 @@ export function useElectronAgent({
// Auto-process queue when agent finishes processing
useEffect(() => {
if (!isProcessing && !isProcessingQueue && queuedMessages.length > 0) {
console.log('[useElectronAgent] Auto-processing next queued message');
logger.info('Auto-processing next queued message');
processNext();
}
}, [isProcessing, isProcessingQueue, queuedMessages.length, processNext]);
@@ -241,21 +244,21 @@ export function useElectronAgent({
if (!api?.agent) return;
if (!sessionId) return; // Don't subscribe if no session
console.log('[useElectronAgent] Subscribing to stream events for session:', sessionId);
logger.info('Subscribing to stream events for session:', sessionId);
const handleStream = (event: StreamEvent) => {
// CRITICAL: Only process events for our specific session
if (event.sessionId !== sessionId) {
console.log('[useElectronAgent] Ignoring event for different session:', event.sessionId);
logger.info('Ignoring event for different session:', event.sessionId);
return;
}
console.log('[useElectronAgent] Stream event for', sessionId, ':', event.type);
logger.info('Stream event for', sessionId, ':', event.type);
switch (event.type) {
case 'started':
// Agent started processing (including from queue)
console.log('[useElectronAgent] Agent started processing for session:', sessionId);
logger.info('Agent started processing for session:', sessionId);
setIsProcessing(true);
break;
@@ -300,13 +303,13 @@ export function useElectronAgent({
case 'tool_use':
// Tool being used
console.log('[useElectronAgent] Tool use:', event.tool.name);
logger.info('Tool use:', event.tool.name);
onToolUse?.(event.tool.name, event.tool.input);
break;
case 'complete':
// Agent finished processing for THIS session
console.log('[useElectronAgent] Processing complete for session:', sessionId);
logger.info('Processing complete for session:', sessionId);
setIsProcessing(false);
if (event.messageId) {
setMessages((prev) =>
@@ -319,7 +322,7 @@ export function useElectronAgent({
case 'error':
// Error occurred for THIS session
console.error('[useElectronAgent] Agent error for session:', sessionId, event.error);
logger.error('Agent error for session:', sessionId, event.error);
setIsProcessing(false);
setError(event.error);
if (event.message) {
@@ -330,13 +333,13 @@ export function useElectronAgent({
case 'queue_updated':
// Server queue was updated
console.log('[useElectronAgent] Queue updated:', event.queue);
logger.info('Queue updated:', event.queue);
setServerQueue(event.queue || []);
break;
case 'queue_error':
// Error processing a queued prompt
console.error('[useElectronAgent] Queue error:', event.error);
logger.error('Queue error:', event.error);
setError(event.error);
break;
}
@@ -346,7 +349,7 @@ export function useElectronAgent({
return () => {
if (unsubscribeRef.current) {
console.log('[useElectronAgent] Unsubscribing from stream events for session:', sessionId);
logger.info('Unsubscribing from stream events for session:', sessionId);
unsubscribeRef.current();
unsubscribeRef.current = null;
}
@@ -363,7 +366,7 @@ export function useElectronAgent({
}
if (isProcessing) {
console.warn('[useElectronAgent] Already processing a message');
logger.warn('Already processing a message');
return;
}
@@ -371,7 +374,7 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Sending message', {
logger.info('Sending message', {
hasImages: images && images.length > 0,
imageCount: images?.length || 0,
hasTextFiles: textFiles && textFiles.length > 0,
@@ -401,9 +404,9 @@ export function useElectronAgent({
);
if (result.success && result.path) {
imagePaths.push(result.path);
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
logger.info('Saved image to .automaker/images:', result.path);
} else {
console.error('[useElectronAgent] Failed to save image:', result.error);
logger.error('Failed to save image:', result.error);
}
}
}
@@ -424,7 +427,7 @@ export function useElectronAgent({
// Note: We don't set isProcessing to false here because
// it will be set by the "complete" or "error" stream event
} catch (err) {
console.error('[useElectronAgent] Failed to send message:', err);
logger.error('Failed to send message:', err);
setError(err instanceof Error ? err.message : 'Failed to send message');
setIsProcessing(false);
}
@@ -441,7 +444,7 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Stopping execution');
logger.info('Stopping execution');
const result = await api.agent!.stop(sessionId);
if (!result.success) {
@@ -450,7 +453,7 @@ export function useElectronAgent({
setIsProcessing(false);
}
} catch (err) {
console.error('[useElectronAgent] Failed to stop:', err);
logger.error('Failed to stop:', err);
setError(err instanceof Error ? err.message : 'Failed to stop execution');
}
}, [sessionId]);
@@ -464,7 +467,7 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Clearing history');
logger.info('Clearing history');
const result = await api.agent!.clear(sessionId);
if (result.success) {
@@ -474,7 +477,7 @@ export function useElectronAgent({
setError(result.error || 'Failed to clear history');
}
} catch (err) {
console.error('[useElectronAgent] Failed to clear:', err);
logger.error('Failed to clear:', err);
setError(err instanceof Error ? err.message : 'Failed to clear history');
}
}, [sessionId]);
@@ -516,14 +519,14 @@ export function useElectronAgent({
}
}
console.log('[useElectronAgent] Adding to server queue');
logger.info('Adding to server queue');
const result = await api.agent.queueAdd(sessionId, messageContent, imagePaths, model);
if (!result.success) {
setError(result.error || 'Failed to add to queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to add to queue:', err);
logger.error('Failed to add to queue:', err);
setError(err instanceof Error ? err.message : 'Failed to add to queue');
}
},
@@ -540,14 +543,14 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Removing from server queue:', promptId);
logger.info('Removing from server queue:', promptId);
const result = await api.agent.queueRemove(sessionId, promptId);
if (!result.success) {
setError(result.error || 'Failed to remove from queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to remove from queue:', err);
logger.error('Failed to remove from queue:', err);
setError(err instanceof Error ? err.message : 'Failed to remove from queue');
}
},
@@ -563,14 +566,14 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Clearing server queue');
logger.info('Clearing server queue');
const result = await api.agent.queueClear(sessionId);
if (!result.success) {
setError(result.error || 'Failed to clear queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to clear queue:', err);
logger.error('Failed to clear queue:', err);
setError(err instanceof Error ? err.message : 'Failed to clear queue');
}
}, [sessionId]);

View File

@@ -1,6 +1,9 @@
import { useState, useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger';
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
const logger = createLogger('MessageQueue');
export interface QueuedMessage {
id: string;
content: string;
@@ -72,7 +75,7 @@ export function useMessageQueue({ onProcessNext }: UseMessageQueueOptions): UseM
// Remove the processed message from queue
setQueuedMessages((prev) => prev.slice(1));
} catch (error) {
console.error('Error processing queued message:', error);
logger.error('Error processing queued message:', error);
// Keep the message in queue for retry or manual removal
} finally {
setIsProcessingQueue(false);

View File

@@ -18,11 +18,14 @@
*/
import { useEffect, useState, useRef } from 'react';
import { createLogger } from '@automaker/utils/logger';
import { getHttpApiClient, waitForApiKeyInit } from '@/lib/http-api-client';
import { isElectron } from '@/lib/electron';
import { getItem, removeItem } from '@/lib/storage';
import { useAppStore } from '@/store/app-store';
const logger = createLogger('SettingsMigration');
/**
* State returned by useSettingsMigration hook
*/
@@ -109,7 +112,7 @@ export function useSettingsMigration(): MigrationState {
const status = await api.settings.getStatus();
if (!status.success) {
console.error('[Settings Migration] Failed to get status:', status);
logger.error('Failed to get status:', status);
setState({
checked: true,
migrated: false,
@@ -120,7 +123,7 @@ export function useSettingsMigration(): MigrationState {
// If settings files already exist, no migration needed
if (!status.needsMigration) {
console.log('[Settings Migration] Settings files exist, no migration needed');
logger.info('Settings files exist, no migration needed');
setState({ checked: true, migrated: false, error: null });
return;
}
@@ -128,12 +131,12 @@ export function useSettingsMigration(): MigrationState {
// Check if we have localStorage data to migrate
const automakerStorage = getItem('automaker-storage');
if (!automakerStorage) {
console.log('[Settings Migration] No localStorage data to migrate');
logger.info('No localStorage data to migrate');
setState({ checked: true, migrated: false, error: null });
return;
}
console.log('[Settings Migration] Starting migration...');
logger.info('Starting migration...');
// Collect all localStorage data
const localStorageData: Record<string, string> = {};
@@ -148,7 +151,7 @@ export function useSettingsMigration(): MigrationState {
const result = await api.settings.migrate(localStorageData);
if (result.success) {
console.log('[Settings Migration] Migration successful:', {
logger.info('Migration successful:', {
globalSettings: result.migratedGlobalSettings,
credentials: result.migratedCredentials,
projects: result.migratedProjectCount,
@@ -161,7 +164,7 @@ export function useSettingsMigration(): MigrationState {
setState({ checked: true, migrated: true, error: null });
} else {
console.warn('[Settings Migration] Migration had errors:', result.errors);
logger.warn('Migration had errors:', result.errors);
setState({
checked: true,
migrated: false,
@@ -169,7 +172,7 @@ export function useSettingsMigration(): MigrationState {
});
}
} catch (error) {
console.error('[Settings Migration] Migration failed:', error);
logger.error('Migration failed:', error);
setState({
checked: true,
migrated: false,
@@ -244,7 +247,7 @@ export async function syncSettingsToServer(): Promise<boolean> {
const result = await api.settings.updateGlobal(updates);
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync settings:', error);
logger.error('Failed to sync settings:', error);
return false;
}
}
@@ -271,7 +274,7 @@ export async function syncCredentialsToServer(apiKeys: {
const result = await api.settings.updateCredentials({ apiKeys });
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync credentials:', error);
logger.error('Failed to sync credentials:', error);
return false;
}
}
@@ -312,7 +315,7 @@ export async function syncProjectSettingsToServer(
const result = await api.settings.updateProject(projectPath, updates);
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync project settings:', error);
logger.error('Failed to sync project settings:', error);
return false;
}
}
@@ -332,7 +335,7 @@ export async function loadMCPServersFromServer(): Promise<boolean> {
const result = await api.settings.getGlobal();
if (!result.success || !result.settings) {
console.error('[Settings Load] Failed to load settings:', result.error);
logger.error('Failed to load settings:', result.error);
return false;
}
@@ -344,10 +347,10 @@ export async function loadMCPServersFromServer(): Promise<boolean> {
// We need to update the store directly since we can't use hooks here
useAppStore.setState({ mcpServers, mcpAutoApproveTools, mcpUnrestrictedTools });
console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`);
logger.info(`Loaded ${mcpServers.length} MCP servers from server`);
return true;
} catch (error) {
console.error('[Settings Load] Failed to load MCP servers:', error);
logger.error('Failed to load MCP servers:', error);
return false;
}
}