diff --git a/dashcaddy-api/__tests__/error-handler.test.js b/dashcaddy-api/__tests__/error-handler.test.js index f7f54b8..34a53ab 100644 --- a/dashcaddy-api/__tests__/error-handler.test.js +++ b/dashcaddy-api/__tests__/error-handler.test.js @@ -156,18 +156,19 @@ describe('Error Handler', () => { }); it('logs non-operational errors as FATAL', () => { - const origError = console.error; - console.error = jest.fn(); + const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true); - const err = new Error('programming bug'); - errorMiddleware(err, req, res, next); + try { + const err = new Error('programming bug'); + errorMiddleware(err, req, res, next); - expect(console.error).toHaveBeenCalledWith( - 'FATAL: Non-operational error detected', - expect.any(Object) - ); - - console.error = origError; + const calls = stderrSpy.mock.calls.map(c => String(c[0])); + const fatalLine = calls.find(l => l.includes('FATAL')); + expect(fatalLine).toBeDefined(); + expect(fatalLine).toContain('programming bug'); + } finally { + stderrSpy.mockRestore(); + } }); }); diff --git a/dashcaddy-api/routes/backups.js b/dashcaddy-api/routes/backups.js index eeafc5f..a86e124 100644 --- a/dashcaddy-api/routes/backups.js +++ b/dashcaddy-api/routes/backups.js @@ -775,7 +775,7 @@ async function getStorageInfo() { : 0; } } catch (error) { - console.error('[BackupsRouter] Error getting storage info:', error.message); + process.stderr.write(`[BackupsRouter] Error getting storage info: ${error.message}\n`); } return result; diff --git a/dashcaddy-api/src/auth/providers/email.js b/dashcaddy-api/src/auth/providers/email.js index e118b86..3d73a72 100644 --- a/dashcaddy-api/src/auth/providers/email.js +++ b/dashcaddy-api/src/auth/providers/email.js @@ -410,8 +410,7 @@ class EmailMagicLinkProvider extends AuthProvider { if (this.deps.log && typeof this.deps.log.warn === 'function') { this.deps.log.warn('auth-magic-dev', marker); } else { - // eslint-disable-next-line no-console - console.warn(marker); + process.stderr.write(`${marker}\n`); } } diff --git a/dashcaddy-api/src/dns/dns-providers/registry.js b/dashcaddy-api/src/dns/dns-providers/registry.js index 915cf92..b285b8b 100644 --- a/dashcaddy-api/src/dns/dns-providers/registry.js +++ b/dashcaddy-api/src/dns/dns-providers/registry.js @@ -16,7 +16,7 @@ class DNSProviderRegistry { const instance = new adapterClass({}, {}); const id = instance.providerId; if (this.providers.has(id)) { - console.warn(`DNS provider "${id}" already registered, overwriting`); + process.stderr.write(`[DNS Registry] Provider "${id}" already registered, overwriting\n`); } this.providers.set(id, adapterClass); } @@ -88,7 +88,7 @@ class DNSProviderRegistry { } } } catch (err) { - console.error(`Failed to load DNS provider from ${file}:`, err.message); + process.stderr.write(`[DNS Registry] Failed to load DNS provider from ${file}: ${err.message}\n`); } } } diff --git a/dashcaddy-api/src/managers/auto-restart-manager.js b/dashcaddy-api/src/managers/auto-restart-manager.js index 3c7d1d3..04663c3 100644 --- a/dashcaddy-api/src/managers/auto-restart-manager.js +++ b/dashcaddy-api/src/managers/auto-restart-manager.js @@ -50,7 +50,7 @@ class AutoRestartManager extends EventEmitter { super(); this.ctx = ctx; this.log = ctx.log || console; - this.logError = ctx.logError || ((_ctx, err) => console.error(err)); + this.logError = ctx.logError || ((_ctx, err) => process.stderr.write(`[auto-restart] ${err?.message || err}\n`)); this.docker = ctx.docker; this.healthChecker = ctx.healthChecker; this.notification = ctx.notification; diff --git a/dashcaddy-api/src/managers/config-drift-detector.js b/dashcaddy-api/src/managers/config-drift-detector.js index dbb677b..899fc7e 100644 --- a/dashcaddy-api/src/managers/config-drift-detector.js +++ b/dashcaddy-api/src/managers/config-drift-detector.js @@ -41,7 +41,7 @@ class ConfigDriftDetector extends EventEmitter { super(); this.ctx = ctx; this.log = ctx.log || console; - this.logError = ctx.logError || ((_c, err) => console.error(err)); + this.logError = ctx.logError || ((_c, err) => process.stderr.write(`[config-drift] ${err?.message || err}\n`)); this.docker = ctx.docker; this.servicesStateManager = ctx.servicesStateManager; this.notification = ctx.notification; diff --git a/dashcaddy-api/src/security/audit-logger.js b/dashcaddy-api/src/security/audit-logger.js index 17ae814..86fef28 100644 --- a/dashcaddy-api/src/security/audit-logger.js +++ b/dashcaddy-api/src/security/audit-logger.js @@ -184,10 +184,10 @@ class AuditLogger { }); } catch (e) { // Non-fatal — security store is a best-effort mirror - console.error('[AuditLogger] Security event emit failed:', e.message); + process.stderr.write(`[AuditLogger] Security event emit failed: ${e.message}\n`); } } catch (e) { - console.error('[AuditLogger] Failed to write entry:', e.message); + process.stderr.write(`[AuditLogger] Failed to write entry: ${e.message}\n`); } } @@ -199,7 +199,7 @@ class AuditLogger { } return entries.slice(offset, offset + limit); } catch (e) { - console.error('[AuditLogger] Failed to read:', e.message); + process.stderr.write(`[AuditLogger] Failed to read: ${e.message}\n`); return []; } } diff --git a/dashcaddy-api/src/security/csrf-protection.js b/dashcaddy-api/src/security/csrf-protection.js index 7f5be87..97cc7f4 100644 --- a/dashcaddy-api/src/security/csrf-protection.js +++ b/dashcaddy-api/src/security/csrf-protection.js @@ -216,14 +216,14 @@ function csrfValidationMiddleware(req, res, next) { // Validate both values exist if (!cookieNonce) { - console.warn(`[CSRF] Missing CSRF cookie: ${method} ${req.path} from ${req.ip}`); + process.stderr.write(`[CSRF] Missing CSRF cookie: ${method} ${req.path} from ${req.ip}\n`); return errorResponse(res, 403, '[DC-100] CSRF token missing', { message: 'CSRF cookie not found. Please refresh the page (Ctrl+Shift+R) and try again.' }); } if (!headerToken) { - console.warn(`[CSRF] Missing CSRF header: ${method} ${req.path} from ${req.ip}`); + process.stderr.write(`[CSRF] Missing CSRF header: ${method} ${req.path} from ${req.ip}\n`); return errorResponse(res, 403, '[DC-100] CSRF token missing', { message: 'CSRF token not provided in request headers. Please refresh the page (Ctrl+Shift+R) and try again.' }); @@ -247,7 +247,7 @@ function csrfValidationMiddleware(req, res, next) { next(); } catch (err) { - console.warn(`[CSRF] Invalid CSRF token: ${method} ${req.path} from ${req.ip} - ${err.message}`); + process.stderr.write(`[CSRF] Invalid CSRF token: ${method} ${req.path} from ${req.ip} - ${err.message}\n`); return errorResponse(res, 403, '[DC-101] CSRF token invalid', { message: 'CSRF token validation failed. Please refresh the page (Ctrl+Shift+R) and try again.' }); diff --git a/dashcaddy-api/src/utilities/error-handler.js b/dashcaddy-api/src/utilities/error-handler.js index 093db46..790bd81 100644 --- a/dashcaddy-api/src/utilities/error-handler.js +++ b/dashcaddy-api/src/utilities/error-handler.js @@ -34,7 +34,7 @@ function errorMiddleware(err, req, res, next) { userId: req.user?.id, body: req.body } - ).catch(e => console.error('Failed to write to error log:', e.message)); + ).catch(e => process.stderr.write(`[error-handler] Failed to write to error log: ${e.message}\n`)); // Determine if this is an operational error (AppError) or programming error const isOperational = err.isOperational || err instanceof AppError; @@ -65,11 +65,7 @@ function errorMiddleware(err, req, res, next) { // For non-operational errors, log as fatal if (!isOperational) { - console.error('FATAL: Non-operational error detected', { - error: err.message, - stack: err.stack, - path: req.path - }); + process.stderr.write(`[FATAL] Non-operational error detected: ${JSON.stringify({ error: err.message, stack: err.stack, path: req.path })}\n`); } } diff --git a/dashcaddy-api/src/utils/http.js b/dashcaddy-api/src/utils/http.js index 506e90d..0e52423 100644 --- a/dashcaddy-api/src/utils/http.js +++ b/dashcaddy-api/src/utils/http.js @@ -43,7 +43,7 @@ function fetchT(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) { // passes `timeout: N` here, it's almost certainly a bug — we used to silently // strip it, which masked the issue. Now we surface it in logs and strip it. if ('timeout' in opts) { - console.warn(`[fetchT] opts.timeout=${opts.timeout} is ignored — pass timeoutMs as the 3rd arg of fetchT() instead. Called from: ${new Error().stack.split('\n').slice(2, 4).join(' <- ')}`); + process.stderr.write(`[fetchT] opts.timeout=${opts.timeout} is ignored — pass timeoutMs as the 3rd arg of fetchT() instead. Called from: ${new Error().stack.split('\n').slice(2, 4).join(' <- ')}\n`); const { timeout: _timeout, ...rest } = opts; opts = rest; }