#35 Skip duplicate timestamps on ServerDataEntry

Đã hợp nhất
tunefish đã nhập 2 commit từ hostbbq/bugfix/skip-duplicate-timestamps vào [3]s 11 tháng trước cách đây

+ 4 - 4
common/util/logger.class.ts

@@ -14,18 +14,18 @@ export class Logger {
   }
 
   public static debug(...data: any[]) {
-    if (Logger.levels.includes('DEBUG')) console.log(...data);
+    if (Logger.levels.includes('DEBUG')) console.log('[DEBUG]', ...data);
   }
 
   public static info(...data: any[]) {
-    if (Logger.levels.includes('INFO')) console.log(...data);
+    if (Logger.levels.includes('INFO')) console.log('[INFO]', ...data);
   }
 
   public static warn(...data: any[]) {
-    if (Logger.levels.includes('WARNING')) console.warn(...data);
+    if (Logger.levels.includes('WARNING')) console.warn('[WARN]', ...data);
   }
 
   public static error(...data: any[]) {
-    if (Logger.levels.includes('ERROR')) console.error(...data);
+    if (Logger.levels.includes('ERROR')) console.error('[ERROR]', ...data);
   }
 }

+ 10 - 10
daemon/src/collector.class.ts

@@ -22,7 +22,7 @@ const REDUCE_GROUP_MINUTES = 1;
 
 const MONITOR_MOUNTS = !!process.env.MONITOR_MOUNTS ? process.env.MONITOR_MOUNTS.split(':') : [];
 
-Logger.info('[INFO] Monitoring Drives:', MONITOR_MOUNTS);
+Logger.info('Monitoring Drives:', MONITOR_MOUNTS);
 
 const CSV_COLS = {
   buffer: {
@@ -58,7 +58,7 @@ export class Collector {
     (async () => {
       try {
         if (!fs.existsSync(DATA_DIR)) {
-          Logger.info('[INFO] DATA_DIR', DATA_DIR, 'does not exist - creating now ...');
+          Logger.info('DATA_DIR', DATA_DIR, 'does not exist - creating now ...');
           await fsp.mkdir(DATA_DIR);
         }
 
@@ -94,7 +94,7 @@ export class Collector {
             const stats = (await exec(`./hdd.sh "${mount}"`)).trim();
             if (stats?.length) this.currentHddUsage.mounts.push({ mount, stats });
           } catch (err) {
-            Logger.warn('[WARN] Error while getting space usage of mount', mount, ':', err);
+            Logger.warn('Error while getting space usage of mount', mount, ':', err);
           }
         }
       }
@@ -119,7 +119,7 @@ export class Collector {
           const tmpFile = await this.createTmpFile();
           process.nextTick(() => this.reduceData(tmpFile));
         } catch (err) {
-          Logger.error('[ERROR] Creating Temp File for Reducing Data failed:', err);
+          Logger.error('Creating Temp File for Reducing Data failed:', err);
         }
       }
 
@@ -176,7 +176,7 @@ export class Collector {
 
   private async reduceData(tmpFilename: string) {
     const tmpFilepath = path.resolve(DATA_DIR, tmpFilename);
-    Logger.info('[INFO] Reducing data in', tmpFilepath);
+    Logger.info('Reducing data in', tmpFilepath);
     try {
       const lines: string[][] = [];
       if (fs.existsSync(DATA_BUFFER_REMAINS)) {
@@ -192,7 +192,7 @@ export class Collector {
         if (!line || line.length < 3) break;
 
         const data = this.parseBufferedData(line);
-        Logger.debug('[DEBUG] BufferedData:', JSON.stringify(data));
+        Logger.debug('BufferedData:', JSON.stringify(data));
         valueBuffer.push(data);
 
         if (valueBuffer.length <= 1) {
@@ -258,7 +258,7 @@ export class Collector {
               : undefined
           });
 
-          Logger.debug('[DEBUG] ReducedData:', JSON.stringify(reduced[reduced.length - 1]));
+          Logger.debug('ReducedData:', JSON.stringify(reduced[reduced.length - 1]));
 
           valueBuffer = [];
         }
@@ -280,7 +280,7 @@ export class Collector {
       // Delete tmpFile
       await fsp.unlink(tmpFilepath);
     } catch (err) {
-      Logger.error(`[ERROR] Reducing Data of tmpFile ${tmpFilepath} failed:`, err);
+      Logger.error(`Reducing Data of tmpFile ${tmpFilepath} failed:`, err);
     }
   }
 
@@ -418,11 +418,11 @@ export class Collector {
   } = {
     start: async () => {
       if (this.trx.file) {
-        Logger.warn(`[WARN] Old transaction file found - rolling back now before starting new transaction ...`);
+        Logger.warn(`Old transaction file found - rolling back now before starting new transaction ...`);
         const m = /trx_(\d+)\.csv/.exec(this.trx.file as string);
         const hdl = Number(m?.[1] ?? '0');
         await this.trx.rollback(hdl);
-        Logger.warn(`[WARN] Transaction rollback succeeded.`);
+        Logger.warn(`Transaction rollback succeeded.`);
       }
 
       if (!fs.existsSync(DATA_REDUCED_FILE)) {

+ 1 - 1
daemon/src/index.ts

@@ -13,7 +13,7 @@ process.on('SIGABRT', exitGracefully);
 process.on('SIGQUIT', exitGracefully);
 process.on('SIGTERM', exitGracefully);
 
-Logger.info('[INFO] Starting Monitoring Daemon, pid:', process.pid);
+Logger.info('Starting Monitoring Daemon, pid:', process.pid);
 
 const collector = new Collector();
 new Webserver(Number(process.env.WEB_PORT ?? '80'), collector);

+ 2 - 2
daemon/src/top-tty.ts

@@ -43,8 +43,8 @@ export class TopTTY {
           }
         });
       } catch (err) {
-        Logger.error('[ERROR] top runloop crashed:', err);
-        Logger.info('[INFO] Restaring top runloop...');
+        Logger.error('top runloop crashed:', err);
+        Logger.info('Restaring top runloop...');
       }
       await new Promise<void>(res => setTimeout(res, 1000));
     }

+ 2 - 2
daemon/src/webserver.class.ts

@@ -54,13 +54,13 @@ export class Webserver {
       if (err instanceof HttpStatusException) {
         res.status(err.statusCode).send(err.message);
       } else {
-        Logger.error('[ERROR] Webservice ErrorHandler caught:', err);
+        Logger.error('Webservice ErrorHandler caught:', err);
         res.status(500).send(JSON.stringify(err));
       }
     });
 
     this.app.listen(this.port, () => {
-      Logger.info(`[INFO] Monitoring Daemon Webservice started at http://localhost:${this.port}`);
+      Logger.info(`Monitoring Daemon Webservice started at http://localhost:${this.port}`);
     });
   }
 }

+ 20 - 20
server/src/ctrl/http-check-controller.class.ts

@@ -31,7 +31,7 @@ export class HttpCheckController {
 
           this.scheduleCheck(conf);
 
-          Logger.info('[INFO] Initial HTTP Service Check for', conf.title, '...');
+          Logger.info('Initial HTTP Service Check for', conf.title, '...');
           await this.runCheck(conf);
         }
       } catch (err) {
@@ -69,7 +69,7 @@ export class HttpCheckController {
     let interval = Number(conf.interval);
     if (Number.isNaN(interval)) interval = defaults.serviceChecks.interval;
 
-    if (log) Logger.info(`[INFO] Starting HTTP Service Check Controller for "${conf.title}" with interval ${interval} seconds ...`);
+    if (log) Logger.info(`Starting HTTP Service Check Controller for "${conf.title}" with interval ${interval} seconds ...`);
     const id = Timer.instance.subscribe(interval, async () => await this.runCheck(conf));
     const sub = { id, interval, conf };
     this.subscriptions.push(sub);
@@ -77,7 +77,7 @@ export class HttpCheckController {
   }
 
   private rescheduleCheck(conf: HttpCheckConfig, sub?: Subscriber) {
-    Logger.info('[INFO] Rescheduling HTTP Service Check for', conf.title);
+    Logger.info('Rescheduling HTTP Service Check for', conf.title);
     this.unscheduleCheck(sub, false);
     this.scheduleCheck(conf, false);
   }
@@ -85,13 +85,13 @@ export class HttpCheckController {
   private unscheduleCheck(sub?: Subscriber, log = true) {
     if (!sub) return;
 
-    if (log) Logger.info('[INFO] Removing HTTP Service Check for', sub.conf.title);
+    if (log) Logger.info('Removing HTTP Service Check for', sub.conf.title);
     Timer.instance.unsubscribe(sub.id);
     this.subscriptions = this.subscriptions.filter(s => s.id !== sub.id);
   }
 
   public async runCheck(conf: HttpCheckConfig, db?: HealthCheckDataProvider) {
-    Logger.debug('[DEBUG] TICK', new Date(), JSON.stringify(conf));
+    Logger.debug('TICK', new Date(), JSON.stringify(conf));
 
     const now = new Date();
     const options: AxiosRequestConfig<any> = {
@@ -110,7 +110,7 @@ export class HttpCheckController {
       conf = (await db.getHttpCheckConfigByID(conf.serverId ?? 0, id)) as HttpCheckConfig;
 
       if (!conf) {
-        Logger.warn(`[WARN] HealthCheckConfig(${id}) not found in Database but still scheduled in Timer!`);
+        Logger.warn(`HealthCheckConfig(${id}) not found in Database but still scheduled in Timer!`);
         return;
       }
 
@@ -129,18 +129,18 @@ export class HttpCheckController {
           try {
             const lastErrors = await db.getLastErrors(conf.id, conf.notifyThreshold + 1);
             if (lastErrors.length > conf.notifyThreshold) {
-              Logger.debug(`[DEBUG] Sending [RECOVERY] FCM Notification for`, conf.title);
+              Logger.debug(`Sending [RECOVERY] FCM Notification for`, conf.title);
               await FCMController.instance.sendNotificationToTopic(defaults.fcmTopics.services, {
                 title: `[RECOVERY] ${moment(now).format('HH:mm')} ${conf.title}: [OK]`,
                 body: `HTTP Check '${conf.title}' has recovered to [OK].`
               });
             }
           } catch (err) {
-            Logger.error('[ERROR] Notification failure:', err);
+            Logger.error('Notification failure:', err);
           }
         }
 
-        Logger.debug(`[DEBUG] HTTP Service Check "${conf.title}": OK.`);
+        Logger.debug(`HTTP Service Check "${conf.title}": OK.`);
         await db.insertHealthCheckData(conf.id, now, HttpCheckStatus.OK, 'OK');
       }
     } catch (err) {
@@ -157,18 +157,18 @@ export class HttpCheckController {
             await db.insertHealthCheckData(conf.id, now, HttpCheckStatus.RequestFailed, err.message);
           }
         } catch (insertErr) {
-          Logger.error(`[ERROR] Inserting HealthCheckData on Error failed:`, insertErr);
+          Logger.error(`Inserting HealthCheckData on Error failed:`, insertErr);
           log = true;
         }
       } else {
         try {
           await db.insertHealthCheckData(conf.id, now, HttpCheckStatus.Unknown, new String(err).toString());
         } catch (insertErr) {
-          Logger.error(`[ERROR] Inserting HealthCheckData on Error failed:`, insertErr);
+          Logger.error(`Inserting HealthCheckData on Error failed:`, insertErr);
         }
         log = true;
       }
-      if (log) Logger.error('[ERROR] HTTP Service Check failed:', err);
+      if (log) Logger.error('HTTP Service Check failed:', err);
     } finally {
       try {
         await db.close();
@@ -179,7 +179,7 @@ export class HttpCheckController {
       try {
         const lastErrors = await db.getLastErrors(conf.id, conf.notifyThreshold + 1);
         if (lastErrors.length > conf.notifyThreshold) {
-          Logger.debug(`[DEBUG] Sending [CRIT] FCM Notification for`, conf.title);
+          Logger.debug(`Sending [CRIT] FCM Notification for`, conf.title);
           const lastCheck = lastErrors[0];
           const lastError = lastCheck.data[0];
           await FCMController.instance.sendNotificationToTopic(defaults.fcmTopics.services, {
@@ -190,14 +190,14 @@ export class HttpCheckController {
           });
         }
       } catch (err) {
-        Logger.error('[ERROR] Notification failure:', err);
+        Logger.error('Notification failure:', err);
       }
     }
   }
 
   private recurseDisjunctChecks(checks: CheckDisjunction, responseText: string): ContentCheckError[] {
     const errorBuffer: ContentCheckError[] = [];
-    Logger.debug(`[DEBUG] Processing ${checks.length} disjunctive checks ...`);
+    Logger.debug(`Processing ${checks.length} disjunctive checks ...`);
     for (const check of checks) {
       const errors: ContentCheckError[] = [];
       if (typeof check === 'string') {
@@ -220,13 +220,13 @@ export class HttpCheckController {
         return [];
       }
     }
-    Logger.debug(`[DEBUG] All disjunctive checks failed, collected ${errorBuffer.length} errors`);
+    Logger.debug(`All disjunctive checks failed, collected ${errorBuffer.length} errors`);
     return errorBuffer;
   }
 
   private recurseConjunctChecks(check: CheckConjunction, responseText: string): ContentCheckError[] {
     const errorBuffer: ContentCheckError[] = [];
-    Logger.debug(`[DEBUG] Processing ${check.and.length} conjunctive checks ...`);
+    Logger.debug(`Processing ${check.and.length} conjunctive checks ...`);
     for (const con of check.and) {
       try {
         if (typeof con === 'string') {
@@ -242,16 +242,16 @@ export class HttpCheckController {
         } else throw error;
       }
     }
-    Logger.debug(`[DEBUG] Ran through conjunctive checks, collected ${errorBuffer.length} errors`);
+    Logger.debug(`Ran through conjunctive checks, collected ${errorBuffer.length} errors`);
     return errorBuffer;
   }
 
   private doCheck(check: string, responseText: string) {
     const reg = new RegExp(check, 'i');
     if (!reg.test(responseText)) {
-      Logger.debug(`[DEBUG] Regular expression /${check}/i not found in response`);
+      Logger.debug(`Regular expression /${check}/i not found in response`);
       throw { type: 'contentCheck', status: HttpCheckStatus.CheckFailed, message: `Regular expression /${check}/i not found in response` };
     }
-    Logger.debug(`[DEBUG] RegExp check /${check}/i successful ✔︎`);
+    Logger.debug(`RegExp check /${check}/i successful ✔︎`);
   }
 }

+ 2 - 2
server/src/ctrl/mariadb-connector.class.ts

@@ -57,7 +57,7 @@ export class MariaDBConnector {
     let lasterror: any | null = null;
     while (retries <= MAX_CONNECTION_RETRIES) {
       try {
-        Logger.debug(`[DEBUG] Connecting mariadb connection pool (${retries}/${MAX_CONNECTION_RETRIES}) ...`);
+        Logger.debug(`Connecting mariadb connection pool (${retries}/${MAX_CONNECTION_RETRIES}) ...`);
         await this._connect();
         lasterror = null;
         break;
@@ -92,7 +92,7 @@ export class MariaDBConnector {
     return new Promise(async (resolve, reject) => {
       try {
         if (this._conn && ['connected', 'authenticated'].includes(this._conn?.state)) {
-          Logger.debug(`[DEBUG] Closing mariadb connection.`);
+          Logger.debug(`Closing mariadb connection.`);
           this._conn.release();
         }
         resolve();

+ 21 - 13
server/src/ctrl/mariadb-database.class.ts

@@ -1,5 +1,5 @@
 import moment from 'moment';
-import { Pool } from 'mysql';
+import { MysqlError, Pool } from 'mysql';
 
 import defaults from '../../../common/defaults.module';
 import { ServiceConfig, validateParamType } from '../../../common/interfaces/service-config.interface';
@@ -25,7 +25,7 @@ export class MariaDBDatabase implements DataProvider, HealthCheckDataProvider {
     try {
       await this.db.connect();
 
-      Logger.debug('[DEBUG] Opened MariaDB Connection');
+      Logger.debug('Opened MariaDB Connection');
 
       if (migrate) {
         //TODO: RUN DB MIGRATIONS
@@ -75,15 +75,23 @@ export class MariaDBDatabase implements DataProvider, HealthCheckDataProvider {
     await this.db.beginTransaction();
     try {
       for (const entry of data) {
-        const result = await this.db.query(
-          `INSERT INTO \`ServerDataEntry\`(\`ServerID\`, \`Timestamp\`) VALUES(?, ?);
-          SELECT LAST_INSERT_ID() as 'ID';`,
-          [serverID, entry.time]
-        );
-
-        if (!result || result.length < 2) throw new DatabaseException('Unexpected result during insertServerData');
-
-        let entryID = (result[1] as any[])[0]['ID'];
+        let entryID = 0;
+        try {
+          const result = await this.db.query(
+            `INSERT INTO \`ServerDataEntry\`(\`ServerID\`, \`Timestamp\`) VALUES(?, ?);
+            SELECT LAST_INSERT_ID() as 'ID';`,
+            [serverID, entry.time]
+          );
+
+          if (!result || result.length < 2) throw new DatabaseException('Unexpected result during insertServerData');
+
+          entryID = (result[1] as any[])[0]['ID'];
+        } catch (err: any) {
+          if (err.code === 'ER_DUP_ENTRY' && (err as MysqlError).sqlMessage?.includes('UQ_ServerDataEntry_1')) {
+            Logger.warn('Skipping', err.sqlMessage);
+            continue;
+          }
+        }
 
         for (const type of Object.keys(entry).filter(t => !['time', 'hdd'].includes(t))) {
           for (const key of Object.keys((entry as any)[type])) {
@@ -359,7 +367,7 @@ export class MariaDBDatabase implements DataProvider, HealthCheckDataProvider {
     try {
       if (oldConf) {
         // UPDATE
-        Logger.debug('[DEBUG] Updating HealthCheckConfig', conf.title, `(${oldConf.id})`);
+        Logger.debug('Updating HealthCheckConfig', conf.title, `(${oldConf.id})`);
         if (oldConf.title !== conf.title) {
           await this.db.query('UPDATE `HealthCheckConfig` SET `Title` = ? WHERE `ID` = ?', [conf.title, oldConf.id]);
         }
@@ -413,7 +421,7 @@ export class MariaDBDatabase implements DataProvider, HealthCheckDataProvider {
         }
       } else {
         // INSERT
-        Logger.debug('[DEBUG] Inserting new HealthCheckConfig', conf.title);
+        Logger.debug('Inserting new HealthCheckConfig', conf.title);
         const res = await this.db.query(
           `INSERT INTO \`HealthCheckConfig\`(\`ServerID\`, \`Type\`, \`Title\`) VALUES(?, ?, ?);
           SELECT LAST_INSERT_ID() as ID;`,

+ 7 - 7
server/src/ctrl/server-connector.class.ts

@@ -23,11 +23,11 @@ export class ServerConnector {
           let interval = Number(server.config['syncInterval']);
           if (Number.isNaN(interval)) interval = defaults.serverSync.interval;
 
-          Logger.info('[INFO] Starting Server Sync Connector for', server.title, 'with interval', interval, 'seconds ...');
+          Logger.info('Starting Server Sync Connector for', server.title, 'with interval', interval, 'seconds ...');
           const id = Timer.instance.subscribe(interval, async () => await this.timerTick(server));
           this.subscriptions.push({ id, interval, server });
 
-          Logger.info('[INFO] Initial Sync for', server.title, '...');
+          Logger.info('Initial Sync for', server.title, '...');
           await this.timerTick(server);
         }
       } catch (err) {
@@ -43,8 +43,8 @@ export class ServerConnector {
   }
 
   private async timerTick(server: Server & { errors?: number }) {
-    Logger.debug('[DEBUG] TICK', new Date(), JSON.stringify(server));
-    if (process.env.DEV_MODE) return Logger.warn('[WARN] DEV_MODE active - sync inactive.');
+    Logger.debug('TICK', new Date(), JSON.stringify(server));
+    if (process.env.DEV_MODE) return Logger.warn('DEV_MODE active - sync inactive.');
 
     let trxHdl: number | undefined = undefined;
     const db = new MariaDBDatabase(this.pool);
@@ -73,12 +73,12 @@ export class ServerConnector {
             body: `[RECOVERY] Server '${server.title}': daemon OK`
           });
         } catch (err) {
-          Logger.error('[ERROR] Notification failure:', err);
+          Logger.error('Notification failure:', err);
         }
       }
       server.errors = 0;
     } catch (err) {
-      Logger.error('[ERROR] Server data sync failed:', err);
+      Logger.error('Server data sync failed:', err);
 
       if (err instanceof AxiosError) {
         if (['ECONNREFUSED', 'ECONNABORTED'].includes(err.code ?? '')) {
@@ -89,7 +89,7 @@ export class ServerConnector {
               body: new String(err).toString()
             });
           } catch (err) {
-            Logger.error('[ERROR] Notification failure:', err);
+            Logger.error('Notification failure:', err);
           }
         }
       }

+ 1 - 1
server/src/index.ts

@@ -48,7 +48,7 @@ async function exitGracefully(...args: any[]) {
     Logger.info(`[EXITING] Tear down MariaDB Connection Pool ...`);
     if (connectionPool) await MariaDBPoolFactory.end(connectionPool);
   } catch (err) {
-    Logger.error(`[ERROR] Tear down sequence failed:`, err);
+    Logger.error(`Tear down sequence failed:`, err);
     process.exit(2);
   }
 

+ 2 - 2
server/src/webserver.class.ts

@@ -70,7 +70,7 @@ export class Webserver {
     process.on('uncaughtException', err => console.error('PROCESS.UNCAUGHT', err));
 
     this.app.listen(this.port, () => {
-      Logger.info(`[INFO] Monitoring Webserver started at http://localhost:${this.port}`);
+      Logger.info(`Monitoring Webserver started at http://localhost:${this.port}`);
     });
   }
 
@@ -94,6 +94,6 @@ export class Webserver {
         res.status(500).send(JSON.stringify(err));
       }
     }
-    if (log) Logger.error('[ERROR] Webservice ErrorHandler caught:', err);
+    if (log) Logger.error('Webservice ErrorHandler caught:', err);
   };
 }