232 lines
7.1 KiB
JavaScript
232 lines
7.1 KiB
JavaScript
const express = require('express');
|
|
const http = require('http');
|
|
const WebSocket = require('ws');
|
|
const axios = require('axios');
|
|
const path = require('path');
|
|
const zlib = require('zlib');
|
|
|
|
const argv = require('yargs').argv;
|
|
const app = express();
|
|
const server = http.createServer(app);
|
|
const wss = new WebSocket.Server({
|
|
server,
|
|
perMessageDeflate: true
|
|
});
|
|
|
|
let logCache = new Set();
|
|
let pollingInterval = 16; // Starting interval
|
|
let consecutiveErrors = 0;
|
|
const MAX_POLLING_INTERVAL = 200; // Maximum polling interval in ms
|
|
const MIN_POLLING_INTERVAL = 16; // Minimum polling interval in ms
|
|
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
wss.on('connection', (ws) => {
|
|
debugLog('Client connected:', ws._socket.remoteAddress);
|
|
|
|
ws.on('close', () => {
|
|
debugLog('Client disconnected:', ws._socket.remoteAddress);
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error('WebSocket error:', error);
|
|
});
|
|
});
|
|
|
|
const debugMode = !!argv.debug;
|
|
let eventsHandled = 0;
|
|
let eventsSentToClients = 0;
|
|
|
|
function debugLog(...args) {
|
|
if (debugMode) {
|
|
console.log(...args);
|
|
}
|
|
}
|
|
|
|
function periodicReport() {
|
|
console.log(`Last 10 seconds: Handled ${eventsHandled} events, Sent ${eventsSentToClients} events to clients`);
|
|
eventsHandled = 0;
|
|
eventsSentToClients = 0;
|
|
}
|
|
|
|
setInterval(periodicReport, 10000);
|
|
|
|
async function pollLogs() {
|
|
debugLog('Entering pollLogs function');
|
|
|
|
let logs;
|
|
try {
|
|
if (debugMode) {
|
|
debugLog('debugMode is true');
|
|
const fs = require('fs');
|
|
const data = fs.readFileSync('logsexample.out', 'utf8');
|
|
const logLines = data.split('\n');
|
|
const validLogs = [];
|
|
logLines.forEach((line, index) => {
|
|
if (line.trim() !== '') {
|
|
try {
|
|
const unescapedLine = line.replace(/\\"/g, '"');
|
|
const parsedLog = JSON.parse(unescapedLine);
|
|
validLogs.push(parsedLog);
|
|
} catch (parseError) {
|
|
console.error(`Error parsing log line at index ${index}: ${parseError.message}`);
|
|
}
|
|
}
|
|
});
|
|
logs = validLogs;
|
|
} else {
|
|
debugLog('debugMode is false');
|
|
|
|
const query = 'query=_time:2s relay received';
|
|
debugLog('Sending request to API');
|
|
|
|
const response = await axios.post('https://vmselect.riff.cc/select/logsql/query', query, {
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
}
|
|
});
|
|
|
|
debugLog('Received response from API');
|
|
|
|
// Ensure response.data is a string before calling trim
|
|
const responseData = typeof response.data === 'string' ? response.data.trim() : '';
|
|
if (responseData === '') {
|
|
debugLog('Received empty response from API');
|
|
} else {
|
|
debugLog('Received non-empty response from API');
|
|
}
|
|
|
|
const validLogs = parseLogLines(responseData);
|
|
logs = validLogs;
|
|
}
|
|
|
|
debugLog('Fetched logs:', logs);
|
|
|
|
if (Array.isArray(logs) && logs.length > 0) {
|
|
debugLog('logs is a non-empty array, processing...');
|
|
processLogs(logs);
|
|
eventsHandled += logs.length;
|
|
consecutiveErrors = 0;
|
|
// Keep polling interval at minimum when successful
|
|
pollingInterval = MIN_POLLING_INTERVAL;
|
|
} else {
|
|
debugLog('No logs fetched or logs is not an array');
|
|
// Keep polling interval at minimum when no logs are fetched
|
|
pollingInterval = MIN_POLLING_INTERVAL;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in pollLogs:', error);
|
|
consecutiveErrors++;
|
|
// Increase polling interval on error, but cap at MAX_POLLING_INTERVAL
|
|
pollingInterval = Math.min(MAX_POLLING_INTERVAL, MIN_POLLING_INTERVAL * Math.pow(2, consecutiveErrors));
|
|
}
|
|
|
|
// Schedule the next poll
|
|
setTimeout(pollLogs, pollingInterval);
|
|
}
|
|
|
|
// Start polling
|
|
pollLogs();
|
|
|
|
server.listen(3000, () => {
|
|
console.log('Server is listening on port 3000');
|
|
});
|
|
function parseLogLines(data) {
|
|
const logLines = data.split('\n');
|
|
const validLogs = [];
|
|
|
|
logLines.forEach((line, index) => {
|
|
if (line.trim() !== '') {
|
|
try {
|
|
// Check if the line is already valid JSON
|
|
try {
|
|
const parsedLog = JSON.parse(line);
|
|
validLogs.push(parsedLog);
|
|
} catch {
|
|
// If it's not valid JSON, apply transformations
|
|
let jsonLine = line
|
|
.replace(/\\"/g, '"') // Replace escaped quotes with regular quotes
|
|
.replace(/(\w+)=/g, '"$1":') // Replace '=' with ':' to form valid JSON
|
|
.replace(/,(\s*})/g, '$1') // Remove trailing commas before closing braces
|
|
.replace(/_stream":"{(.+?)}"/g, (match, p1) => `_stream":{"${p1.replace(/=/g, '":"').replace(/,/g, '","')}"}`) // Transform _stream field into a JSON object
|
|
.replace(/_msg":"(.+?)"/g, (match, p1) => `_msg":"${p1.replace(/([a-zA-Z0-9]+)=/g, '$1:').replace(/([a-zA-Z0-9]+): /g, '"$1": ').replace(/, /g, ',').replace(/([a-zA-Z0-9]+):/g, '"$1":')}"`); // Transform _msg field into a JSON object
|
|
|
|
const parsedLog = JSON.parse(jsonLine);
|
|
validLogs.push(parsedLog);
|
|
}
|
|
} catch (parseError) {
|
|
debugLog(`Error parsing log line at index ${index}: ${parseError.message}`);
|
|
debugLog(`Log line: ${line}`);
|
|
const position = parseError.message.match(/position (\d+)/);
|
|
if (position) {
|
|
const pos = parseInt(position[1], 10);
|
|
debugLog(`Character at position ${pos}: ${line.charAt(pos)}`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return validLogs;
|
|
}
|
|
|
|
let updateBatch = [];
|
|
const BATCH_INTERVAL = 1000 / 60; // ~16.67ms for 60 updates per second
|
|
|
|
function processLogs(logs) {
|
|
logs.forEach((log, index) => {
|
|
debugLog(`Processing log at index ${index}`);
|
|
if (!log._msg) {
|
|
debugLog(`Log at index ${index} is missing required fields:`, log);
|
|
return;
|
|
}
|
|
|
|
const msgMatch = log._msg.match(/msg_hash=0x[0-9a-fA-F]+/);
|
|
const timeMatch = log._msg.match(/receivedTime=\d+/);
|
|
const peerIdMatch = log._msg.match(/my_peer_id=([^\s]+)/);
|
|
|
|
if (msgMatch && timeMatch && peerIdMatch) {
|
|
const msg_hash = msgMatch[0].split('=')[1];
|
|
const receivedTime = timeMatch[0].split('=')[1];
|
|
const peerId = peerIdMatch[1];
|
|
|
|
const logIdentifier = `${msg_hash}-${peerId}`;
|
|
if (!logCache.has(logIdentifier)) {
|
|
logCache.add(logIdentifier);
|
|
const logData = {
|
|
msg_hash,
|
|
receivedTime,
|
|
peerId,
|
|
newNode: !logCache.has(`node-${peerId}`)
|
|
};
|
|
|
|
logCache.add(`node-${peerId}`);
|
|
updateBatch.push(logData);
|
|
eventsSentToClients++;
|
|
} else {
|
|
debugLog(`Duplicate log found: ${logIdentifier}`);
|
|
}
|
|
} else {
|
|
debugLog(`Log at index ${index} did not match expected format:`, {
|
|
msgMatch: !!msgMatch,
|
|
timeMatch: !!timeMatch,
|
|
peerIdMatch: !!peerIdMatch,
|
|
log: log
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function sendBatchedUpdates() {
|
|
if (updateBatch.length > 0) {
|
|
const batchData = JSON.stringify(updateBatch);
|
|
wss.clients.forEach(client => {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(batchData);
|
|
}
|
|
});
|
|
updateBatch = [];
|
|
}
|
|
}
|
|
|
|
setInterval(sendBatchedUpdates, BATCH_INTERVAL);
|