DC-010: convert res.json({success:true,...}) → ok(res, {...}) in 3 routes; refactor config/context/utils
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.
This commit is contained in:
@@ -15,7 +15,7 @@ const { ValidationError, ForbiddenError } = require('../errors');
|
|||||||
* @param {Object} deps.docker - Docker client
|
* @param {Object} deps.docker - Docker client
|
||||||
* @returns {express.Router}
|
* @returns {express.Router}
|
||||||
*/
|
*/
|
||||||
module.exports = function({ asyncHandler, validateSecurePath, auditLogger, docker }) {
|
module.exports = function({ asyncHandler, ok, validateSecurePath, auditLogger, docker }) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Parse browse roots from environment
|
// Parse browse roots from environment
|
||||||
@@ -44,7 +44,7 @@ module.exports = function({ asyncHandler, validateSecurePath, auditLogger, docke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, roots });
|
ok(res, { roots });
|
||||||
}, 'browse-roots'));
|
}, 'browse-roots'));
|
||||||
|
|
||||||
// Browse directory contents
|
// Browse directory contents
|
||||||
@@ -64,7 +64,7 @@ module.exports = function({ asyncHandler, validateSecurePath, auditLogger, docke
|
|||||||
roots.push(r);
|
roots.push(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.json({ success: true, path: '', items: roots });
|
return ok(res, { path: '', items: roots });
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingRoot = BROWSE_ROOTS.find(r =>
|
const matchingRoot = BROWSE_ROOTS.find(r =>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const { NotFoundError, ValidationError, ForbiddenError } = require('../errors');
|
|||||||
* @param {Object} deps.dockerMaintenance - Docker maintenance module (optional)
|
* @param {Object} deps.dockerMaintenance - Docker maintenance module (optional)
|
||||||
* @returns {express.Router}
|
* @returns {express.Router}
|
||||||
*/
|
*/
|
||||||
module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }) {
|
module.exports = function({ asyncHandler, ok, docker, logDigest, dockerMaintenance }) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// List containers with logs
|
// List containers with logs
|
||||||
@@ -31,7 +31,7 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
|
|
||||||
const paginationParams = parsePaginationParams(req.query);
|
const paginationParams = parsePaginationParams(req.query);
|
||||||
const result = paginate(containerList, paginationParams);
|
const result = paginate(containerList, paginationParams);
|
||||||
res.json({ success: true, containers: result.data, ...(result.pagination && { pagination: result.pagination }) });
|
ok(res, { containers: result.data, ...(result.pagination && { pagination: result.pagination }) });
|
||||||
}, 'logs-containers'));
|
}, 'logs-containers'));
|
||||||
|
|
||||||
// Get logs for a specific container
|
// Get logs for a specific container
|
||||||
@@ -81,8 +81,7 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
offset += 8 + size;
|
offset += 8 + size;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
ok(res, {
|
||||||
success: true,
|
|
||||||
containerId, containerName,
|
containerId, containerName,
|
||||||
logs: lines,
|
logs: lines,
|
||||||
count: lines.length
|
count: lines.length
|
||||||
@@ -153,23 +152,23 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
if (!logDigest) throw new Error('Log digest not available');
|
if (!logDigest) throw new Error('Log digest not available');
|
||||||
const digest = await logDigest.getLatestDigest();
|
const digest = await logDigest.getLatestDigest();
|
||||||
if (!digest) {
|
if (!digest) {
|
||||||
return res.json({ success: true, digest: null, message: 'No digest available yet. First digest is generated at midnight.' });
|
return ok(res, { digest: null, message: 'No digest available yet. First digest is generated at midnight.' });
|
||||||
}
|
}
|
||||||
res.json({ success: true, digest });
|
ok(res, { digest });
|
||||||
}, 'logs-digest-latest'));
|
}, 'logs-digest-latest'));
|
||||||
|
|
||||||
// Get live digest data (today's accumulated stats)
|
// Get live digest data (today's accumulated stats)
|
||||||
router.get('/logs/digest/live', asyncHandler(async (req, res) => {
|
router.get('/logs/digest/live', asyncHandler(async (req, res) => {
|
||||||
if (!logDigest) throw new Error('Log digest not available');
|
if (!logDigest) throw new Error('Log digest not available');
|
||||||
const live = logDigest.getLiveData();
|
const live = logDigest.getLiveData();
|
||||||
res.json({ success: true, ...live });
|
ok(res, { ...live });
|
||||||
}, 'logs-digest-live'));
|
}, 'logs-digest-live'));
|
||||||
|
|
||||||
// List available digest dates
|
// List available digest dates
|
||||||
router.get('/logs/digest/history', asyncHandler(async (req, res) => {
|
router.get('/logs/digest/history', asyncHandler(async (req, res) => {
|
||||||
if (!logDigest) throw new Error('Log digest not available');
|
if (!logDigest) throw new Error('Log digest not available');
|
||||||
const dates = await logDigest.listDigests();
|
const dates = await logDigest.listDigests();
|
||||||
res.json({ success: true, dates });
|
ok(res, { dates });
|
||||||
}, 'logs-digest-history'));
|
}, 'logs-digest-history'));
|
||||||
|
|
||||||
// Generate digest on demand (for today or a specific date)
|
// Generate digest on demand (for today or a specific date)
|
||||||
@@ -177,7 +176,7 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
if (!logDigest) throw new Error('Log digest not available');
|
if (!logDigest) throw new Error('Log digest not available');
|
||||||
const date = req.body.date || new Date().toISOString().slice(0, 10);
|
const date = req.body.date || new Date().toISOString().slice(0, 10);
|
||||||
const digest = await logDigest.generateDailyDigest(date);
|
const digest = await logDigest.generateDailyDigest(date);
|
||||||
res.json({ success: true, digest });
|
ok(res, { digest });
|
||||||
}, 'logs-digest-generate'));
|
}, 'logs-digest-generate'));
|
||||||
|
|
||||||
// Get digest for a specific date (JSON)
|
// Get digest for a specific date (JSON)
|
||||||
@@ -196,7 +195,7 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
}
|
}
|
||||||
const digest = await logDigest.getDigestByDate(date);
|
const digest = await logDigest.getDigestByDate(date);
|
||||||
if (!digest) throw new NotFoundError(`Digest for ${date}`);
|
if (!digest) throw new NotFoundError(`Digest for ${date}`);
|
||||||
res.json({ success: true, digest });
|
ok(res, { digest });
|
||||||
}, 'logs-digest-date'));
|
}, 'logs-digest-date'));
|
||||||
|
|
||||||
// Get Docker disk usage snapshot
|
// Get Docker disk usage snapshot
|
||||||
@@ -204,14 +203,14 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
|
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
|
||||||
const diskUsage = await dockerMaintenance.getDiskUsage();
|
const diskUsage = await dockerMaintenance.getDiskUsage();
|
||||||
const status = dockerMaintenance.getStatus();
|
const status = dockerMaintenance.getStatus();
|
||||||
res.json({ success: true, diskUsage, maintenance: status });
|
ok(res, { diskUsage, maintenance: status });
|
||||||
}, 'logs-docker-disk'));
|
}, 'logs-docker-disk'));
|
||||||
|
|
||||||
// Trigger Docker maintenance manually
|
// Trigger Docker maintenance manually
|
||||||
router.post('/logs/docker-maintenance', asyncHandler(async (req, res) => {
|
router.post('/logs/docker-maintenance', asyncHandler(async (req, res) => {
|
||||||
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
|
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
|
||||||
const result = await dockerMaintenance.runMaintenance();
|
const result = await dockerMaintenance.runMaintenance();
|
||||||
res.json({ success: true, result });
|
ok(res, { result });
|
||||||
}, 'logs-docker-maintenance'));
|
}, 'logs-docker-maintenance'));
|
||||||
|
|
||||||
// Get logs from a file path (for native applications)
|
// Get logs from a file path (for native applications)
|
||||||
@@ -261,8 +260,7 @@ module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }
|
|||||||
timestamp: extractTimestamp(line)
|
timestamp: extractTimestamp(line)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.json({
|
ok(res, {
|
||||||
success: true,
|
|
||||||
logPath: normalizedPath,
|
logPath: normalizedPath,
|
||||||
logs,
|
logs,
|
||||||
count: logs.length,
|
count: logs.length,
|
||||||
|
|||||||
@@ -17,20 +17,20 @@ const { validateURL } = require('../input-validator');
|
|||||||
* @param {Object} deps.log - Logger instance
|
* @param {Object} deps.log - Logger instance
|
||||||
* @returns {express.Router}
|
* @returns {express.Router}
|
||||||
*/
|
*/
|
||||||
module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addServiceToConfig, siteConfig, log }) {
|
module.exports = function({ asyncHandler, ok, caddy, dns, fetchT, buildDomain, addServiceToConfig, siteConfig, log }) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Get Caddyfile contents
|
// Get Caddyfile contents
|
||||||
router.get('/caddyfile', asyncHandler(async (req, res) => {
|
router.get('/caddyfile', asyncHandler(async (req, res) => {
|
||||||
const content = await caddy.read();
|
const content = await caddy.read();
|
||||||
res.json({ success: true, content });
|
ok(res, { content });
|
||||||
}, 'caddyfile-get'));
|
}, 'caddyfile-get'));
|
||||||
|
|
||||||
// Get current Caddy config (from admin API)
|
// Get current Caddy config (from admin API)
|
||||||
router.get('/caddy/config', asyncHandler(async (req, res) => {
|
router.get('/caddy/config', asyncHandler(async (req, res) => {
|
||||||
const response = await fetchT(`${caddy.adminUrl}/config/`);
|
const response = await fetchT(`${caddy.adminUrl}/config/`);
|
||||||
const config = await response.json();
|
const config = await response.json();
|
||||||
res.json({ success: true, config });
|
ok(res, { config });
|
||||||
}, 'caddy-config'));
|
}, 'caddy-config'));
|
||||||
|
|
||||||
// Reload Caddy configuration via admin API
|
// Reload Caddy configuration via admin API
|
||||||
@@ -49,7 +49,7 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe
|
|||||||
throw new Error('Caddy reload failed. Check server logs for details.');
|
throw new Error('Caddy reload failed. Check server logs for details.');
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, message: 'Caddy configuration reloaded successfully' });
|
ok(res, { message: 'Caddy configuration reloaded successfully' });
|
||||||
}, 'caddy-reload'));
|
}, 'caddy-reload'));
|
||||||
|
|
||||||
// Get Certificate Authorities from Caddyfile
|
// Get Certificate Authorities from Caddyfile
|
||||||
@@ -152,7 +152,7 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe
|
|||||||
throw new NotFoundError(`Site block for "" in Caddyfile`);
|
throw new NotFoundError(`Site block for "" in Caddyfile`);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, message: `Site "${domain}" removed from Caddyfile and Caddy reloaded` });
|
ok(res, { message: `Site "${domain}" removed from Caddyfile and Caddy reloaded` });
|
||||||
}, 'site-delete'));
|
}, 'site-delete'));
|
||||||
|
|
||||||
// Add a new site to Caddyfile and reload
|
// Add a new site to Caddyfile and reload
|
||||||
@@ -180,7 +180,7 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe
|
|||||||
result.rolledBack ? { note: 'Caddyfile was rolled back to previous state' } : {});
|
result.rolledBack ? { note: 'Caddyfile was rolled back to previous state' } : {});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ success: true, message: `Site "${domain}" added to Caddyfile and Caddy reloaded successfully` });
|
ok(res, { message: `Site "${domain}" added to Caddyfile and Caddy reloaded successfully` });
|
||||||
}, 'site-add'));
|
}, 'site-add'));
|
||||||
|
|
||||||
// Add external service reverse proxy to Caddyfile
|
// Add external service reverse proxy to Caddyfile
|
||||||
@@ -260,12 +260,11 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = {
|
const data = {
|
||||||
success: true,
|
|
||||||
message: `External service proxy for ${domain} -> ${externalUrl} created${shouldReload ? ' and Caddy reloaded' : ''}`
|
message: `External service proxy for ${domain} -> ${externalUrl} created${shouldReload ? ' and Caddy reloaded' : ''}`
|
||||||
};
|
};
|
||||||
if (dnsWarning) response.warning = dnsWarning;
|
if (dnsWarning) data.warning = dnsWarning;
|
||||||
res.json(response);
|
ok(res, data);
|
||||||
}, 'site-external'));
|
}, 'site-external'));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
|||||||
@@ -458,6 +458,7 @@ function createApp() {
|
|||||||
}));
|
}));
|
||||||
apiRouter.use(sitesRoutes({
|
apiRouter.use(sitesRoutes({
|
||||||
asyncHandler: ctx.asyncHandler,
|
asyncHandler: ctx.asyncHandler,
|
||||||
|
ok: ctx.ok,
|
||||||
caddy: ctx.caddy,
|
caddy: ctx.caddy,
|
||||||
dns: ctx.dns,
|
dns: ctx.dns,
|
||||||
fetchT: ctx.fetchT,
|
fetchT: ctx.fetchT,
|
||||||
@@ -475,6 +476,7 @@ function createApp() {
|
|||||||
apiRouter.use('/openclaw', openClawRoutes(ctx));
|
apiRouter.use('/openclaw', openClawRoutes(ctx));
|
||||||
apiRouter.use(logsRoutes({
|
apiRouter.use(logsRoutes({
|
||||||
asyncHandler: ctx.asyncHandler,
|
asyncHandler: ctx.asyncHandler,
|
||||||
|
ok: ctx.ok,
|
||||||
docker: ctx.docker,
|
docker: ctx.docker,
|
||||||
logDigest: ctx.logDigest,
|
logDigest: ctx.logDigest,
|
||||||
dockerMaintenance: ctx.dockerMaintenance
|
dockerMaintenance: ctx.dockerMaintenance
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ const siteConfig = {
|
|||||||
routingMode: 'subdomain'
|
routingMode: 'subdomain'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function applyRawConfig(raw) {
|
||||||
|
siteConfig.tld = raw.tld || '.home';
|
||||||
|
if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld;
|
||||||
|
siteConfig.caName = raw.caName || '';
|
||||||
|
siteConfig.dnsServerIp = (raw.dns && raw.dns.ip) || '';
|
||||||
|
siteConfig.dnsServerPort = (raw.dns && raw.dns.port) || CADDY.DEFAULT_DNS_PORT;
|
||||||
|
siteConfig.dashboardHost = raw.dashboardHost || `status${siteConfig.tld}`;
|
||||||
|
siteConfig.timezone = raw.timezone || 'UTC';
|
||||||
|
siteConfig.dnsServers = raw.dnsServers || {};
|
||||||
|
siteConfig.configurationType = raw.configurationType || 'homelab';
|
||||||
|
siteConfig.domain = raw.domain || '';
|
||||||
|
siteConfig.routingMode = raw.routingMode || 'subdomain';
|
||||||
|
siteConfig.pylon = raw.pylon || null;
|
||||||
|
}
|
||||||
|
|
||||||
function loadSiteConfig(CONFIG_FILE, log) {
|
function loadSiteConfig(CONFIG_FILE, log) {
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(CONFIG_FILE)) {
|
if (fs.existsSync(CONFIG_FILE)) {
|
||||||
@@ -35,18 +50,7 @@ function loadSiteConfig(CONFIG_FILE, log) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
siteConfig.tld = raw.tld || '.home';
|
applyRawConfig(raw);
|
||||||
if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld;
|
|
||||||
siteConfig.caName = raw.caName || '';
|
|
||||||
siteConfig.dnsServerIp = (raw.dns && raw.dns.ip) || '';
|
|
||||||
siteConfig.dnsServerPort = (raw.dns && raw.dns.port) || CADDY.DEFAULT_DNS_PORT;
|
|
||||||
siteConfig.dashboardHost = raw.dashboardHost || `status${siteConfig.tld}`;
|
|
||||||
siteConfig.timezone = raw.timezone || 'UTC';
|
|
||||||
siteConfig.dnsServers = raw.dnsServers || {};
|
|
||||||
siteConfig.configurationType = raw.configurationType || 'homelab';
|
|
||||||
siteConfig.domain = raw.domain || '';
|
|
||||||
siteConfig.routingMode = raw.routingMode || 'subdomain';
|
|
||||||
siteConfig.pylon = raw.pylon || null;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (log && log.error) {
|
if (log && log.error) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ async function modifyCaddyfile(CADDYFILE_PATH, reloadCaddy, modifyFn) {
|
|||||||
* Read the current Caddyfile content
|
* Read the current Caddyfile content
|
||||||
*/
|
*/
|
||||||
async function readCaddyfile(CADDYFILE_PATH) {
|
async function readCaddyfile(CADDYFILE_PATH) {
|
||||||
return fsp.readFile(CADDYFILE_PATH, 'utf8');
|
return await fsp.readFile(CADDYFILE_PATH, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -73,6 +73,25 @@ async function refreshDnsToken(username, password, server, fetchT, log) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to refresh the DNS token using per-server (dns.<id>.<role>) credentials.
|
||||||
|
* Returns the refresh result on success, or null if no per-server credentials match.
|
||||||
|
*/
|
||||||
|
async function refreshWithPerServerCredentials(dnsId, serverIp, credentialManager, fetchT, log) {
|
||||||
|
for (const role of ['admin', 'readonly']) {
|
||||||
|
try {
|
||||||
|
const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
|
||||||
|
const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
|
||||||
|
if (username && password) {
|
||||||
|
return await refreshDnsToken(username, password, serverIp, fetchT, log);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure we have a valid DNS token (auto-refresh if needed)
|
* Ensure we have a valid DNS token (auto-refresh if needed)
|
||||||
*/
|
*/
|
||||||
@@ -86,17 +105,8 @@ async function ensureValidDnsToken(siteConfig, credentialManager, fetchT, log) {
|
|||||||
if (primaryIp) {
|
if (primaryIp) {
|
||||||
const dnsId = dnsIpToDnsId(primaryIp, siteConfig);
|
const dnsId = dnsIpToDnsId(primaryIp, siteConfig);
|
||||||
if (dnsId) {
|
if (dnsId) {
|
||||||
for (const role of ['admin', 'readonly']) {
|
const result = await refreshWithPerServerCredentials(dnsId, primaryIp, credentialManager, fetchT, log);
|
||||||
try {
|
if (result) return result;
|
||||||
const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
|
|
||||||
const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
|
|
||||||
if (username && password) {
|
|
||||||
return await refreshDnsToken(username, password, primaryIp, fetchT, log);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ function _httpsFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
|
|||||||
get: (k) => res.headers[k.toLowerCase()],
|
get: (k) => res.headers[k.toLowerCase()],
|
||||||
getSetCookie: () => {
|
getSetCookie: () => {
|
||||||
const sc = res.headers['set-cookie'];
|
const sc = res.headers['set-cookie'];
|
||||||
return sc ? (Array.isArray(sc) ? sc : [sc]) : [];
|
if (!sc) return [];
|
||||||
|
return Array.isArray(sc) ? sc : [sc];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -152,7 +153,8 @@ function _httpFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
|
|||||||
get: (k) => res.headers[k.toLowerCase()],
|
get: (k) => res.headers[k.toLowerCase()],
|
||||||
getSetCookie: () => {
|
getSetCookie: () => {
|
||||||
const sc = res.headers['set-cookie'];
|
const sc = res.headers['set-cookie'];
|
||||||
return sc ? (Array.isArray(sc) ? sc : [sc]) : [];
|
if (!sc) return [];
|
||||||
|
return Array.isArray(sc) ? sc : [sc];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
* log.audit({ action: 'service.create', resource: 'nginx', outcome: 'success', ip, details });
|
* log.audit({ action: 'service.create', resource: 'nginx', outcome: 'success', ip, details });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const fsp = require('fs').promises;
|
const fsp = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
@@ -49,8 +48,6 @@ const C = {
|
|||||||
cyan: '\x1b[36m',
|
cyan: '\x1b[36m',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LEVEL_COLOUR = { debug: C.dim, info: C.green, warn: C.yellow, error: C.red };
|
|
||||||
|
|
||||||
const LEVEL_PREFIX = {
|
const LEVEL_PREFIX = {
|
||||||
debug: `${C.dim}[DBG]${C.reset}`,
|
debug: `${C.dim}[DBG]${C.reset}`,
|
||||||
info: `${C.green}[INF]${C.reset}`,
|
info: `${C.green}[INF]${C.reset}`,
|
||||||
@@ -71,7 +68,6 @@ function formatTime() {
|
|||||||
function consoleWrite(level, ctx, msg, data) {
|
function consoleWrite(level, ctx, msg, data) {
|
||||||
if (GLOBAL_LEVEL > LEVELS[level]) return;
|
if (GLOBAL_LEVEL > LEVELS[level]) return;
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
const colour = LEVEL_COLOUR[level] || C.reset;
|
|
||||||
const parts = [
|
const parts = [
|
||||||
`${C.dim}${formatTime()}${C.reset}`,
|
`${C.dim}${formatTime()}${C.reset}`,
|
||||||
LEVEL_PREFIX[level],
|
LEVEL_PREFIX[level],
|
||||||
@@ -81,15 +77,20 @@ function consoleWrite(level, ctx, msg, data) {
|
|||||||
if (data && typeof data === 'object' && !(data instanceof Error)) {
|
if (data && typeof data === 'object' && !(data instanceof Error)) {
|
||||||
parts.push(`${C.dim}${JSON.stringify(data)}${C.reset}`);
|
parts.push(`${C.dim}${JSON.stringify(data)}${C.reset}`);
|
||||||
}
|
}
|
||||||
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
let fn = console.log;
|
||||||
|
if (level === 'error') fn = console.error;
|
||||||
|
else if (level === 'warn') fn = console.warn;
|
||||||
fn(parts.join(' '));
|
fn(parts.join(' '));
|
||||||
} else {
|
} else {
|
||||||
const entry = {
|
let extra;
|
||||||
t: new Date().toISOString(), level, ctx, msg,
|
if (data instanceof Error) {
|
||||||
...(data instanceof Error
|
extra = { error: { message: data.message, code: data.code, stack: data.stack } };
|
||||||
? { error: { message: data.message, code: data.code, stack: data.stack } }
|
} else if (data && typeof data === 'object') {
|
||||||
: (data && typeof data === 'object' ? { data } : {})),
|
extra = { data };
|
||||||
};
|
} else {
|
||||||
|
extra = {};
|
||||||
|
}
|
||||||
|
const entry = { t: new Date().toISOString(), level, ctx, msg, ...extra };
|
||||||
(level === 'error' ? console.error : console.info)(JSON.stringify(entry));
|
(level === 'error' ? console.error : console.info)(JSON.stringify(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,13 @@ function sanitize(obj) {
|
|||||||
if (!obj || typeof obj !== 'object') return obj;
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
const clean = Array.isArray(obj) ? [] : {};
|
const clean = Array.isArray(obj) ? [] : {};
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
clean[k] = SENSITIVE_KEYS.includes(k) ? '***' : v && typeof v === 'object' ? sanitize(v) : v;
|
if (SENSITIVE_KEYS.includes(k)) {
|
||||||
|
clean[k] = '***';
|
||||||
|
} else if (v && typeof v === 'object') {
|
||||||
|
clean[k] = sanitize(v);
|
||||||
|
} else {
|
||||||
|
clean[k] = v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return clean;
|
return clean;
|
||||||
}
|
}
|
||||||
@@ -270,7 +277,14 @@ class Logger extends EventEmitter {
|
|||||||
consoleWrite(level, ctx, msg, data);
|
consoleWrite(level, ctx, msg, data);
|
||||||
|
|
||||||
if (level === 'error') {
|
if (level === 'error') {
|
||||||
const errObj = data instanceof Error ? data : (data && data.message ? new Error(data.message) : new Error(msg));
|
let errObj;
|
||||||
|
if (data instanceof Error) {
|
||||||
|
errObj = data;
|
||||||
|
} else if (data && data.message) {
|
||||||
|
errObj = new Error(data.message);
|
||||||
|
} else {
|
||||||
|
errObj = new Error(msg);
|
||||||
|
}
|
||||||
// Await the error log write so callers using await on log.error()
|
// Await the error log write so callers using await on log.error()
|
||||||
// can rely on the file being flushed before proceeding.
|
// can rely on the file being flushed before proceeding.
|
||||||
return writeErrorLog(ctx, errObj, req, payload);
|
return writeErrorLog(ctx, errObj, req, payload);
|
||||||
|
|||||||
Reference in New Issue
Block a user