feat: implement structured logging across server components

- Integrated a centralized logging system using createLogger from @automaker/utils, replacing console.log and console.error statements with logger methods for consistent log formatting and improved readability.
- Updated various modules, including auth, events, and services, to utilize the new logging system, enhancing error tracking and operational visibility.
- Refactored logging messages to provide clearer context and information, ensuring better maintainability and debugging capabilities.

This update significantly enhances the observability of the server components, facilitating easier troubleshooting and monitoring.
This commit is contained in:
Shirone
2026-01-02 15:40:15 +01:00
parent 8c04e0028f
commit 96a999817f
23 changed files with 284 additions and 275 deletions

View File

@@ -22,7 +22,7 @@ export function createSendHandler(agentService: AgentService) {
thinkingLevel?: ThinkingLevel;
};
console.log('[Send Handler] Received request:', {
logger.debug('Received request:', {
sessionId,
messageLength: message?.length,
workingDirectory,
@@ -32,7 +32,7 @@ export function createSendHandler(agentService: AgentService) {
});
if (!sessionId || !message) {
console.log('[Send Handler] ERROR: Validation failed - missing sessionId or message');
logger.warn('Validation failed - missing sessionId or message');
res.status(400).json({
success: false,
error: 'sessionId and message are required',
@@ -40,7 +40,7 @@ export function createSendHandler(agentService: AgentService) {
return;
}
console.log('[Send Handler] Validation passed, calling agentService.sendMessage()');
logger.debug('Validation passed, calling agentService.sendMessage()');
// Start the message processing (don't await - it streams via WebSocket)
agentService
@@ -53,16 +53,16 @@ export function createSendHandler(agentService: AgentService) {
thinkingLevel,
})
.catch((error) => {
console.error('[Send Handler] ERROR: Background error in sendMessage():', error);
logger.error('Background error in sendMessage():', error);
logError(error, 'Send message failed (background)');
});
console.log('[Send Handler] Returning immediate response to client');
logger.debug('Returning immediate response to client');
// Return immediately - responses come via WebSocket
res.json({ success: true, message: 'Message sent' });
} catch (error) {
console.error('[Send Handler] ERROR: Synchronous error:', error);
logger.error('Synchronous error:', error);
logError(error, 'Send message failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}

View File

@@ -31,7 +31,7 @@ export function createResumeFeatureHandler(autoModeService: AutoModeService) {
autoModeService
.resumeFeature(projectPath, featureId, useWorktrees ?? false)
.catch((error) => {
logger.error(`[AutoMode] Resume feature ${featureId} error:`, error);
logger.error(`Resume feature ${featureId} error:`, error);
});
res.json({ success: true });

View File

@@ -31,7 +31,7 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) {
autoModeService
.executeFeature(projectPath, featureId, useWorktrees ?? false, false)
.catch((error) => {
logger.error(`[AutoMode] Feature ${featureId} error:`, error);
logger.error(`Feature ${featureId} error:`, error);
})
.finally(() => {
// Release the starting slot when execution completes (success or error)

View File

@@ -1,5 +1,8 @@
import { Router, Request, Response } from 'express';
import { ClaudeUsageService } from '../../services/claude-usage-service.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('Claude');
export function createClaudeRoutes(service: ClaudeUsageService): Router {
const router = Router();
@@ -33,7 +36,7 @@ export function createClaudeRoutes(service: ClaudeUsageService): Router {
message: 'The Claude CLI took too long to respond',
});
} else {
console.error('Error fetching usage:', error);
logger.error('Error fetching usage:', error);
res.status(500).json({ error: message });
}
}

View File

@@ -97,7 +97,7 @@ export function createDescribeFileHandler(
return;
}
logger.info(`[DescribeFile] Starting description generation for: ${filePath}`);
logger.info(`Starting description generation for: ${filePath}`);
// Resolve the path for logging and cwd derivation
const resolvedPath = secureFs.resolvePath(filePath);
@@ -112,7 +112,7 @@ export function createDescribeFileHandler(
} catch (readError) {
// Path not allowed - return 403 Forbidden
if (readError instanceof PathNotAllowedError) {
logger.warn(`[DescribeFile] Path not allowed: ${filePath}`);
logger.warn(`Path not allowed: ${filePath}`);
const response: DescribeFileErrorResponse = {
success: false,
error: 'File path is not within the allowed directory',
@@ -128,7 +128,7 @@ export function createDescribeFileHandler(
'code' in readError &&
readError.code === 'ENOENT'
) {
logger.warn(`[DescribeFile] File not found: ${resolvedPath}`);
logger.warn(`File not found: ${resolvedPath}`);
const response: DescribeFileErrorResponse = {
success: false,
error: `File not found: ${filePath}`,
@@ -138,7 +138,7 @@ export function createDescribeFileHandler(
}
const errorMessage = readError instanceof Error ? readError.message : 'Unknown error';
logger.error(`[DescribeFile] Failed to read file: ${errorMessage}`);
logger.error(`Failed to read file: ${errorMessage}`);
const response: DescribeFileErrorResponse = {
success: false,
error: `Failed to read file: ${errorMessage}`,
@@ -182,23 +182,20 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`;
// Get model from phase settings
const settings = await settingsService?.getGlobalSettings();
logger.info(
`[DescribeFile] Raw phaseModels from settings:`,
JSON.stringify(settings?.phaseModels, null, 2)
);
logger.info(`Raw phaseModels from settings:`, JSON.stringify(settings?.phaseModels, null, 2));
const phaseModelEntry =
settings?.phaseModels?.fileDescriptionModel || DEFAULT_PHASE_MODELS.fileDescriptionModel;
logger.info(`[DescribeFile] fileDescriptionModel entry:`, JSON.stringify(phaseModelEntry));
logger.info(`fileDescriptionModel entry:`, JSON.stringify(phaseModelEntry));
const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
logger.info(`[DescribeFile] Resolved model: ${model}, thinkingLevel: ${thinkingLevel}`);
logger.info(`Resolved model: ${model}, thinkingLevel: ${thinkingLevel}`);
let description: string;
// Route to appropriate provider based on model type
if (isCursorModel(model)) {
// Use Cursor provider for Cursor models
logger.info(`[DescribeFile] Using Cursor provider for model: ${model}`);
logger.info(`Using Cursor provider for model: ${model}`);
const provider = ProviderFactory.getProviderForModel(model);
@@ -225,7 +222,7 @@ File: ${fileName}${truncated ? ' (truncated)' : ''}`;
description = responseText;
} else {
// Use Claude SDK for Claude models
logger.info(`[DescribeFile] Using Claude SDK for model: ${model}`);
logger.info(`Using Claude SDK for model: ${model}`);
// Use centralized SDK options with proper cwd validation
// No tools needed since we're passing file content directly

View File

@@ -4,6 +4,9 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import { createLogger } from '@automaker/utils';
const logger = createLogger('GitHub');
export const execAsync = promisify(exec);
@@ -31,5 +34,5 @@ export function getErrorMessage(error: unknown): string {
}
export function logError(error: unknown, context: string): void {
console.error(`[GitHub] ${context}:`, error);
logger.error(`${context}:`, error);
}

View File

@@ -6,6 +6,9 @@ import { spawn } from 'child_process';
import type { Request, Response } from 'express';
import { execAsync, execEnv, getErrorMessage, logError } from './common.js';
import { checkGitHubRemote } from './check-github-remote.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('ListIssues');
export interface GitHubLabel {
name: string;
@@ -179,7 +182,7 @@ async function fetchLinkedPRs(
}
} catch (error) {
// If GraphQL fails, continue without linked PRs
console.warn(
logger.warn(
'Failed to fetch linked PRs via GraphQL:',
error instanceof Error ? error.message : error
);

View File

@@ -2,6 +2,10 @@
* Common utilities for MCP routes
*/
import { createLogger } from '@automaker/utils';
const logger = createLogger('MCP');
/**
* Extract error message from unknown error
*/
@@ -16,5 +20,5 @@ export function getErrorMessage(error: unknown): string {
* Log error with prefix
*/
export function logError(error: unknown, message: string): void {
console.error(`[MCP] ${message}:`, error);
logger.error(`${message}:`, error);
}

View File

@@ -8,6 +8,9 @@
import * as secureFs from '../../../lib/secure-fs.js';
import path from 'path';
import { getBranchTrackingPath, ensureAutomakerDir } from '@automaker/platform';
import { createLogger } from '@automaker/utils';
const logger = createLogger('BranchTracking');
export interface TrackedBranch {
name: string;
@@ -32,7 +35,7 @@ export async function getTrackedBranches(projectPath: string): Promise<TrackedBr
if (error.code === 'ENOENT') {
return [];
}
console.warn('[branch-tracking] Failed to read tracked branches:', error);
logger.warn('Failed to read tracked branches:', error);
return [];
}
}
@@ -65,7 +68,7 @@ export async function trackBranch(projectPath: string, branchName: string): Prom
});
await saveTrackedBranches(projectPath, branches);
console.log(`[branch-tracking] Now tracking branch: ${branchName}`);
logger.info(`Now tracking branch: ${branchName}`);
}
/**
@@ -77,7 +80,7 @@ export async function untrackBranch(projectPath: string, branchName: string): Pr
if (filtered.length !== branches.length) {
await saveTrackedBranches(projectPath, filtered);
console.log(`[branch-tracking] Stopped tracking branch: ${branchName}`);
logger.info(`Stopped tracking branch: ${branchName}`);
}
}

View File

@@ -12,6 +12,9 @@ import {
isGhCliAvailable,
} from '../common.js';
import { updateWorktreePRInfo } from '../../../lib/worktree-metadata.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('CreatePR');
export function createCreatePRHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -56,15 +59,15 @@ export function createCreatePRHandler() {
}
// Check for uncommitted changes
console.log(`[CreatePR] Checking for uncommitted changes in: ${worktreePath}`);
logger.debug(`Checking for uncommitted changes in: ${worktreePath}`);
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: worktreePath,
env: execEnv,
});
const hasChanges = status.trim().length > 0;
console.log(`[CreatePR] Has uncommitted changes: ${hasChanges}`);
logger.debug(`Has uncommitted changes: ${hasChanges}`);
if (hasChanges) {
console.log(`[CreatePR] Changed files:\n${status}`);
logger.debug(`Changed files:\n${status}`);
}
// If there are changes, commit them
@@ -72,15 +75,15 @@ export function createCreatePRHandler() {
let commitError: string | null = null;
if (hasChanges) {
const message = commitMessage || `Changes from ${branchName}`;
console.log(`[CreatePR] Committing changes with message: ${message}`);
logger.debug(`Committing changes with message: ${message}`);
try {
// Stage all changes
console.log(`[CreatePR] Running: git add -A`);
logger.debug(`Running: git add -A`);
await execAsync('git add -A', { cwd: worktreePath, env: execEnv });
// Create commit
console.log(`[CreatePR] Running: git commit`);
logger.debug(`Running: git commit`);
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
cwd: worktreePath,
env: execEnv,
@@ -92,11 +95,11 @@ export function createCreatePRHandler() {
env: execEnv,
});
commitHash = hashOutput.trim().substring(0, 8);
console.log(`[CreatePR] Commit successful: ${commitHash}`);
logger.info(`Commit successful: ${commitHash}`);
} catch (commitErr: unknown) {
const err = commitErr as { stderr?: string; message?: string };
commitError = err.stderr || err.message || 'Commit failed';
console.error(`[CreatePR] Commit failed: ${commitError}`);
logger.error(`Commit failed: ${commitError}`);
// Return error immediately - don't proceed with push/PR if commit fails
res.status(500).json({
@@ -126,7 +129,7 @@ export function createCreatePRHandler() {
// Capture push error for reporting
const err = error2 as { stderr?: string; message?: string };
pushError = err.stderr || err.message || 'Push failed';
console.error('[CreatePR] Push failed:', pushError);
logger.error('Push failed:', pushError);
}
}
@@ -246,26 +249,22 @@ export function createCreatePRHandler() {
const headRef = upstreamRepo && originOwner ? `${originOwner}:${branchName}` : branchName;
const repoArg = upstreamRepo ? ` --repo "${upstreamRepo}"` : '';
console.log(
`[CreatePR] Checking for existing PR for branch: ${branchName} (headRef: ${headRef})`
);
logger.debug(`Checking for existing PR for branch: ${branchName} (headRef: ${headRef})`);
try {
const listCmd = `gh pr list${repoArg} --head "${headRef}" --json number,title,url,state --limit 1`;
console.log(`[CreatePR] Running: ${listCmd}`);
logger.debug(`Running: ${listCmd}`);
const { stdout: existingPrOutput } = await execAsync(listCmd, {
cwd: worktreePath,
env: execEnv,
});
console.log(`[CreatePR] gh pr list output: ${existingPrOutput}`);
logger.debug(`gh pr list output: ${existingPrOutput}`);
const existingPrs = JSON.parse(existingPrOutput);
if (Array.isArray(existingPrs) && existingPrs.length > 0) {
const existingPr = existingPrs[0];
// PR already exists - use it and store metadata
console.log(
`[CreatePR] PR already exists for branch ${branchName}: PR #${existingPr.number}`
);
logger.info(`PR already exists for branch ${branchName}: PR #${existingPr.number}`);
prUrl = existingPr.url;
prNumber = existingPr.number;
prAlreadyExisted = true;
@@ -278,15 +277,15 @@ export function createCreatePRHandler() {
state: existingPr.state || 'open',
createdAt: new Date().toISOString(),
});
console.log(
`[CreatePR] Stored existing PR info for branch ${branchName}: PR #${existingPr.number}`
logger.debug(
`Stored existing PR info for branch ${branchName}: PR #${existingPr.number}`
);
} else {
console.log(`[CreatePR] No existing PR found for branch ${branchName}`);
logger.debug(`No existing PR found for branch ${branchName}`);
}
} catch (listError) {
// gh pr list failed - log but continue to try creating
console.log(`[CreatePR] gh pr list failed (this is ok, will try to create):`, listError);
logger.debug(`gh pr list failed (this is ok, will try to create):`, listError);
}
// Only create a new PR if one doesn't already exist
@@ -307,13 +306,13 @@ export function createCreatePRHandler() {
prCmd += ` --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}" ${draftFlag}`;
prCmd = prCmd.trim();
console.log(`[CreatePR] Creating PR with command: ${prCmd}`);
logger.debug(`Creating PR with command: ${prCmd}`);
const { stdout: prOutput } = await execAsync(prCmd, {
cwd: worktreePath,
env: execEnv,
});
prUrl = prOutput.trim();
console.log(`[CreatePR] PR created: ${prUrl}`);
logger.info(`PR created: ${prUrl}`);
// Extract PR number and store metadata for newly created PR
if (prUrl) {
@@ -329,11 +328,9 @@ export function createCreatePRHandler() {
state: draft ? 'draft' : 'open',
createdAt: new Date().toISOString(),
});
console.log(
`[CreatePR] Stored PR info for branch ${branchName}: PR #${prNumber}`
);
logger.debug(`Stored PR info for branch ${branchName}: PR #${prNumber}`);
} catch (metadataError) {
console.error('[CreatePR] Failed to store PR metadata:', metadataError);
logger.error('Failed to store PR metadata:', metadataError);
}
}
}
@@ -341,11 +338,11 @@ export function createCreatePRHandler() {
// gh CLI failed - check if it's "already exists" error and try to fetch the PR
const err = ghError as { stderr?: string; message?: string };
const errorMessage = err.stderr || err.message || 'PR creation failed';
console.log(`[CreatePR] gh pr create failed: ${errorMessage}`);
logger.debug(`gh pr create failed: ${errorMessage}`);
// If error indicates PR already exists, try to fetch it
if (errorMessage.toLowerCase().includes('already exists')) {
console.log(`[CreatePR] PR already exists error - trying to fetch existing PR`);
logger.debug(`PR already exists error - trying to fetch existing PR`);
try {
const { stdout: viewOutput } = await execAsync(
`gh pr view --json number,title,url,state`,
@@ -364,10 +361,10 @@ export function createCreatePRHandler() {
state: existingPr.state || 'open',
createdAt: new Date().toISOString(),
});
console.log(`[CreatePR] Fetched and stored existing PR: #${existingPr.number}`);
logger.debug(`Fetched and stored existing PR: #${existingPr.number}`);
}
} catch (viewError) {
console.error('[CreatePR] Failed to fetch existing PR:', viewError);
logger.error('Failed to fetch existing PR:', viewError);
prError = errorMessage;
}
} else {

View File

@@ -20,6 +20,9 @@ import {
ensureInitialCommit,
} from '../common.js';
import { trackBranch } from './branch-tracking.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('Worktree');
const execAsync = promisify(exec);
@@ -114,8 +117,8 @@ export function createCreateHandler() {
if (existingWorktree) {
// Worktree already exists, return it as success (not an error)
// This handles manually created worktrees or worktrees from previous runs
console.log(
`[Worktree] Found existing worktree for branch "${branchName}" at: ${existingWorktree.path}`
logger.info(
`Found existing worktree for branch "${branchName}" at: ${existingWorktree.path}`
);
// Track the branch so it persists in the UI

View File

@@ -11,6 +11,9 @@ import {
isValidBranchName,
isGhCliAvailable,
} from '../common.js';
import { createLogger } from '@automaker/utils';
const logger = createLogger('PRInfo');
export interface PRComment {
id: number;
@@ -174,7 +177,7 @@ export function createPRInfoHandler() {
})
);
} catch (error) {
console.warn('[PRInfo] Failed to fetch PR comments:', error);
logger.warn('Failed to fetch PR comments:', error);
}
// Get review comments (inline code comments)
@@ -209,10 +212,10 @@ export function createPRInfoHandler() {
})
);
} catch (error) {
console.warn('[PRInfo] Failed to fetch review comments:', error);
logger.warn('Failed to fetch review comments:', error);
}
} else {
console.warn('[PRInfo] Cannot fetch review comments: repository info not available');
logger.warn('Cannot fetch review comments: repository info not available');
}
const prInfo: PRInfo = {