Refactor logging in hooks, services, and routes to use centralized logger

- Replaced console.log and console.error statements with logger.info and logger.error in new-hook.ts, SDKAgent.ts, SessionManager.ts, and SessionRoutes.ts for consistent logging.
- Introduced log file creation and management in logger.ts, ensuring logs are saved to a file with a date-based naming convention.
- Enhanced error handling in logger to prevent crashes if log file operations fail.
This commit is contained in:
Alex Newman
2025-12-27 20:20:43 -05:00
parent b7d0664868
commit 356e3acae3
13 changed files with 226 additions and 185 deletions
+7 -22
View File
@@ -2,6 +2,7 @@ import { stdin } from 'process';
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
import { getProjectName } from '../utils/project-name.js';
import { logger } from '../utils/logger.js';
export interface UserPromptSubmitInput {
session_id: string;
@@ -24,19 +25,11 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
const { session_id, cwd, prompt } = input;
const project = getProjectName(cwd);
console.log('[NEW-HOOK] Received hook input:', {
session_id: session_id,
has_prompt: !!prompt,
cwd: cwd
});
logger.info('HOOK', 'new-hook: Received hook input', { session_id, has_prompt: !!prompt, cwd });
const port = getWorkerPort();
console.log('[NEW-HOOK] Calling /api/sessions/init:', {
claudeSessionId: session_id,
project,
prompt_length: prompt?.length
});
logger.info('HOOK', 'new-hook: Calling /api/sessions/init', { claudeSessionId: session_id, project, prompt_length: prompt?.length });
// Initialize session via HTTP - handles DB operations and privacy checks
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
@@ -58,30 +51,22 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
const sessionDbId = initResult.sessionDbId;
const promptNumber = initResult.promptNumber;
console.log('[NEW-HOOK] Received from /api/sessions/init:', {
sessionDbId: sessionDbId,
promptNumber: promptNumber,
skipped: initResult.skipped
});
logger.info('HOOK', 'new-hook: Received from /api/sessions/init', { sessionDbId, promptNumber, skipped: initResult.skipped });
// Check if prompt was entirely private (worker performs privacy check)
if (initResult.skipped && initResult.reason === 'private') {
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
logger.info('HOOK', `new-hook: Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
console.log(STANDARD_HOOK_RESPONSE);
return;
}
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
logger.info('HOOK', `new-hook: Session ${sessionDbId}, prompt #${promptNumber}`);
// Strip leading slash from commands for memory agent
// /review 101 → review 101 (more semantic for observations)
const cleanedPrompt = prompt.startsWith('/') ? prompt.substring(1) : prompt;
console.log('[NEW-HOOK] Calling /sessions/{sessionDbId}/init:', {
sessionDbId: sessionDbId,
promptNumber: promptNumber,
userPrompt_length: cleanedPrompt?.length
});
logger.info('HOOK', 'new-hook: Calling /sessions/{sessionDbId}/init', { sessionDbId, promptNumber, userPrompt_length: cleanedPrompt?.length });
// Initialize SDK agent session via HTTP (starts the agent!)
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
+2 -2
View File
@@ -64,7 +64,7 @@ export class SDKAgent {
// Create message generator (event-driven)
const messageGenerator = this.createMessageGenerator(session);
console.log('[SDK-AGENT] Starting SDK query with:', {
logger.info('SDK', 'Starting SDK query', {
sessionDbId: session.sessionDbId,
claudeSessionId: session.claudeSessionId,
resume_parameter: session.claudeSessionId,
@@ -205,7 +205,7 @@ export class SDKAgent {
// Build initial prompt
const isInitPrompt = session.lastPromptNumber === 1;
console.log('[SDK-AGENT] Creating message generator:', {
logger.info('SDK', 'Creating message generator', {
sessionDbId: session.sessionDbId,
claudeSessionId: session.claudeSessionId,
lastPromptNumber: session.lastPromptNumber,
+4 -4
View File
@@ -47,7 +47,7 @@ export class SessionManager {
* Initialize a new session or return existing one
*/
initializeSession(sessionDbId: number, currentUserPrompt?: string, promptNumber?: number): ActiveSession {
console.log('[SESSION-MANAGER] initializeSession called:', {
logger.info('SESSION', 'initializeSession called', {
sessionDbId,
promptNumber,
has_currentUserPrompt: !!currentUserPrompt
@@ -56,7 +56,7 @@ export class SessionManager {
// Check if already active
let session = this.sessions.get(sessionDbId);
if (session) {
console.log('[SESSION-MANAGER] Returning cached session:', {
logger.info('SESSION', 'Returning cached session', {
sessionDbId,
claudeSessionId: session.claudeSessionId,
lastPromptNumber: session.lastPromptNumber
@@ -98,7 +98,7 @@ export class SessionManager {
// Fetch from database
const dbSession = this.dbManager.getSessionById(sessionDbId);
console.log('[SESSION-MANAGER] Fetched session from database:', {
logger.info('SESSION', 'Fetched session from database', {
sessionDbId,
claude_session_id: dbSession.claude_session_id,
sdk_session_id: dbSession.sdk_session_id
@@ -141,7 +141,7 @@ export class SessionManager {
currentProvider: null // Will be set when generator starts
};
console.log('[SESSION-MANAGER] Creating new session object:', {
logger.info('SESSION', 'Creating new session object', {
sessionDbId,
claudeSessionId: dbSession.claude_session_id,
lastPromptNumber: promptNumber || this.dbManager.getSessionStore().getPromptNumberFromUserPrompts(dbSession.claude_session_id)
@@ -173,7 +173,7 @@ export class SessionRoutes extends BaseRouteHandler {
if (sessionDbId === null) return;
const { userPrompt, promptNumber } = req.body;
console.log('[SESSION-ROUTES] handleSessionInit called:', {
logger.info('HTTP', 'SessionRoutes: handleSessionInit called', {
sessionDbId,
promptNumber,
has_userPrompt: !!userPrompt
@@ -488,7 +488,7 @@ export class SessionRoutes extends BaseRouteHandler {
private handleSessionInitByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
const { claudeSessionId, project, prompt } = req.body;
console.log('[SESSION-ROUTES] handleSessionInitByClaudeId called:', {
logger.info('HTTP', 'SessionRoutes: handleSessionInitByClaudeId called', {
claudeSessionId,
project,
prompt_length: prompt?.length
@@ -504,7 +504,7 @@ export class SessionRoutes extends BaseRouteHandler {
// Step 1: Create/get SDK session (idempotent INSERT OR IGNORE)
const sessionDbId = store.createSDKSession(claudeSessionId, project, prompt);
console.log('[SESSION-ROUTES] createSDKSession returned:', {
logger.info('HTTP', 'SessionRoutes: createSDKSession returned', {
sessionDbId,
claudeSessionId
});
@@ -513,7 +513,7 @@ export class SessionRoutes extends BaseRouteHandler {
const currentCount = store.getPromptNumberFromUserPrompts(claudeSessionId);
const promptNumber = currentCount + 1;
console.log('[SESSION-ROUTES] Calculated promptNumber:', {
logger.info('HTTP', 'SessionRoutes: Calculated promptNumber', {
sessionDbId,
promptNumber,
currentCount
+52 -4
View File
@@ -4,6 +4,8 @@
*/
import { SettingsDefaultsManager } from '../shared/SettingsDefaultsManager.js';
import { appendFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
export enum LogLevel {
DEBUG = 0,
@@ -25,19 +27,55 @@ interface LogContext {
class Logger {
private level: LogLevel | null = null;
private useColor: boolean;
private logFilePath: string | null = null;
constructor() {
// Disable colors when output is not a TTY (e.g., PM2 logs)
this.useColor = process.stdout.isTTY ?? false;
this.initializeLogFile();
}
/**
* Lazy-load log level from settings (breaks circular dependency with SettingsDefaultsManager)
* Initialize log file path and ensure directory exists
*/
private initializeLogFile(): void {
try {
// Get data directory from settings
const dataDir = SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR');
const logsDir = join(dataDir, 'logs');
// Ensure logs directory exists
if (!existsSync(logsDir)) {
mkdirSync(logsDir, { recursive: true });
}
// Create log file path with date
const date = new Date().toISOString().split('T')[0];
this.logFilePath = join(logsDir, `claude-mem-${date}.log`);
} catch (error) {
// If log file initialization fails, just log to console
console.error('[LOGGER] Failed to initialize log file:', error);
this.logFilePath = null;
}
}
/**
* Lazy-load log level from settings file (not hardcoded defaults!)
*/
private getLevel(): LogLevel {
if (this.level === null) {
const envLevel = SettingsDefaultsManager.get('CLAUDE_MEM_LOG_LEVEL').toUpperCase();
this.level = LogLevel[envLevel as keyof typeof LogLevel] ?? LogLevel.INFO;
try {
// Load settings from file to get user's actual log level
const dataDir = SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR');
const settingsPath = join(dataDir, 'settings.json');
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
const envLevel = settings.CLAUDE_MEM_LOG_LEVEL.toUpperCase();
this.level = LogLevel[envLevel as keyof typeof LogLevel] ?? LogLevel.INFO;
} catch (error) {
// Fallback to INFO if settings can't be loaded
console.error('[LOGGER] Failed to load settings, using INFO level:', error);
this.level = LogLevel.INFO;
}
}
return this.level;
}
@@ -219,12 +257,22 @@ class Logger {
const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
// Output to appropriate stream
// Output to console
if (level === LogLevel.ERROR) {
console.error(logLine);
} else {
console.log(logLine);
}
// Output to log file
if (this.logFilePath) {
try {
appendFileSync(this.logFilePath, logLine + '\n', 'utf8');
} catch (error) {
// If file write fails, just continue (don't crash)
console.error('[LOGGER] Failed to write to log file:', error);
}
}
}
// Public logging methods