zwave-js-ui
Version:
Z-Wave Control Panel and MQTT Gateway
317 lines (316 loc) • 11.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.stopCleanJob = exports.setupCleanJob = exports.setupAll = exports.module = exports.setupLogger = exports.customTransports = exports.logStream = exports.customFormat = exports.sanitizedConfig = exports.disableColors = exports.defaultLogFile = void 0;
const winston_daily_rotate_file_1 = __importDefault(require("winston-daily-rotate-file"));
const fs_extra_1 = require("fs-extra");
const winston_1 = __importDefault(require("winston"));
const app_1 = require("../config/app");
const utils_1 = require("./utils");
const path = __importStar(require("path"));
const promises_1 = require("fs/promises");
const escape_string_regexp_1 = __importDefault(require("@esm2cjs/escape-string-regexp"));
const stream_1 = require("stream");
const { format, transports, addColors } = winston_1.default;
const { combine, timestamp, printf, colorize, splat } = format;
exports.defaultLogFile = 'z-ui_%DATE%.log';
exports.disableColors = process.env.NO_LOG_COLORS === 'true';
let transportsList = null;
// ensure store and logs directories exist
(0, fs_extra_1.ensureDirSync)(app_1.storeDir);
(0, fs_extra_1.ensureDirSync)(app_1.logsDir);
// custom colors for timestamp and module
addColors({
time: 'grey',
module: 'bold',
});
const colorizer = colorize();
/**
* Generate logger configuration starting from settings.gateway
*/
function sanitizedConfig(module, config) {
config = config || {};
const filePath = (0, utils_1.joinPath)(app_1.logsDir, config.logFileName || exports.defaultLogFile);
return {
module: module || '-',
enabled: config.logEnabled !== undefined ? config.logEnabled : true,
level: config.logLevel || 'info',
logToFile: config.logToFile !== undefined ? config.logToFile : false,
filePath: filePath,
};
}
exports.sanitizedConfig = sanitizedConfig;
/**
* Return a custom logger format
*/
function customFormat(noColor = false) {
noColor = noColor || exports.disableColors;
const formats = [
splat(), // used for formats like: logger.log('info', Message %s', strinVal)
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
format((info) => {
info.level = info.level.toUpperCase();
return info;
})(),
];
if (!noColor) {
formats.push(colorize({ level: true }));
}
// must be added at last
formats.push(printf((info) => {
if (!noColor) {
info.timestamp = colorizer.colorize('time', info.timestamp);
info.module = colorizer.colorize('module', info.module);
}
return `${info.timestamp} ${info.level} ${info.module}: ${info.message}${info.stack ? '\n' + info.stack : ''}`;
}));
return combine(...formats);
}
exports.customFormat = customFormat;
exports.logStream = new stream_1.PassThrough();
/**
* Create the base transports based on settings provided
*/
function customTransports(config) {
// setup transports only once (see issue #2937)
if (transportsList) {
return transportsList;
}
transportsList = [];
if (process.env.ZUI_NO_CONSOLE !== 'true') {
transportsList.push(new transports.Console({
format: customFormat(),
level: config.level,
stderrLevels: ['error'],
}));
}
const streamTransport = new transports.Stream({
format: customFormat(),
level: config.level,
stream: exports.logStream,
});
transportsList.push(streamTransport);
if (config.logToFile) {
let fileTransport;
if (process.env.DISABLE_LOG_ROTATION === 'true') {
fileTransport = new transports.File({
format: customFormat(true),
filename: config.filePath,
level: config.level,
});
}
else {
const options = {
filename: config.filePath,
auditFile: (0, utils_1.joinPath)(app_1.logsDir, 'zui-logs.audit.json'),
datePattern: 'YYYY-MM-DD',
createSymlink: true,
symlinkName: path
.basename(config.filePath)
.replace(`_%DATE%`, '_current'),
zippedArchive: true,
maxFiles: process.env.ZUI_LOG_MAXFILES || '7d',
maxSize: process.env.ZUI_LOG_MAXSIZE || '50m',
level: config.level,
format: customFormat(true),
};
fileTransport = new winston_daily_rotate_file_1.default(options);
setupCleanJob(options);
}
transportsList.push(fileTransport);
}
// giving that we re-use transports, each module will subscribe to events
// increeasing the default limit of 100 prevents warnings
transportsList.forEach((t) => {
t.setMaxListeners(100);
});
return transportsList;
}
exports.customTransports = customTransports;
/**
* Setup a logger
*/
function setupLogger(container, module, config) {
const sanitized = sanitizedConfig(module, config);
// Winston automatically reuses an existing module logger
const logger = container.add(module);
const moduleName = module.toUpperCase() || '-';
logger.configure({
format: combine(format((info) => {
info.module = moduleName;
return info;
})(), format.errors({ stack: true }), format.json()), // to correctly parse errors
silent: !sanitized.enabled,
level: sanitized.level,
transports: customTransports(sanitized),
});
logger.module = module;
logger.setup = (cfg) => setupLogger(container, module, cfg);
return logger;
}
exports.setupLogger = setupLogger;
const logContainer = new winston_1.default.Container();
/**
* Create a new logger for a specific module
*/
function module(module) {
return setupLogger(logContainer, module);
}
exports.module = module;
/**
* Setup all loggers starting from config
*/
function setupAll(config) {
stopCleanJob();
transportsList.forEach((t) => {
if (typeof t.close === 'function') {
t.close();
}
});
transportsList = null;
logContainer.loggers.forEach((logger) => {
logger.setup(config);
});
}
exports.setupAll = setupAll;
let cleanJob;
function setupCleanJob(settings) {
if (cleanJob) {
return;
}
let maxFilesMs;
let maxFiles;
let maxSizeBytes;
const logger = module('LOGGER');
// convert maxFiles to milliseconds
if (settings.maxFiles !== undefined) {
const matches = settings.maxFiles.toString().match(/(\d+)([dhm])/);
if (matches) {
const value = parseInt(matches[1]);
const unit = matches[2];
switch (unit) {
case 'd':
maxFilesMs = value * 24 * 60 * 60 * 1000;
break;
case 'h':
maxFilesMs = value * 60 * 60 * 1000;
break;
case 'm':
maxFilesMs = value * 60 * 1000;
break;
}
}
else {
maxFiles = Number(settings.maxFiles);
}
}
if (settings.maxSize !== undefined) {
// convert maxSize to bytes
const matches2 = settings.maxSize.toString().match(/(\d+)([kmg])/);
if (matches2) {
const value = parseInt(matches2[1]);
const unit = matches2[2];
switch (unit) {
case 'k':
maxSizeBytes = value * 1024;
break;
case 'm':
maxSizeBytes = value * 1024 * 1024;
break;
case 'g':
maxSizeBytes = value * 1024 * 1024 * 1024;
break;
}
}
else {
maxSizeBytes = Number(settings.maxSize);
}
}
// clean up old log files based on maxFiles and maxSize
const filePathRegExp = new RegExp((0, escape_string_regexp_1.default)(path.basename(settings.filename)).replace(/%DATE%/g, '(.*)'));
const logsDir = path.dirname(settings.filename);
const deleteFile = async (filePath) => {
logger.info(`Deleting log file: ${filePath}`);
return (0, promises_1.unlink)(filePath).catch((e) => {
if (e.code !== 'ENOENT') {
logger.error(`Error deleting log file: ${filePath}`, e);
}
});
};
const clean = async () => {
try {
logger.info('Cleaning up log files...');
const files = await (0, promises_1.readdir)(logsDir);
const logFiles = files.filter((file) => file !== settings.symlinkName && filePathRegExp.test(file));
const fileStats = await Promise.allSettled(logFiles.map(async (file) => ({
file,
stats: await (0, promises_1.stat)(path.join(logsDir, file)),
})));
const logFilesStats = [];
for (const res of fileStats) {
if (res.status === 'fulfilled') {
logFilesStats.push(res.value);
}
else {
logger.error('Error getting file stats:', res.reason);
}
}
logFilesStats.sort((a, b) => a.stats.mtimeMs - b.stats.mtimeMs);
// sort by mtime
let totalSize = 0;
let deletedFiles = 0;
for (const { file, stats } of logFilesStats) {
const filePath = path.join(logsDir, file);
totalSize += stats.size;
// last modified time in milliseconds
const fileMs = stats.mtimeMs;
const shouldDelete = (maxSizeBytes && totalSize > maxSizeBytes) ||
(maxFiles && logFiles.length - deletedFiles > maxFiles) ||
(maxFilesMs && fileMs && Date.now() - fileMs > maxFilesMs);
if (shouldDelete) {
await deleteFile(filePath);
deletedFiles++;
}
}
}
catch (e) {
logger.error('Error cleaning up log files:', e);
}
};
cleanJob = setInterval(clean, 60 * 60 * 1000);
clean().catch(() => { });
}
exports.setupCleanJob = setupCleanJob;
function stopCleanJob() {
if (cleanJob) {
clearInterval(cleanJob);
cleanJob = undefined;
}
}
exports.stopCleanJob = stopCleanJob;
exports.default = logContainer.loggers;