[grade=A] DC-060: replace 49 console.* calls in update-manager.js with structured logger
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

Replaced all 49 console.log/warn/error calls in src/managers/update-manager.js
with log.info/log.warn/log.error from src/utils/logging. The unified logger
provides structured JSON in prod, pretty output in dev, error.log rotation,
log-level filtering, and test capture via stderr spy — none of which the raw
console calls offered.

Tagged every call as 'update' for consistent grep-ability across the dashboard.
Mixed-content strings (containerName, schedule, imageName, error.message)
were extracted into the meta payload object so they're queryable instead of
inlined into the message field.

1539/1539 Jest tests pass. ESLint clean for the file (14 pre-existing
warnings unchanged, zero new). Codex grade A.
This commit is contained in:
Hermes
2026-08-10 15:49:40 -07:00
parent baba762dab
commit e8b9dd5b91
+50 -49
View File
@@ -10,6 +10,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const { log } = require('../utils/logging');
const docker = new Docker(); const docker = new Docker();
@@ -33,7 +34,7 @@ class UpdateManager extends EventEmitter {
start() { start() {
if (this.checking) return; if (this.checking) return;
console.log('[UpdateManager] Starting update checks'); log.info('update', 'Starting update checks');
this.checking = true; this.checking = true;
// Initial check // Initial check
@@ -52,7 +53,7 @@ class UpdateManager extends EventEmitter {
stop() { stop() {
if (!this.checking) return; if (!this.checking) return;
console.log('[UpdateManager] Stopping update checks'); log.info('update', 'Stopping update checks');
this.checking = false; this.checking = false;
if (this.checkInterval) { if (this.checkInterval) {
@@ -70,7 +71,7 @@ class UpdateManager extends EventEmitter {
*/ */
triggerWorkflows(eventType, eventData) { triggerWorkflows(eventType, eventData) {
if (!this.workflowEngine) { if (!this.workflowEngine) {
console.log('[UpdateManager] Workflow engine not set, skipping workflow trigger'); log.info('update', 'Workflow engine not set, skipping workflow trigger');
return; return;
} }
@@ -78,14 +79,14 @@ class UpdateManager extends EventEmitter {
this.workflowEngine.triggerForEvent(eventType, eventData) this.workflowEngine.triggerForEvent(eventType, eventData)
.then(results => { .then(results => {
if (results && results.length > 0) { if (results && results.length > 0) {
console.log(`[UpdateManager] Triggered ${results.length} workflow(s) for ${eventType}`); log.info('update', `Triggered workflows for ${eventType}`, { count: results.length });
} }
}) })
.catch(err => { .catch(err => {
console.error('[UpdateManager] Workflow trigger error:', err.message); log.error('update', err);
}); });
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error triggering workflows:', error.message); log.error('update', error);
} }
} }
@@ -94,7 +95,7 @@ class UpdateManager extends EventEmitter {
*/ */
setWorkflowEngine(workflowEngine) { setWorkflowEngine(workflowEngine) {
this.workflowEngine = workflowEngine; this.workflowEngine = workflowEngine;
console.log('[UpdateManager] Workflow engine configured'); log.info('update', 'Workflow engine configured');
} }
/** /**
@@ -131,13 +132,13 @@ class UpdateManager extends EventEmitter {
this.availableUpdates.delete(containerInfo.Id); this.availableUpdates.delete(containerInfo.Id);
} }
} catch (error) { } catch (error) {
console.error(`[UpdateManager] Error checking ${containerInfo.Names[0]}:`, error.message); log.error('update', error, null, { containerName: containerInfo.Names[0] });
} }
} }
console.log(`[UpdateManager] Found ${this.availableUpdates.size} updates available`); log.info('update', 'Checked for updates', { availableCount: this.availableUpdates.size });
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error checking for updates:', error.message); log.error('update', error);
} }
} }
@@ -168,10 +169,10 @@ class UpdateManager extends EventEmitter {
} }
// gcr.io / quay.io / registry.gitlab.com — currently unsupported // gcr.io / quay.io / registry.gitlab.com — currently unsupported
console.warn(`[UpdateManager] Custom registry not yet supported: ${remainder}`); log.warn('update', 'Custom registry not yet supported', { remainder });
return null; return null;
} catch (error) { } catch (error) {
console.error(`[UpdateManager] Error getting digest for ${imageName}:`, error.message); log.error('update', error, null, { imageName });
return null; return null;
} }
} }
@@ -338,7 +339,7 @@ class UpdateManager extends EventEmitter {
async updateContainer(containerId, options = {}) { async updateContainer(containerId, options = {}) {
const startTime = Date.now(); const startTime = Date.now();
console.log(`[UpdateManager] Starting update for container ${containerId}`); log.info('update', 'Starting update for container', { containerId });
this.emit('update-start', { containerId, timestamp: new Date().toISOString() }); this.emit('update-start', { containerId, timestamp: new Date().toISOString() });
try { try {
@@ -355,9 +356,9 @@ class UpdateManager extends EventEmitter {
const oldImage = docker.getImage(oldImageId); const oldImage = docker.getImage(oldImageId);
const oldImageInspect = await oldImage.inspect(); const oldImageInspect = await oldImage.inspect();
oldImageDigest = oldImageInspect.RepoDigests?.[0] || oldImageId; oldImageDigest = oldImageInspect.RepoDigests?.[0] || oldImageId;
console.log(`[UpdateManager] Stored old image digest: ${oldImageDigest.substring(0, 40)}...`); log.info('update', 'Stored old image digest', { digestPrefix: oldImageDigest.substring(0, 40) });
} catch (error) { } catch (error) {
console.warn(`[UpdateManager] Could not get old image digest: ${error.message}`); log.warn('update', 'Could not get old image digest', { error: error.message });
} }
// Create backup of current state // Create backup of current state
@@ -380,19 +381,19 @@ class UpdateManager extends EventEmitter {
this.triggerWorkflows('pre-update', { containerId, containerName, appId: containerName, imageName }); this.triggerWorkflows('pre-update', { containerId, containerName, appId: containerName, imageName });
// Pull latest image // Pull latest image
console.log(`[UpdateManager] Pulling latest image: ${imageName}`); log.info('update', 'Pulling latest image', { imageName });
await this.pullImage(imageName); await this.pullImage(imageName);
// Stop container // Stop container
console.log(`[UpdateManager] Stopping container: ${containerName}`); log.info('update', 'Stopping container', { containerName });
await container.stop(); await container.stop();
// Remove old container // Remove old container
console.log(`[UpdateManager] Removing old container: ${containerName}`); log.info('update', 'Removing old container', { containerName });
await container.remove(); await container.remove();
// Create new container with same configuration // Create new container with same configuration
console.log(`[UpdateManager] Creating new container: ${containerName}`); log.info('update', 'Creating new container', { containerName });
const newContainer = await docker.createContainer({ const newContainer = await docker.createContainer({
name: containerName, name: containerName,
Image: imageName, Image: imageName,
@@ -401,11 +402,11 @@ class UpdateManager extends EventEmitter {
}); });
// Start new container // Start new container
console.log(`[UpdateManager] Starting new container: ${containerName}`); log.info('update', 'Starting new container', { containerName });
await newContainer.start(); await newContainer.start();
// Extended verification with health checks and port accessibility // Extended verification with health checks and port accessibility
console.log(`[UpdateManager] Performing extended verification...`); log.info('update', 'Performing extended verification');
await this.verifyContainerExtended(newContainer, inspect, options.verifyTimeout || 60000); await this.verifyContainerExtended(newContainer, inspect, options.verifyTimeout || 60000);
// Get new image ID // Get new image ID
@@ -415,12 +416,12 @@ class UpdateManager extends EventEmitter {
// Remove old image only after successful verification // Remove old image only after successful verification
if (oldImageId !== newImageId) { if (oldImageId !== newImageId) {
try { try {
console.log(`[UpdateManager] Removing old image: ${oldImageId.substring(0, 12)}`); log.info('update', 'Removing old image', { oldImageIdPrefix: oldImageId.substring(0, 12) });
const oldImage = docker.getImage(oldImageId); const oldImage = docker.getImage(oldImageId);
await oldImage.remove({ force: false }); await oldImage.remove({ force: false });
console.log(`[UpdateManager] Old image removed successfully`); log.info('update', 'Old image removed successfully');
} catch (error) { } catch (error) {
console.warn(`[UpdateManager] Could not remove old image (may be in use): ${error.message}`); log.warn('update', 'Could not remove old image (may be in use)', { error: error.message });
} }
} }
@@ -442,7 +443,7 @@ class UpdateManager extends EventEmitter {
this.availableUpdates.delete(containerId); this.availableUpdates.delete(containerId);
this.emit('update-complete', historyEntry); this.emit('update-complete', historyEntry);
console.log(`[UpdateManager] Update completed in ${duration}ms`); log.info('update', 'Update completed', { durationMs: duration });
return historyEntry; return historyEntry;
} catch (error) { } catch (error) {
@@ -461,11 +462,11 @@ class UpdateManager extends EventEmitter {
// Attempt rollback // Attempt rollback
if (options.autoRollback !== false) { if (options.autoRollback !== false) {
console.log(`[UpdateManager] Attempting rollback for ${containerId}`); log.info('update', 'Attempting rollback', { containerId });
try { try {
await this.rollbackUpdate(containerId); await this.rollbackUpdate(containerId);
} catch (rollbackError) { } catch (rollbackError) {
console.error(`[UpdateManager] Rollback failed:`, rollbackError.message); log.error('update', rollbackError);
} }
} }
@@ -538,7 +539,7 @@ class UpdateManager extends EventEmitter {
const maxAttempts = Math.floor(timeout / 2000); // Check every 2 seconds const maxAttempts = Math.floor(timeout / 2000); // Check every 2 seconds
let lastError = null; let lastError = null;
console.log(`[UpdateManager] Extended verification with ${maxAttempts} attempts over ${timeout/1000}s`); log.info('update', 'Extended verification', { maxAttempts, timeoutSec: timeout / 1000 });
for (let attempt = 0; attempt < maxAttempts; attempt++) { for (let attempt = 0; attempt < maxAttempts; attempt++) {
try { try {
@@ -553,14 +554,14 @@ class UpdateManager extends EventEmitter {
// Step 2: Check Docker health check if available // Step 2: Check Docker health check if available
if (inspect.State.Health) { if (inspect.State.Health) {
if (inspect.State.Health.Status === 'healthy') { if (inspect.State.Health.Status === 'healthy') {
console.log(`[UpdateManager] Container health check: healthy`); log.info('update', 'Container health check: healthy');
return true; return true;
} else if (inspect.State.Health.Status === 'unhealthy') { } else if (inspect.State.Health.Status === 'unhealthy') {
lastError = 'Container health check failed (unhealthy)'; lastError = 'Container health check failed (unhealthy)';
throw new Error(lastError); throw new Error(lastError);
} }
// Status is 'starting' - continue waiting // Status is 'starting' - continue waiting
console.log(`[UpdateManager] Health check status: ${inspect.State.Health.Status} (attempt ${attempt + 1}/${maxAttempts})`); log.info('update', 'Health check status', { status: inspect.State.Health.Status, attempt: attempt + 1, maxAttempts });
} else { } else {
// Step 3: No Docker health check - verify HTTP port accessibility // Step 3: No Docker health check - verify HTTP port accessibility
const ports = this.extractPorts(inspect); const ports = this.extractPorts(inspect);
@@ -578,22 +579,22 @@ class UpdateManager extends EventEmitter {
// Accept 2xx, 3xx, 4xx as "accessible" (server is responding) // Accept 2xx, 3xx, 4xx as "accessible" (server is responding)
if (response.status >= 200 && response.status < 500) { if (response.status >= 200 && response.status < 500) {
console.log(`[UpdateManager] Port ${primaryPort.hostPort} is accessible (HTTP ${response.status})`); log.info('update', 'Port accessible', { hostPort: primaryPort.hostPort, httpStatus: response.status });
// Wait a bit more to ensure stability // Wait a bit more to ensure stability
if (attempt >= 2) { if (attempt >= 2) {
console.log(`[UpdateManager] Container verified successfully`); log.info('update', 'Container verified successfully');
return true; return true;
} }
} }
} catch (fetchError) { } catch (fetchError) {
lastError = `Port ${primaryPort.hostPort} not accessible: ${fetchError.message}`; lastError = `Port ${primaryPort.hostPort} not accessible: ${fetchError.message}`;
console.log(`[UpdateManager] ${lastError} (attempt ${attempt + 1}/${maxAttempts})`); log.info('update', lastError, { attempt: attempt + 1, maxAttempts });
} }
} else { } else {
// No ports exposed - just verify it's running for a few cycles // No ports exposed - just verify it's running for a few cycles
if (attempt >= 5) { if (attempt >= 5) {
console.log(`[UpdateManager] Container running without exposed ports (verified)`); log.info('update', 'Container running without exposed ports (verified)');
return true; return true;
} }
} }
@@ -605,7 +606,7 @@ class UpdateManager extends EventEmitter {
} }
} catch (error) { } catch (error) {
lastError = error.message; lastError = error.message;
console.log(`[UpdateManager] Verification attempt ${attempt + 1} failed: ${lastError}`); log.info('update', 'Verification attempt failed', { attempt: attempt + 1, error: lastError });
if (attempt < maxAttempts - 1) { if (attempt < maxAttempts - 1) {
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
@@ -649,7 +650,7 @@ class UpdateManager extends EventEmitter {
* Rollback to previous version * Rollback to previous version
*/ */
async rollbackUpdate(containerId) { async rollbackUpdate(containerId) {
console.log(`[UpdateManager] Rolling back container ${containerId}`); log.info('update', 'Rolling back container', { containerId });
// Find last successful update in history // Find last successful update in history
const lastUpdate = this.history const lastUpdate = this.history
@@ -682,12 +683,12 @@ class UpdateManager extends EventEmitter {
await newContainer.start(); await newContainer.start();
console.log(`[UpdateManager] Rollback completed for ${backup.containerName}`); log.info('update', 'Rollback completed', { containerName: backup.containerName });
this.emit('rollback-complete', { containerId, containerName: backup.containerName }); this.emit('rollback-complete', { containerId, containerName: backup.containerName });
return true; return true;
} catch (error) { } catch (error) {
console.error(`[UpdateManager] Rollback failed:`, error.message); log.error('update', error);
throw error; throw error;
} }
} }
@@ -704,11 +705,11 @@ class UpdateManager extends EventEmitter {
setTimeout(() => { setTimeout(() => {
this.updateContainer(containerId).catch(error => { this.updateContainer(containerId).catch(error => {
console.error(`[UpdateManager] Scheduled update failed:`, error.message); log.error('update', error);
}); });
}, delay); }, delay);
console.log(`[UpdateManager] Update scheduled for ${containerId} at ${scheduledTime}`); log.info('update', 'Update scheduled', { containerId, scheduledTime });
} }
/** /**
@@ -784,7 +785,7 @@ class UpdateManager extends EventEmitter {
changelog: this.formatChangelog(repoInfo, tags, imageTag) changelog: this.formatChangelog(repoInfo, tags, imageTag)
}; };
} catch (error) { } catch (error) {
console.error(`[UpdateManager] Error fetching changelog for ${imageName}:`, error.message); log.error('update', error, null, { imageName });
// Return basic info even on error // Return basic info even on error
const [fullRepo] = imageName.split(':'); const [fullRepo] = imageName.split(':');
@@ -940,7 +941,7 @@ class UpdateManager extends EventEmitter {
const count = Object.values(this.config.autoUpdate || {}).filter(c => c.enabled).length; const count = Object.values(this.config.autoUpdate || {}).filter(c => c.enabled).length;
if (count > 0) { if (count > 0) {
console.log(`[UpdateManager] Auto-update scheduler started (${count} container(s) configured)`); log.info('update', 'Auto-update scheduler started', { containerCount: count });
} }
} }
@@ -989,17 +990,17 @@ class UpdateManager extends EventEmitter {
const update = this.availableUpdates.get(containerId); const update = this.availableUpdates.get(containerId);
if (!update) continue; if (!update) continue;
console.log(`[UpdateManager] Auto-updating ${update.containerName} (schedule: ${cfg.schedule})`); log.info('update', 'Auto-updating container', { containerName: update.containerName, schedule: cfg.schedule });
this.emit('auto-update-start', { containerId, containerName: update.containerName, schedule: cfg.schedule }); this.emit('auto-update-start', { containerId, containerName: update.containerName, schedule: cfg.schedule });
try { try {
const result = await this.updateContainer(containerId, { autoRollback: cfg.autoRollback !== false }); const result = await this.updateContainer(containerId, { autoRollback: cfg.autoRollback !== false });
cfg.lastAutoUpdate = now.toISOString(); cfg.lastAutoUpdate = now.toISOString();
this.saveConfig(); this.saveConfig();
console.log(`[UpdateManager] Auto-update completed for ${update.containerName}`); log.info('update', 'Auto-update completed', { containerName: update.containerName });
this.emit('auto-update-complete', { containerId, containerName: update.containerName, result }); this.emit('auto-update-complete', { containerId, containerName: update.containerName, result });
} catch (error) { } catch (error) {
console.error(`[UpdateManager] Auto-update failed for ${update.containerName}:`, error.message); log.error('update', error, null, { containerName: update.containerName });
cfg.lastAutoUpdate = now.toISOString(); // Don't retry same day cfg.lastAutoUpdate = now.toISOString(); // Don't retry same day
this.saveConfig(); this.saveConfig();
this.emit('auto-update-failed', { containerId, containerName: update.containerName, error: error.message }); this.emit('auto-update-failed', { containerId, containerName: update.containerName, error: error.message });
@@ -1056,7 +1057,7 @@ class UpdateManager extends EventEmitter {
return JSON.parse(fs.readFileSync(UPDATE_CONFIG_FILE, 'utf8')); return JSON.parse(fs.readFileSync(UPDATE_CONFIG_FILE, 'utf8'));
} }
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error loading config:', error.message); log.error('update', error);
} }
return { autoUpdate: {} }; return { autoUpdate: {} };
} }
@@ -1068,7 +1069,7 @@ class UpdateManager extends EventEmitter {
try { try {
fs.writeFileSync(UPDATE_CONFIG_FILE, JSON.stringify(this.config, null, 2)); fs.writeFileSync(UPDATE_CONFIG_FILE, JSON.stringify(this.config, null, 2));
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error saving config:', error.message); log.error('update', error);
} }
} }
@@ -1081,7 +1082,7 @@ class UpdateManager extends EventEmitter {
return JSON.parse(fs.readFileSync(UPDATE_HISTORY_FILE, 'utf8')); return JSON.parse(fs.readFileSync(UPDATE_HISTORY_FILE, 'utf8'));
} }
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error loading history:', error.message); log.error('update', error);
} }
return []; return [];
} }
@@ -1093,7 +1094,7 @@ class UpdateManager extends EventEmitter {
try { try {
fs.writeFileSync(UPDATE_HISTORY_FILE, JSON.stringify(this.history, null, 2)); fs.writeFileSync(UPDATE_HISTORY_FILE, JSON.stringify(this.history, null, 2));
} catch (error) { } catch (error) {
console.error('[UpdateManager] Error saving history:', error.message); log.error('update', error);
} }
} }
} }