2024-06-27 05:57:10 +01:00
const express = require ( 'express' );
const http = require ( 'http' );
const WebSocket = require ( 'ws' );
const axios = require ( 'axios' );
2024-06-27 06:34:29 +01:00
const path = require ( 'path' );
2024-07-03 23:23:06 -03:00
const zlib = require ( 'zlib' );
2024-06-27 05:57:10 +01:00
2024-06-27 06:26:54 +01:00
const argv = require ( 'yargs' ). argv ;
2024-06-27 05:57:10 +01:00
const app = express ();
const server = http . createServer ( app );
2024-07-03 23:23:06 -03:00
const wss = new WebSocket . Server ({
server ,
perMessageDeflate : true
});
2024-06-27 05:57:10 +01:00
let logCache = new Set ();
2024-07-04 00:52:14 -03:00
let pollingInterval = 16 ; // Starting interval
2024-06-27 09:22:55 +01:00
let consecutiveErrors = 0 ;
2024-07-04 00:52:14 -03:00
const MAX_POLLING_INTERVAL = 200 ; // Maximum polling interval in ms
const MIN_POLLING_INTERVAL = 16 ; // Minimum polling interval in ms
2024-06-27 05:57:10 +01:00
2024-06-27 06:34:29 +01:00
app . use ( express . static ( path . join ( __dirname , 'public' )));
2024-06-27 05:57:10 +01:00
wss . on ( 'connection' , ( ws ) => {
2024-06-27 08:24:37 +01:00
debugLog ( 'Client connected:' , ws . _socket . remoteAddress );
2024-06-27 06:51:54 +01:00
ws . on ( 'close' , () => {
2024-06-27 08:24:37 +01:00
debugLog ( 'Client disconnected:' , ws . _socket . remoteAddress );
2024-06-27 06:51:54 +01:00
});
ws . on ( 'error' , ( error ) => {
console . error ( 'WebSocket error:' , error );
});
2024-06-27 05:57:10 +01:00
});
2024-06-27 06:58:06 +01:00
const debugMode = !! argv . debug ;
2024-06-27 08:24:37 +01:00
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 );
2024-06-27 06:26:54 +01:00
2024-06-27 09:22:55 +01:00
async function pollLogs () {
debugLog ( 'Entering pollLogs function' );
2024-06-27 06:57:01 +01:00
2024-06-27 06:27:08 +01:00
let logs ;
2024-06-27 09:22:55 +01:00
try {
if ( debugMode ) {
debugLog ( 'debugMode is true' );
2024-06-27 06:28:19 +01:00
const fs = require ( 'fs' );
const data = fs . readFileSync ( 'logsexample.out' , 'utf8' );
2024-06-27 06:28:58 +01:00
const logLines = data . split ( '\n' );
const validLogs = [];
logLines . forEach (( line , index ) => {
if ( line . trim () !== '' ) {
try {
2024-06-27 07:03:23 +01:00
const unescapedLine = line . replace ( /\\"/g , '"' );
const parsedLog = JSON . parse ( unescapedLine );
2024-06-27 06:28:58 +01:00
validLogs . push ( parsedLog );
} catch ( parseError ) {
2024-06-27 07:03:23 +01:00
console . error ( `Error parsing log line at index ${ index } : ${ parseError . message } ` );
2024-06-27 06:28:58 +01:00
}
}
});
logs = validLogs ;
2024-06-27 09:22:55 +01:00
} else {
debugLog ( 'debugMode is false' );
2024-07-04 00:52:14 -03:00
const query = 'query=_time:2s relay received' ;
2024-06-27 09:22:55 +01:00
debugLog ( 'Sending request to API' );
2024-06-27 07:00:26 +01:00
const response = await axios . post ( 'https://vmselect.riff.cc/select/logsql/query' , query , {
2024-06-27 06:31:45 +01:00
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
}
});
2024-06-27 06:55:37 +01:00
2024-06-27 08:24:37 +01:00
debugLog ( 'Received response from API' );
2024-06-27 06:57:01 +01:00
2024-06-27 09:01:47 +01:00
// Ensure response.data is a string before calling trim
const responseData = typeof response . data === 'string' ? response . data . trim () : '' ;
if ( responseData === '' ) {
2024-06-27 08:24:37 +01:00
debugLog ( 'Received empty response from API' );
2024-06-27 06:55:37 +01:00
} else {
2024-06-27 08:24:37 +01:00
debugLog ( 'Received non-empty response from API' );
2024-06-27 06:55:37 +01:00
}
2024-06-27 09:01:47 +01:00
const validLogs = parseLogLines ( responseData );
2024-06-27 06:30:20 +01:00
logs = validLogs ;
2024-06-27 06:49:14 +01:00
}
2024-06-27 06:48:53 +01:00
2024-06-27 08:24:37 +01:00
debugLog ( 'Fetched logs:' , logs );
2024-06-27 06:53:55 +01:00
2024-06-27 09:22:55 +01:00
if ( Array . isArray ( logs ) && logs . length > 0 ) {
debugLog ( 'logs is a non-empty array, processing...' );
2024-06-27 06:59:19 +01:00
processLogs ( logs );
2024-06-27 09:22:55 +01:00
eventsHandled += logs . length ;
consecutiveErrors = 0 ;
2024-06-27 10:10:07 +01:00
// Keep polling interval at minimum when successful
pollingInterval = MIN_POLLING_INTERVAL ;
2024-06-27 06:00:05 +01:00
} else {
2024-06-27 09:22:55 +01:00
debugLog ( 'No logs fetched or logs is not an array' );
2024-06-27 10:10:07 +01:00
// Keep polling interval at minimum when no logs are fetched
pollingInterval = MIN_POLLING_INTERVAL ;
2024-06-27 05:58:10 +01:00
}
2024-06-27 09:22:55 +01:00
} catch ( error ) {
console . error ( 'Error in pollLogs:' , error );
consecutiveErrors ++ ;
2024-06-27 10:10:07 +01:00
// Increase polling interval on error, but cap at MAX_POLLING_INTERVAL
pollingInterval = Math . min ( MAX_POLLING_INTERVAL , MIN_POLLING_INTERVAL * Math . pow ( 2 , consecutiveErrors ));
2024-06-27 08:24:37 +01:00
}
2024-06-27 09:22:55 +01:00
// Schedule the next poll
setTimeout ( pollLogs , pollingInterval );
}
// Start polling
pollLogs ();
2024-06-27 05:57:10 +01:00
server . listen ( 3000 , () => {
console . log ( 'Server is listening on port 3000' );
});
2024-06-27 06:59:19 +01:00
function parseLogLines ( data ) {
const logLines = data . split ( '\n' );
const validLogs = [];
2024-06-27 07:13:20 +01:00
2024-06-27 07:29:32 +01:00
logLines . forEach (( line , index ) => {
if ( line . trim () !== '' ) {
try {
2024-06-27 07:47:00 +01:00
// 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 );
}
2024-06-27 07:29:32 +01:00
} catch ( parseError ) {
2024-06-27 08:24:37 +01:00
debugLog ( `Error parsing log line at index ${ index } : ${ parseError . message } ` );
debugLog ( `Log line: ${ line } ` );
2024-06-27 07:40:52 +01:00
const position = parseError . message . match ( /position (\d+)/ );
if ( position ) {
const pos = parseInt ( position [ 1 ], 10 );
2024-06-27 08:24:37 +01:00
debugLog ( `Character at position ${ pos } : ${ line . charAt ( pos ) } ` );
2024-06-27 07:39:56 +01:00
}
2024-06-27 07:29:32 +01:00
}
}
});
2024-06-27 07:21:42 +01:00
return validLogs ;
2024-06-27 07:34:04 +01:00
}
2024-07-03 23:23:06 -03:00
let updateBatch = [];
const BATCH_INTERVAL = 1000 / 60 ; // ~16.67ms for 60 updates per second
2024-06-27 06:59:19 +01:00
function processLogs ( logs ) {
logs . forEach (( log , index ) => {
2024-06-27 08:24:37 +01:00
debugLog ( `Processing log at index ${ index } ` );
2024-06-27 07:50:41 +01:00
if ( ! log . _msg ) {
2024-06-27 08:24:37 +01:00
debugLog ( `Log at index ${ index } is missing required fields:` , log );
2024-06-27 07:48:42 +01:00
return ;
}
2024-06-27 06:59:19 +01:00
const msgMatch = log . _msg . match ( /msg_hash=0x[0-9a-fA-F]+/ );
const timeMatch = log . _msg . match ( /receivedTime=\d+/ );
2024-06-27 07:50:41 +01:00
const peerIdMatch = log . _msg . match ( /my_peer_id=([^\s]+)/ );
2024-06-27 06:59:19 +01:00
2024-06-27 07:50:41 +01:00
if ( msgMatch && timeMatch && peerIdMatch ) {
2024-06-27 06:59:19 +01:00
const msg_hash = msgMatch [ 0 ]. split ( '=' )[ 1 ];
const receivedTime = timeMatch [ 0 ]. split ( '=' )[ 1 ];
2024-06-27 07:50:41 +01:00
const peerId = peerIdMatch [ 1 ];
2024-06-27 06:59:19 +01:00
2024-07-04 00:18:18 -03:00
const logIdentifier = ` ${ msg_hash } - ${ peerId } ` ;
2024-06-27 06:59:19 +01:00
if ( ! logCache . has ( logIdentifier )) {
logCache . add ( logIdentifier );
const logData = {
msg_hash ,
receivedTime ,
2024-06-27 07:50:41 +01:00
peerId ,
newNode : ! logCache . has ( `node- ${ peerId } ` )
2024-06-27 06:59:19 +01:00
};
2024-06-27 07:50:41 +01:00
logCache . add ( `node- ${ peerId } ` );
2024-07-03 23:23:06 -03:00
updateBatch . push ( logData );
eventsSentToClients ++ ;
2024-06-27 06:59:19 +01:00
} else {
2024-06-27 08:24:37 +01:00
debugLog ( `Duplicate log found: ${ logIdentifier } ` );
2024-06-27 06:59:19 +01:00
}
} else {
2024-06-27 08:24:37 +01:00
debugLog ( `Log at index ${ index } did not match expected format:` , {
2024-06-27 07:48:42 +01:00
msgMatch : !! msgMatch ,
timeMatch : !! timeMatch ,
2024-06-27 07:50:41 +01:00
peerIdMatch : !! peerIdMatch ,
2024-06-27 07:48:42 +01:00
log : log
});
2024-06-27 06:59:19 +01:00
}
});
}
2024-07-03 23:23:06 -03:00
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 );