267 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Сервис интеграции ПП Парус 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;