/* Сервис интеграции ПП Парус 8 с WEB API Модуль ядра: протоколирование работы */ //---------------------- // Подключение библиотек //---------------------- const { validateObject, getNowString, deepClone, makeErrorText } = require("./utils"); //Вспомогательные функции const db = require("./db_connector"); //Модуль взаимодействия с БД const { LogFileWriter } = require("./log_file_writer"); //Запись протокола в файл const { SCONSOLE_LOG_COLOR_PATTERN_ERR, SCONSOLE_LOG_COLOR_PATTERN_WRN, SCONSOLE_LOG_COLOR_PATTERN_INF, SLOG_PREFIX_INF, SLOG_PREFIX_WRN, SLOG_PREFIX_ERR, SLOG_PREFIX_PROTOCOL_ERR } = require("./constants"); //Общие константы const { NLOG_STATE_INF, NLOG_STATE_WRN, NLOG_STATE_ERR } = require("../models/obj_log"); //Схемы валидации записи журнала работы сервиса обмена const prmsLoggerSchema = require("../models/prms_logger"); //Схемы валидации параметров функций модуля //-------------------------- // Глобальные идентификаторы //-------------------------- //Интервал повторного сообщения об ошибке передачи строки лога по IPC (мс) const NLOG_IPC_ERROR_REPORT_INTERVAL = 10000; //Сообщение об останове сервера const SMSG_SERVER_STOPPED = "Сервер приложений остановлен"; //------------ // Тело модуля //------------ //Признак работы в дочернем процессе обработчика const isWorkerProcess = () => typeof process.send === "function" && !!process.connected; //Формирование строки лога без ANSI-оформления const formatLogLine = (sNow, sPrefix, sMsg) => `${sNow} ${sPrefix}: ${sMsg}`; //Оформление сообщения по типу записи const resolveLogStyle = nLogState => { let sPrefix = SLOG_PREFIX_INF; let sColorPattern = ""; switch (nLogState) { case NLOG_STATE_ERR: { sPrefix = SLOG_PREFIX_ERR; sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_ERR; break; } case NLOG_STATE_WRN: { sPrefix = SLOG_PREFIX_WRN; sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_WRN; break; } case NLOG_STATE_INF: { sPrefix = SLOG_PREFIX_INF; sColorPattern = SCONSOLE_LOG_COLOR_PATTERN_INF; break; } default: break; } return { sPrefix, sColorPattern }; }; //Класс управления протоколом class Logger { //Конструктор класса constructor() { this.dbConnector = null; this.bLogDB = false; this.fileWriter = null; this.bStopLogged = false; this.nLastIpcErrorAt = 0; } //Включение/выключение записи протоколов в БД setLogDB(bLogDB) { if (this.dbConnector) this.bLogDB = bLogDB; } //Установка объекта для протоколирования в БД setDBConnector(dbConnector, bLogDB) { if (dbConnector instanceof db.DBConnector) { this.dbConnector = dbConnector; if (bLogDB === true) this.setLogDB(true); else this.setLogDB(false); } } //Удаление объекта для протоколирования в БД removeDBConnector() { this.dbConnector = null; this.setLogDB(false); } //Открытие файла лога (пустой путь - не открываем) openLogFile(sLogPath) { //Нет пути - протоколирование в файл не ведётся if (!sLogPath || !String(sLogPath).trim()) { this.bStopLogged = false; return; } //Создаём писатель при первом реальном открытии if (!this.fileWriter) this.fileWriter = new LogFileWriter(); this.fileWriter.open(sLogPath); this.bStopLogged = false; } //Уведомление в консоль о протоколировании в файл (без записи в файл) notifyFileLogConsole() { if (this.fileWriter) this.fileWriter.notifyOpenedConsole(); } //Корректное закрытие файла лога async closeLogFile() { if (this.fileWriter) await this.fileWriter.close(); } //Аварийное синхронное освобождение файла лога closeLogFileSync() { if (this.fileWriter) this.fileWriter.closeSync(); } //Признак, что сообщение об останове уже выдано wasStopLogged() { return this.bStopLogged === true; } //Фиксация выдачи сообщения об останове markStopLogged() { this.bStopLogged = true; } //Подготовка сообщения об останове сервера prepareServerStoppedMsg(nCode) { const nExitCode = typeof nCode === "number" ? nCode : 0; this.markStopLogged(); return `${SMSG_SERVER_STOPPED} (код: ${nExitCode})`; } //Выдача сообщения об останове сервера async notifyServerStopped(nCode) { await this.warn(this.prepareServerStoppedMsg(nCode)); } //Синхронная выдача сообщения об останове сервера (авария / process.on("exit")) notifyServerStoppedSync(nCode) { this.warnSync(this.prepareServerStoppedMsg(nCode)); } //Сообщение об ошибке передачи строки лога по IPC reportIpcLogError(e) { const nNow = Date.now(); if (nNow - this.nLastIpcErrorAt < NLOG_IPC_ERROR_REPORT_INTERVAL) return; this.nLastIpcErrorAt = nNow; console.log(SCONSOLE_LOG_COLOR_PATTERN_ERR, `${getNowString()} ${SLOG_PREFIX_PROTOCOL_ERR} (IPC): `, makeErrorText(e)); } //Отправка строки лога родителю (воркер) sendLogLineToParent(prms) { if (typeof process.send !== "function" || !process.connected) return; if (!prms || typeof prms.sMsg !== "string") return; try { process.send({ ...prms, bLogLine: true }, err => { if (err) this.reportIpcLogError(err); }); } catch (e) { this.reportIpcLogError(e); } } //Приём строки лога от воркера acceptWorkerLogLine(prms) { if (!prms || typeof prms.sNow !== "string" || typeof prms.sPrefix !== "string" || typeof prms.sMsg !== "string") return; this.emitLogLine(prms.nLogState, prms.sMsg, prms.sNow, prms.sPrefix); } //Выдача строки протокола: воркер - IPC; main - файл, затем console emitLogLine(nLogState, sMsg, sNow, sPrefix, bSync) { const style = resolveLogStyle(nLogState); const sPrefixUse = sPrefix || style.sPrefix; //Воркер - только IPC родителю (файл и console пишет main) if (isWorkerProcess()) { this.sendLogLineToParent({ nLogState, sNow, sPrefix: sPrefixUse, sMsg }); return; } //Main: сначала файл, затем console const sLine = formatLogLine(sNow, sPrefixUse, sMsg); if (this.fileWriter) { if (bSync) this.fileWriter.appendSync(sLine); else this.fileWriter.enqueue(sLine); } console.log(style.sColorPattern, `${sNow} ${sPrefixUse}: `, sMsg); } //Синхронное протоколирование без БД (авария / финал) logSync(nLogState, sMsg) { this.emitLogLine(nLogState, sMsg, getNowString(), undefined, true); } //Синхронное протоколирование ошибки errorSync(sMsg) { this.logSync(NLOG_STATE_ERR, sMsg); } //Синхронное протоколирование предупреждения warnSync(sMsg) { this.logSync(NLOG_STATE_WRN, sMsg); } //Синхронное протоколирование информации infoSync(sMsg) { this.logSync(NLOG_STATE_INF, sMsg); } //Протоколирование async log(prms) { //Фиксируем время const sNow = getNowString(); //Проверяем структуру переданного объекта для подключения let sCheckResult = validateObject(prms, prmsLoggerSchema.log, "Параметры функции протоколирования"); //Если структура объекта в норме if (!sCheckResult) { this.emitLogLine(prms.nLogState, prms.sMsg, sNow); //Протоколируем в БД, если это необходимо if (this.bLogDB) { try { //Если есть чем протоколировать if (this.dbConnector && this.dbConnector.bConnected) { await this.dbConnector.putLog(prms); } } catch (e) { this.emitLogLine(NLOG_STATE_ERR, e.sMessage, sNow, SLOG_PREFIX_PROTOCOL_ERR); try { if (this.dbConnector && this.dbConnector.bConnected) await this.dbConnector.putLog({ nLogState: NLOG_STATE_ERR, sMsg: `${SLOG_PREFIX_PROTOCOL_ERR}: ${e.sMessage}` }); } catch {} } } } else { this.emitLogLine(NLOG_STATE_ERR, sCheckResult, sNow, SLOG_PREFIX_PROTOCOL_ERR); console.log(prms); } } //Протоколирование ошибки async error(sMsg, prms) { //Подготовим параметры для протоколирования let logData = prms ? deepClone(prms) : {}; //Выставим сообщение и тип записи журнала logData.nLogState = NLOG_STATE_ERR; logData.sMsg = sMsg; //Протоколируем await this.log(logData); } //Протоколирование предупреждения async warn(sMsg, prms) { //Подготовим параметры для протоколирования let logData = prms ? deepClone(prms) : {}; //Выставим сообщение и тип записи журнала logData.nLogState = NLOG_STATE_WRN; logData.sMsg = sMsg; //Протоколируем await this.log(logData); } //Протоколирование информации async info(sMsg, prms) { //Подготовим параметры для протоколирования let logData = prms ? deepClone(prms) : {}; //Выставим сообщение и тип записи журнала logData.nLogState = NLOG_STATE_INF; logData.sMsg = sMsg; //Протоколируем await this.log(logData); } } //----------------- // Интерфейс модуля //----------------- exports.Logger = Logger;