diff --git a/backend/src/routes/stacks.ts b/backend/src/routes/stacks.ts index 7e666cdd..22cc56db 100644 --- a/backend/src/routes/stacks.ts +++ b/backend/src/routes/stacks.ts @@ -30,6 +30,15 @@ import { getTerminalWs } from '../websocket/generic'; // adversarial) file cannot exhaust heap during an env or service lookup. const MAX_COMPOSE_PARSE_BYTES = 1_048_576; // 1 MiB +/** + * Diagnostic-mode log helper. Wraps console.log so non-essential informational + * lines (deploys completed, files saved, stacks created) stay silent in + * production and only appear when developer_mode is on. console.warn and + * console.error remain unconditional - they are operational signal, not noise. + */ +function dlog(...args: Parameters): void { + if (isDebugEnabled()) console.log(...args); +} function notifyActionFailure(action: string, stackName: string, error: unknown): void { const message = getErrorMessage(error, `Failed to ${action} stack`); NotificationService.getInstance() @@ -480,7 +489,7 @@ stacksRouter.put('/:stackName', async (req: Request, res: Response) => { }); } invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Compose file saved: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Compose file saved: ${sanitizeForLog(stackName)}`); res.setHeader('ETag', stackFileEtag(result.mtimeMs)); res.json({ message: 'Stack saved successfully', mtimeMs: result.mtimeMs }); } catch (error) { @@ -602,7 +611,7 @@ stacksRouter.put('/:stackName/env', async (req: Request, res: Response) => { } invalidateNodeCaches(req.nodeId); const envFileName = path.basename(envPath); - console.log(`[Stacks] Env file saved: ${sanitizeForLog(stackName)}/${sanitizeForLog(envFileName)}`); + dlog(`[Stacks] Env file saved: ${sanitizeForLog(stackName)}/${sanitizeForLog(envFileName)}`); res.setHeader('ETag', stackFileEtag(result.mtimeMs)); res.json({ message: 'Env file saved successfully', mtimeMs: result.mtimeMs }); } catch (error) { @@ -623,7 +632,7 @@ stacksRouter.post('/', async (req: Request, res: Response) => { } await FileSystemService.getInstance(req.nodeId).createStack(stackName); invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Stack created: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Stack created: ${sanitizeForLog(stackName)}`); res.json({ message: 'Stack created successfully', name: stackName }); } catch (error: unknown) { const message = getErrorMessage(error, ''); @@ -721,7 +730,7 @@ stacksRouter.post('/from-git', async (req: Request, res: Response) => { : null; if (fromGitDiag) { - console.log( + dlog( `[Stacks:diag] from-git start stack=${sanitizeForLog(stack_name)} nodeId=${req.nodeId ?? 'local'} host=${sanitizeForLog(gitRepoHost(repo_url))} branch=${sanitizeForLog(branch)} composePath=${sanitizeForLog(compose_path)} envPath=${sanitizeForLog(resolvedEnvPath ?? 'none')} authType=${sanitizeForLog(resolvedAuthType)} autoApplyOnWebhook=${autoApplyOnWebhook} autoDeployOnApply=${autoDeployOnApply} deployNow=${deploy_now === true}` ); } @@ -763,9 +772,9 @@ stacksRouter.post('/from-git', async (req: Request, res: Response) => { } } - console.log(`[Stacks] Stack created from Git: ${sanitizeForLog(stack_name)} at ${result.commitSha.slice(0, 7)}`); + dlog(`[Stacks] Stack created from Git: ${sanitizeForLog(stack_name)} at ${result.commitSha.slice(0, 7)}`); if (fromGitDiag) { - console.log( + dlog( `[Stacks:diag] from-git ok stack=${sanitizeForLog(stack_name)} sha=${result.commitSha.slice(0, 7)} deployed=${deployed} envWritten=${result.envWritten} warnings=${result.warnings.length} elapsedMs=${Date.now() - fromGitStartedAt}` ); } @@ -786,7 +795,7 @@ stacksRouter.post('/from-git', async (req: Request, res: Response) => { } catch (error) { if (fromGitDiag) { const code = error instanceof GitSourceError ? error.code : 'UNKNOWN'; - console.log( + dlog( `[Stacks:diag] from-git fail stack=${sanitizeForLog(fromGitStackName)} code=${code} elapsedMs=${Date.now() - fromGitStartedAt}` ); } @@ -816,7 +825,7 @@ stacksRouter.delete('/:stackName', async (req: Request, res: Response) => { if (pruneVolumes) { try { const result = await DockerController.getInstance().pruneManagedOnly('volumes', [stackName]); - console.log(`[Stacks] Pruned volumes for ${sanitizeForLog(stackName)}: ${result.reclaimedBytes} bytes reclaimed`); + dlog(`[Stacks] Pruned volumes for ${sanitizeForLog(stackName)}: ${result.reclaimedBytes} bytes reclaimed`); } catch (pruneErr) { console.warn('[Stacks] Volume prune failed for %s, continuing delete:', sanitizeForLog(stackName), pruneErr); } @@ -873,7 +882,7 @@ stacksRouter.delete('/:stackName', async (req: Request, res: Response) => { } invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Stack deleted: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Stack deleted: ${sanitizeForLog(stackName)}`); res.json({ success: true }); }); @@ -930,7 +939,7 @@ stacksRouter.post('/:stackName/deploy', async (req: Request, res: Response) => { const t0 = Date.now(); await ComposeService.getInstance(req.nodeId).deployStack(stackName, getTerminalWs(), atomic); invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Deploy completed: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Deploy completed: ${sanitizeForLog(stackName)}`); if (debug) console.debug(`[Stacks:debug] Deploy finished in ${Date.now() - t0}ms`); res.json({ message: 'Deployed successfully' }); notifyActionSuccess('deploy_success', `${stackName} deployed`, stackName, req.user?.username ?? 'system'); @@ -972,7 +981,7 @@ stacksRouter.post('/:stackName/down', async (req: Request, res: Response) => { if (isDebugEnabled()) console.debug(`[Stacks:debug] Down starting`, { stackName: sanitizeForLog(stackName), nodeId: req.nodeId }); await ComposeService.getInstance(req.nodeId).runCommand(stackName, 'down', getTerminalWs()); invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Down completed: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Down completed: ${sanitizeForLog(stackName)}`); res.json({ status: 'Command started' }); } catch (error: unknown) { console.error('[Stacks] Down failed: %s', sanitizeForLog(stackName), error); @@ -1080,7 +1089,7 @@ async function bulkContainerOp( } invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] ${titleCase} completed: ${sanitizeForLog(stackName)} (${outcome.count} containers)`); + dlog(`[Stacks] ${titleCase} completed: ${sanitizeForLog(stackName)} (${outcome.count} containers)`); res.json({ success: true, message: `${titleCase} completed via Engine API.` }); const { category, pastTense } = CONTAINER_ACTION_META[action]; notifyActionSuccess(category, `${stackName} ${pastTense}`, stackName, req.user?.username ?? 'system'); @@ -1132,7 +1141,7 @@ async function handleServiceAction( : (id: string) => dockerController.restartContainer(id); await Promise.all(matching.map(c => op(c.Id))); invalidateNodeCaches(req.nodeId); - console.log( + dlog( `[Stacks] Service ${sanitizeForLog(action)} completed: ${sanitizeForLog(stackName)}/${sanitizeForLog(serviceName)} (${matching.length} containers)`, ); res.json({ @@ -1188,7 +1197,7 @@ stacksRouter.post('/:stackName/update', async (req: Request, res: Response) => { action: 'stack-updated', ts: Date.now(), }); - console.log(`[Stacks] Update completed: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Update completed: ${sanitizeForLog(stackName)}`); if (debug) console.debug(`[Stacks:debug] Update finished in ${Date.now() - t0}ms`); res.json({ status: 'Update completed' }); notifyActionSuccess('image_update_applied', `${stackName} updated`, stackName, req.user?.username ?? 'system'); @@ -1229,12 +1238,12 @@ stacksRouter.post('/:stackName/rollback', async (req: Request, res: Response) => if (!backupInfo.exists) { return res.status(404).json({ error: 'No backup available for this stack.' }); } - console.log(`[Stacks] Rollback initiated: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Rollback initiated: ${sanitizeForLog(stackName)}`); await fsSvc.restoreStackFiles(stackName); if (!(await runPolicyGate(req, res, stackName, req.nodeId))) return; await ComposeService.getInstance(req.nodeId).deployStack(stackName, getTerminalWs(), false); invalidateNodeCaches(req.nodeId); - console.log(`[Stacks] Rollback completed: ${sanitizeForLog(stackName)}`); + dlog(`[Stacks] Rollback completed: ${sanitizeForLog(stackName)}`); res.json({ message: 'Stack rolled back successfully.' }); } catch (error: unknown) { console.error('[Stacks] Rollback failed: %s', sanitizeForLog(stackName), error); diff --git a/backend/src/services/DockerController.ts b/backend/src/services/DockerController.ts index 825ee3c4..e5a2b5c4 100644 --- a/backend/src/services/DockerController.ts +++ b/backend/src/services/DockerController.ts @@ -1438,7 +1438,7 @@ class DockerController { } if (isDebugEnabled()) console.debug('[Exec:diag] Creating exec', { containerId, shell: shellType }); - console.log('[Exec] Shell session started', { containerId, shell: shellType }); + if (isDebugEnabled()) console.log('[Exec] Shell session started', { containerId, shell: shellType }); // --- Downstream: container output → client --- stream.on('data', (chunk: Buffer) => { @@ -1452,7 +1452,7 @@ class DockerController { }); stream.on('end', () => { - console.log('[Exec] Shell session ended', { containerId, reason: 'stream-end' }); + if (isDebugEnabled()) console.log('[Exec] Shell session ended', { containerId, reason: 'stream-end' }); if (ws.readyState === WebSocket.OPEN) { ws.close(); } @@ -1492,7 +1492,7 @@ class DockerController { // --- Cleanup: prevent zombie processes --- ws.on('close', () => { - console.log('[Exec] Shell session ended', { containerId, reason: 'ws-close' }); + if (isDebugEnabled()) console.log('[Exec] Shell session ended', { containerId, reason: 'ws-close' }); try { stream.destroy(); } catch (e) {