Phase 2 (WIP): Add logger and docker context modules
- src/utils/logger.js: Structured JSON logging - src/context/docker.js: Docker API wrapper (pull, findContainer, getUsedPorts) - All modules can now be imported directly instead of via ctx
This commit is contained in:
78
dashcaddy-api/src/context/docker.js
Normal file
78
dashcaddy-api/src/context/docker.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Docker context
|
||||||
|
* Docker API wrapper and container utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Docker = require('dockerode');
|
||||||
|
const { DOCKER } = require('../../constants');
|
||||||
|
|
||||||
|
// Docker client instance
|
||||||
|
const docker = new Docker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull a Docker image with timeout protection
|
||||||
|
* @param {string} imageName - Image name (e.g., 'nginx:latest')
|
||||||
|
* @param {number} timeoutMs - Timeout in milliseconds
|
||||||
|
* @returns {Promise<Array>} Pull progress output
|
||||||
|
*/
|
||||||
|
function dockerPull(imageName, timeoutMs = DOCKER.TIMEOUT) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(
|
||||||
|
() => reject(new Error(`Docker pull timed out after ${timeoutMs / 1000}s: ${imageName}`)),
|
||||||
|
timeoutMs,
|
||||||
|
);
|
||||||
|
|
||||||
|
docker.pull(imageName, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
docker.modem.followProgress(stream, (err, output) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (err) return reject(err);
|
||||||
|
resolve(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a running Docker container by name substring
|
||||||
|
* @param {string} name - Container name or substring
|
||||||
|
* @param {object} opts - Options (e.g., { all: true } to include stopped containers)
|
||||||
|
* @returns {Promise<object|null>} Container object or null if not found
|
||||||
|
*/
|
||||||
|
async function findContainerByName(name, opts = { all: false }) {
|
||||||
|
const containers = await docker.listContainers(opts);
|
||||||
|
const match = containers.find((c) =>
|
||||||
|
c.Names.some((n) => n.toLowerCase().includes(name.toLowerCase())),
|
||||||
|
);
|
||||||
|
return match || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all host ports currently in use by Docker containers
|
||||||
|
* @returns {Promise<Set<number>>} Set of port numbers
|
||||||
|
*/
|
||||||
|
async function getUsedPorts() {
|
||||||
|
const containers = await docker.listContainers({ all: false });
|
||||||
|
const ports = new Set();
|
||||||
|
|
||||||
|
for (const c of containers) {
|
||||||
|
for (const p of (c.Ports || [])) {
|
||||||
|
if (p.PublicPort) {
|
||||||
|
ports.add(p.PublicPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
client: docker,
|
||||||
|
pull: dockerPull,
|
||||||
|
findContainer: findContainerByName,
|
||||||
|
getUsedPorts,
|
||||||
|
};
|
||||||
@@ -6,10 +6,12 @@
|
|||||||
const asyncHandler = require('./async-handler');
|
const asyncHandler = require('./async-handler');
|
||||||
const { errorResponse, ok } = require('./responses');
|
const { errorResponse, ok } = require('./responses');
|
||||||
const { safeErrorMessage } = require('./safe-error');
|
const { safeErrorMessage } = require('./safe-error');
|
||||||
|
const log = require('./logger');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
asyncHandler,
|
asyncHandler,
|
||||||
errorResponse,
|
errorResponse,
|
||||||
ok,
|
ok,
|
||||||
safeErrorMessage,
|
safeErrorMessage,
|
||||||
|
log,
|
||||||
};
|
};
|
||||||
|
|||||||
40
dashcaddy-api/src/utils/logger.js
Normal file
40
dashcaddy-api/src/utils/logger.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Structured logging
|
||||||
|
* JSON-formatted logs with levels (debug, info, warn, error)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||||
|
const LOG_LEVEL = LOG_LEVELS[process.env.LOG_LEVEL || 'info'] || 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core log function
|
||||||
|
* @param {string} level - Log level (debug, info, warn, error)
|
||||||
|
* @param {string} context - Context label (e.g., 'server', 'docker', 'caddy')
|
||||||
|
* @param {string} message - Log message
|
||||||
|
* @param {object} data - Additional structured data
|
||||||
|
*/
|
||||||
|
function log(level, context, message, data = {}) {
|
||||||
|
if (LOG_LEVELS[level] < LOG_LEVEL) return;
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
t: new Date().toISOString(),
|
||||||
|
level,
|
||||||
|
ctx: context,
|
||||||
|
msg: message,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(data).length) {
|
||||||
|
entry.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
||||||
|
fn(JSON.stringify(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience methods
|
||||||
|
log.info = (ctx, msg, data) => log('info', ctx, msg, data);
|
||||||
|
log.warn = (ctx, msg, data) => log('warn', ctx, msg, data);
|
||||||
|
log.error = (ctx, msg, data) => log('error', ctx, msg, data);
|
||||||
|
log.debug = (ctx, msg, data) => log('debug', ctx, msg, data);
|
||||||
|
|
||||||
|
module.exports = log;
|
||||||
Reference in New Issue
Block a user