Upgrade logs
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Per-channel log tail subscriptions over admin WebSocket.
|
||||
*/
|
||||
const { getLogTail, listLogChannels } = require('../../infrastructure/log-files');
|
||||
|
||||
/** @type {Map<import('ws'), string>} */
|
||||
const logSubscribers = new Map();
|
||||
|
||||
function handleLogWsMessage(ws, data) {
|
||||
if (data.type === 'subscribe-log') {
|
||||
const channel = data.channel || 'core';
|
||||
if (!listLogChannels().some((c) => c.id === channel)) {
|
||||
return false;
|
||||
}
|
||||
logSubscribers.set(ws, channel);
|
||||
if (ws.readyState === 1) {
|
||||
const lines = getLogTail(channel, data.lines || 500);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'log-snapshot',
|
||||
channel,
|
||||
lines
|
||||
}));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (data.type === 'unsubscribe-log') {
|
||||
logSubscribers.delete(ws);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onLogWsClose(ws) {
|
||||
logSubscribers.delete(ws);
|
||||
}
|
||||
|
||||
function notifyLogSubscribers(channelId, level, line) {
|
||||
const payload = JSON.stringify({
|
||||
type: 'file-log',
|
||||
channel: channelId,
|
||||
level,
|
||||
message: line
|
||||
});
|
||||
for (const [ws, subscribed] of logSubscribers.entries()) {
|
||||
if (subscribed !== channelId) continue;
|
||||
if (ws.readyState === 1) {
|
||||
ws.send(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeLogWebSocketState() {
|
||||
logSubscribers.clear();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handleLogWsMessage,
|
||||
onLogWsClose,
|
||||
notifyLogSubscribers,
|
||||
closeLogWebSocketState
|
||||
};
|
||||
@@ -15,6 +15,7 @@ const { handleBackupsRoutes } = require('./backups');
|
||||
const { handleDiagnosticsRoutes } = require('./diagnostics');
|
||||
const { handleConsensusRoutes } = require('./consensus');
|
||||
const { handlePluginsRoutes } = require('./plugins');
|
||||
const { handleLogsRoutes } = require('./logs');
|
||||
|
||||
async function handleAdminRequest(req, res) {
|
||||
const url = new URL(req.url, `https://${req.headers.host}`);
|
||||
@@ -52,7 +53,8 @@ async function handleAdminRequest(req, res) {
|
||||
if (await handleDiagnosticsRoutes(req, res)) return;
|
||||
if (await handleConsensusRoutes(req, res)) return;
|
||||
if (await handlePluginsRoutes(req, res)) return;
|
||||
|
||||
if (await handleLogsRoutes(req, res)) return;
|
||||
|
||||
// No route matched
|
||||
res.writeHead(404);
|
||||
res.end('Not Found');
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const { listLogChannels, getLogTail } = require('../../../infrastructure/log-files');
|
||||
|
||||
async function handleLogsRoutes(req, res) {
|
||||
const urlPath = req.urlPath;
|
||||
const method = req.method;
|
||||
|
||||
if (method === 'GET' && urlPath === '/api/logs') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ channels: listLogChannels() }));
|
||||
return true;
|
||||
}
|
||||
|
||||
const match = urlPath.match(/^\/api\/logs\/([a-z]+)$/);
|
||||
if (method === 'GET' && match) {
|
||||
const channelId = match[1];
|
||||
const channels = listLogChannels();
|
||||
if (!channels.some((c) => c.id === channelId)) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Unknown log channel' }));
|
||||
return true;
|
||||
}
|
||||
const url = new URL(req.url, `https://${req.headers.host}`);
|
||||
const lines = Math.min(parseInt(url.searchParams.get('lines') || '500', 10), 5000);
|
||||
const tail = getLogTail(channelId, lines);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ channel: channelId, lines: tail }));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = { handleLogsRoutes };
|
||||
@@ -11,6 +11,11 @@ const {
|
||||
notifyStatsSubscribers,
|
||||
closeStatsWebSocketState
|
||||
} = require('./stats-websocket');
|
||||
const {
|
||||
handleLogWsMessage,
|
||||
onLogWsClose,
|
||||
closeLogWebSocketState
|
||||
} = require('./log-websocket');
|
||||
|
||||
const adminWss = new WebSocket.Server({ noServer: true });
|
||||
const adminClients = new Set();
|
||||
@@ -119,6 +124,8 @@ adminWss.on('connection', (ws) => {
|
||||
});
|
||||
} else if (handleStatsWsMessage(ws, data)) {
|
||||
// subscribe-stats / unsubscribe-stats / request-stats-snapshot
|
||||
} else if (handleLogWsMessage(ws, data)) {
|
||||
// subscribe-log / unsubscribe-log
|
||||
}
|
||||
} catch (err) {
|
||||
logError('Admin', `Error handling WebSocket message: ${err.message}`);
|
||||
@@ -128,6 +135,7 @@ adminWss.on('connection', (ws) => {
|
||||
// Handle close event
|
||||
ws.on('close', () => {
|
||||
onStatsWsClose(ws);
|
||||
onLogWsClose(ws);
|
||||
adminClients.delete(ws);
|
||||
logDebug('Admin', 'WebSocket client disconnected');
|
||||
});
|
||||
@@ -136,6 +144,7 @@ adminWss.on('connection', (ws) => {
|
||||
ws.on('error', (err) => {
|
||||
logError('Admin', `WebSocket error: ${err.message}`);
|
||||
onStatsWsClose(ws);
|
||||
onLogWsClose(ws);
|
||||
adminClients.delete(ws);
|
||||
try {
|
||||
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
||||
@@ -186,6 +195,7 @@ function closeAllWebSockets() {
|
||||
}
|
||||
adminClients.clear();
|
||||
closeStatsWebSocketState();
|
||||
closeLogWebSocketState();
|
||||
|
||||
// Stop health broadcasts
|
||||
stopHealthBroadcasts();
|
||||
|
||||
Reference in New Issue
Block a user