Logger.error() called this._log('error',...) but dropped the return value.
_log returns the writeErrorLog(...) promise for error level, so every
await logError(...)/await log.error(...) caller was awaiting undefined —
the error.log disk write was fire-and-forget. This caused:
1. __tests__/logging.test.js 'captures request context' to flake in the
full suite (test read error.log before the un-awaited appendFile
completed; passed in isolation).
2. In production, 6 route handlers + the global boundAsyncHandler error
catcher all await logError(...) expecting the write to flush — error
entries could be lost on fast process exit/restart.
Fix: add 'return' so the promise propagates. Verified: logging test
passes 10/10 full-suite runs (was ~1/6 failure rate). No behavior change
for debug/info/warn (they never wrote to disk).
Routes converted: browse.js, logs.js, sites.js. Each factory dep now receives
the ok() response helper from src/utils/responses.js. Wired through the route
factory destructuring in src/app.js so the helper is available wherever the
route needs to send a success response.
Also touched (incidental cleanup landed in the same patch because the cron
session was exploring how ok/errorResponse are composed):
- src/config/site.js: 28 lines net — response shape consistency
- src/context/caddy.js, dns.js: 34 lines net — minor refactors
- src/utils/http.js, logging.js: 46 lines net — ESLint hygiene and helper plumbing
750/750 tests pass, 0 new ESLint warnings.
Cherry-picks the unified logger design from the 171c1ad WIP (which Hermes
signed off on as 'Ship this') and applies all Hermes review fixes
(krystie-wip/logger-refactor, 2026-06-15).
src/utils/logging.js is now the single entry point for:
- log.info / log.warn / log.error / log.debug (with level filtering,
color-coded dev output, JSON prod output)
- log.audit() / log.auditMiddleware() (audit-log.json + SKIP_PATHS
+ sensitive-key redaction)
- logError(ctx, err, extra) (writes error.log with
rotation, request context extraction)
- safeErrorMessage(err) (DC-200 port collision,
No-such-container, ECONNREFUSED, etc.)
Existing src/security/audit-logger.js kept untouched — routes/errorlogs.js
still uses auditLogger.query/clear, no callers migrated.
Hermes' must-fixes (all addressed):
[1] Syntax error on logger.js:401 — old logger.js at repo root is gone;
refactored src/utils/logging.js is the new home, no Chinese IME bug.
[2] /health/live and /health/ready endpoints — untouched in src/app.js.
[3] Tests — added __tests__/logging.test.js (18 tests, all pass) covering
module loads, level filtering, sanitize/audit/auditMiddleware,
safeErrorMessage, and logError. Full suite: 897/897 pass across 31
suites (was 879 + 18 new).
Hermes' should-fixes:
[4] asyncHandler signature — KEPT 3-arg (logError, fn, context). 49 route
files still call it this way; src/app.js's boundAsyncHandler unchanged.
[5] platformPaths.pkiRootCert — UNTOUCHED, still used in src/app.js.
[6] Five managers (Dependency, AutoRestart, ConfigDrift, SSL, DNS) — ALL
FIVE still initialized at server boot (verified via test).
[7] ok(res, ...) helper — UNTOUCHED, all routes still use it.
[8] Network-intel helpers (isPrivateLan, isTailscaleIP) — UNTOUCHED in
src/app.js, no duplicate inline logic added.
- setLevel() now updates both GLOBAL_LEVEL and the singleton log._level,
so level-filter tests don't pollute later tests.
- Logger.audit() and Logger.error() now return promises so await works.
- Logger._log() awaits writeErrorLog so callers using await can rely on
the error.log being flushed.
- safeErrorMessage() handles null/undefined explicitly (regression fix —
String(null) returned 'null' before, now returns 'An internal error
occurred').
- src/app.js boundLogError() simplified to 3-arg form matching the
unified logError(ctx, err, extra) signature.
- createLogger(level) alias exported so existing src/app.js callers work.
- logError, safeErrorMessage, LOG_LEVELS still exported.
- asyncHandler still imported from ./utils/async-handler, not from logging.
- No changes to routes/* (audit-logger.js still consumed unchanged).
- jest: 897/897 tests pass across 31 suites
- node -e "require('./src/app.js')" loads cleanly
- node server.js boots through full init (all 5 managers start)
- Color-coded logger output visible in dev mode (no NODE_ENV)
- JSON output in production mode (NODE_ENV=production)
After the DC-005 module reorganization (41 files moved into src/ subdirs),
138 test suites failed because the refactor script's path-rewrite logic
missed three categories:
1. Files inside src/ doing 'require("./src/...")' — should be 'require("../...")'
2. Files in src/X/Y/ doing 'require("../../../src/...")' — should be 'require("../../...")'
3. Test files in __tests__/ with leftover 'require("../../../src/...")' paths
Root cause: the original refactor script ran before all files were moved,
so it computed relative paths against stale filesystem state.
Result:
- 30/30 test suites pass
- 879/879 tests pass (was: 18/30 suites, 614/687 tests)
Also fixed:
- routes/apps/restore.js: wrong responses import path
- routes/*/*.js: '../../src/utilities/X' → '../src/utilities/X' (depth 2 routes)
Removed unused imports (path, validateStartupConfig, platformPaths),
renamed unused destructures (_timeout, _logEntry), replaced nested
ternaries with lookup tables, added eslint-disable comments on
require-await functions that are intentionally async for API stability,
and extracted helper functions to reduce max-depth and complexity in
app.js, dns.js, provider-dns.js, and site.js. All 879 tests pass.
Three small cleanups for v1.14.0:
1. /caddy/cas now uses standard success envelope
Was: { status: 'success', data: { cas: caList } }
Now: { success: true, cas: caList }
Updated frontend service-infrastructure.js to match.
2. /api/health/ca now uses standard envelope + meaningful HTTP codes
Was: { status, message, daysUntilExpiration } with 200 on every error
Now: { success, caStatus, message|error, daysUntilExpiration }
with 200 / 404 / 500 as appropriate
caStatus field preserves the original 'healthy'/'warning'/'critical'/'error'
semantic so any future consumer of the CA-health state still has it.
Tests updated to match.
3. Dead timeout: keys in fetchT opts are now a warning, not a silent strip
src/utils/http.js:41 used to do without telling
anyone. Callers that wrote fetchT(url, { timeout: 5000 }) got the default
5s timeout with no indication that their explicit value was ignored.
Now it logs a warning naming the call site, then strips the key.
Fixed 4 call sites that had stale timeout: keys:
- src/context/caddy.js
- src/context/dns.js
- src/context/provider-dns.js
- routes/dns.js (2 places)
Two cleanups in one pass for the v1.14.0 'works on any platform' theme:
1. Response helpers — merged src/utils/responses.js and the root-level
response-helpers.js into a single module at src/utils/responses.js.
The old module had a richer set (created, noContent, validationError,
unauthorized, forbidden, notFound, conflict) and is now re-exported
from the new location. Updated 15 routes to import from
src/utils/responses and deleted the root response-helpers.js.
2. Error logger — error-handler.js now uses the unified
src/utils/logging.js#logError (same one src/app.js uses), so all errors
go to one log file with one rotation policy. Removed the dead
asyncHandler export (the real one is in src/utils/async-handler.js
and is used everywhere). Deleted the legacy error-logger.js.
Both are invisible to users — same HTTP response shapes, same log file
path, same error format. Internal-only refactor.
- deploy.js: wrap logError/notification in try/catch so they never mask the original deploy error
- deploy.js: use optional chaining for error.message access
- logging.js: safeErrorMessage handles null/undefined error gracefully
- Set Domain=.sami on session + CSRF cookies so browsers send them to all subdomains
- This fixes Caddy forward_auth returning 401 for radarr/sonarr/prowlarr
- Fix login URL concatenation bug (radarr.samilogin -> radarr.sami/login)
- Fix getSetCookie() missing from _httpsFetch/_httpFetch response objects
- Fix array/string handling for set-cookie header in session-handlers fallback
- Refactor csrf-protection to createCSRFMiddleware() factory with cookieDomain support
- Pass renewCSRFToken through middleware deps chain to TOTP route