diff --git a/src/engine/base-engine.js b/src/engine/base-engine.js index fa61825b12..02d4194335 100644 --- a/src/engine/base-engine.js +++ b/src/engine/base-engine.js @@ -26,7 +26,6 @@ southList.Modbus = require('../south/south-modbus/south-modbus') southList.OPCHDA = require('../south/south-opchda/south-opchda') southList.RestApi = require('../south/south-rest/south-rest') -const LoggerService = require('../service/logger/logger.service') const StatusService = require('../service/status.service') /** @@ -39,10 +38,16 @@ class BaseEngine { * @constructor * @param {ConfigurationService} configService - The config service * @param {EncryptionService} encryptionService - The encryption service + * @param {LoggerService} loggerService - The logger service * @param {String} cacheFolder - The base cache folder used by the engine and its connectors * @return {void} */ - constructor(configService, encryptionService, cacheFolder) { + constructor( + configService, + encryptionService, + loggerService, + cacheFolder, + ) { this.version = VERSION this.cacheFolder = path.resolve(cacheFolder) @@ -51,6 +56,7 @@ class BaseEngine { this.configService = configService this.encryptionService = encryptionService + this.loggerService = loggerService // Variable initialized in initEngineServices this.statusService = null @@ -60,18 +66,13 @@ class BaseEngine { /** * Method used to init async services (like logger when loki is used with Bearer token auth) * @param {Object} engineConfig - the config retrieved from the file - * @param {String} loggerScope - the scope used in the logger (for example 'OIBusEngine') * @returns {Promise} - The result promise */ - async initEngineServices(engineConfig, loggerScope) { + async initEngineServices(engineConfig) { this.oibusName = engineConfig.name this.defaultLogParameters = engineConfig.logParameters this.proxies = engineConfig.proxies this.statusService = new StatusService() - // Configure the logger - this.logger = new LoggerService(loggerScope) - this.logger.setEncryptionService(this.encryptionService) - await this.logger.changeParameters(this.oibusName, this.defaultLogParameters) } /** @@ -118,13 +119,14 @@ class BaseEngine { /** * Return the South connector * @param {Object} configuration - The South connector configuration + * @param {Object} logger - The logger to use * @returns {SouthConnector|null} - The South connector */ - createSouth(configuration) { + createSouth(configuration, logger) { try { const SouthConnector = this.installedSouthConnectors[configuration.type] if (SouthConnector) { - return new SouthConnector(configuration, this.addValues.bind(this), this.addFile.bind(this)) + return new SouthConnector(configuration, this.addValues.bind(this), this.addFile.bind(this), logger) } this.logger.error(`South connector for "${configuration.name}" is not found: ${configuration.type}`) } catch (error) { @@ -148,13 +150,14 @@ class BaseEngine { /** * Return the North connector * @param {Object} configuration - The North connector configuration + * @param {Object} logger - The logger to use * @returns {NorthConnector|null} - The North connector */ - createNorth(configuration) { + createNorth(configuration, logger) { try { const NorthConnector = this.installedNorthConnectors[configuration.type] if (NorthConnector) { - return new NorthConnector(configuration, this.proxies) + return new NorthConnector(configuration, this.proxies, logger) } this.logger.error(`North connector for "${configuration.name}" is not found: ${configuration.type}`) } catch (error) { diff --git a/src/engine/base-engine.spec.js b/src/engine/base-engine.spec.js index ca9e4754f2..f1a4da6f8d 100644 --- a/src/engine/base-engine.spec.js +++ b/src/engine/base-engine.spec.js @@ -4,6 +4,15 @@ const ConfigurationService = require('../service/configuration.service') const { testConfig: config } = require('../../tests/test-config') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Mock services jest.mock('../service/configuration.service') jest.mock('../service/logger/logger.service') @@ -21,11 +30,12 @@ describe('BaseEngine', () => { engineConfig: config.engine, southConfig: config.south, }) - ConfigurationService.mockImplementation(() => mockConfigService) - engine = new BaseEngine(mockConfigService, EncryptionService.getInstance(), 'myCacheFolder') - await engine.initEngineServices(config.engine, 'base') + engine = new BaseEngine(mockConfigService, EncryptionService.getInstance(), {}, 'myCacheFolder') + engine.logger = logger + + await engine.initEngineServices(config.engine) }) it('should warn when calling add values', async () => { diff --git a/src/engine/history-query-engine.js b/src/engine/history-query-engine.js index 4d50f34a55..f9a1c9c638 100644 --- a/src/engine/history-query-engine.js +++ b/src/engine/history-query-engine.js @@ -25,9 +25,15 @@ class HistoryQueryEngine extends BaseEngine { * @constructor * @param {ConfigurationService} configService - The config service * @param {EncryptionService} encryptionService - The encryption service + * @param {LoggerService} loggerService - The logger service */ - constructor(configService, encryptionService) { - super(configService, encryptionService, CACHE_FOLDER) + constructor(configService, encryptionService, loggerService) { + super( + configService, + encryptionService, + loggerService, + CACHE_FOLDER, + ) this.cacheFolder = path.resolve(CACHE_FOLDER) this.historyQueryRepository = null @@ -38,11 +44,12 @@ class HistoryQueryEngine extends BaseEngine { /** * Method used to init async services (like logger when loki is used with Bearer token auth) * @param {Object} engineConfig - the config retrieved from the file - * @param {String} loggerScope - the scope used for the logger * @returns {Promise} - The result promise */ - async initEngineServices(engineConfig, loggerScope = 'HistoryQueryEngine') { - await super.initEngineServices(engineConfig, loggerScope) + async initEngineServices(engineConfig) { + await super.initEngineServices(engineConfig) + this.logger = this.loggerService.createChildLogger('HistoryQueryEngine') + this.statusService.updateStatusDataStream({ ongoingHistoryQueryId: null }) try { @@ -71,7 +78,7 @@ class HistoryQueryEngine extends BaseEngine { this.logger.trace(`Add ${values.length} historian values to cache from South "${this.historyQuery.south.name}".`) if (values.length) { - this.historyQuery.north.cacheValues(southId, values) + await this.historyQuery.north.cacheValues(southId, values) } } @@ -188,7 +195,9 @@ class HistoryQueryEngine extends BaseEngine { return } - this.historyQuery = new HistoryQuery(this, historyConfiguration, southToUse, northToUse) + const historyLogger = this.loggerService.createChildLogger(`History: ${historyConfiguration.id}`) + + this.historyQuery = new HistoryQuery(this, historyConfiguration, southToUse, northToUse, historyLogger) this.statusService.updateStatusDataStream({ ongoingHistoryQueryId: historyConfiguration.id }) this.logger.info(`Starting history query "${historyConfiguration.id}".`) this.historyOnGoing = true diff --git a/src/engine/history-query-engine.spec.js b/src/engine/history-query-engine.spec.js index d24cd7ce57..0939adcc63 100644 --- a/src/engine/history-query-engine.spec.js +++ b/src/engine/history-query-engine.spec.js @@ -5,12 +5,20 @@ const ConfigurationService = require('../service/configuration.service') // Mock fs jest.mock('node:fs/promises') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Mock services jest.mock('./history-query/history-query-repository') jest.mock('../service/database.service') jest.mock('../service/configuration.service') jest.mock('../service/logger/logger.service') -jest.mock('../service/encryption.service') jest.mock('../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) let engine = null @@ -27,8 +35,9 @@ describe('HistoryQueryEngine', () => { }) ConfigurationService.mockImplementation(() => mockConfigService) + const mockLoggerService = { createChildLogger: jest.fn(() => logger) } - engine = new HistoryQueryEngine(mockConfigService) + engine = new HistoryQueryEngine(mockConfigService, {}, mockLoggerService) }) it('should be properly initialized', async () => { diff --git a/src/engine/history-query/history-query.js b/src/engine/history-query/history-query.js index 3b0806f2ab..933f440425 100644 --- a/src/engine/history-query/history-query.js +++ b/src/engine/history-query/history-query.js @@ -15,9 +15,9 @@ class HistoryQuery { // History query finished static STATUS_FINISHED = 'finished' - constructor(engine, historyConfiguration, southConfiguration, northConfiguration) { + constructor(engine, historyConfiguration, southConfiguration, northConfiguration, logger) { this.engine = engine - this.logger = engine.logger + this.logger = logger this.historyConfiguration = historyConfiguration this.southConfiguration = southConfiguration this.south = null @@ -41,7 +41,7 @@ class HistoryQuery { this.statusService = new StatusService() this.statusService.updateStatusDataStream({ status: this.historyConfiguration.status }) await createFolder(this.cacheFolder) - this.south = this.engine.createSouth(this.southConfiguration) + this.south = this.engine.createSouth(this.southConfiguration, this.logger) if (!this.south) { this.logger.error(`South connector "${this.southConfiguration.name}" is not found. ` + `Disabling history query "${this.historyConfiguration.id}".`) @@ -54,7 +54,7 @@ class HistoryQuery { await this.disable() return } - this.north = this.engine.createNorth(this.northConfiguration) + this.north = this.engine.createNorth(this.northConfiguration, this.logger) if (!this.north) { this.logger.error(`North connector "${this.northConfiguration.name}" is not found. ` + `Disabling history query "${this.historyConfiguration.id}".`) diff --git a/src/engine/oibus-engine.js b/src/engine/oibus-engine.js index 621600cefa..eac4df316d 100644 --- a/src/engine/oibus-engine.js +++ b/src/engine/oibus-engine.js @@ -21,10 +21,16 @@ class OIBusEngine extends BaseEngine { * @constructor * @param {ConfigurationService} configService - The config service * @param {EncryptionService} encryptionService - The encryption service + * @param {LoggerService} loggerService - The logger service * @return {void} */ - constructor(configService, encryptionService) { - super(configService, encryptionService, CACHE_FOLDER) + constructor(configService, encryptionService, loggerService) { + super( + configService, + encryptionService, + loggerService, + CACHE_FOLDER, + ) // Will only contain South/North connectors enabled based on the config file this.activeSouths = [] @@ -50,11 +56,11 @@ class OIBusEngine extends BaseEngine { /** * Method used to init async services (like logger when loki is used with Bearer token auth) * @param {Object} engineConfig - the config retrieved from the file - * @param {String} loggerScope - the scope used for the logger * @returns {Promise} - The result promise */ - async initEngineServices(engineConfig, loggerScope = 'OIBusEngine') { - await super.initEngineServices(engineConfig, loggerScope) + async initEngineServices(engineConfig) { + await super.initEngineServices(engineConfig) + this.logger = this.loggerService.createChildLogger('OIBusEngine') this.logger.info(`Starting OIBusEngine: ${JSON.stringify(this.getOIBusInfo(), null, 4)}`) engineConfig.scanModes.forEach(({ scanMode }) => { @@ -80,15 +86,10 @@ class OIBusEngine extends BaseEngine { * @returns {Promise} - The result promise */ async addValues(southId, values) { - // When coming from an external source, the south won't be found. - const southOrigin = this.activeSouths.find((south) => south.id === southId) - this.logger.trace(`Add ${values.length} values to cache from South "${southOrigin?.name || southId}".`) - if (values.length) { - // Do not resolve promise if one of the connector fails. Otherwise, if a file is removed after a North fails, - // the file can be lost. - await Promise.all(this.activeNorths.filter((north) => north.canHandleValues && north.isSubscribed(southId)) - .map((north) => north.cacheValues(values))) - } + // Do not resolve promise if one of the connector fails. Otherwise, if a file is removed after a North fails, + // the file can be lost. + await Promise.all(this.activeNorths.filter((north) => north.canHandleValues && north.isSubscribed(southId)) + .map((north) => north.cacheValues(values))) } /** @@ -100,10 +101,6 @@ class OIBusEngine extends BaseEngine { * @returns {Promise} - The result promise */ async addFile(southId, filePath, preserveFiles) { - // When coming from an external source, the south won't be found. - const southOrigin = this.activeSouths.find((south) => south.id === southId) - this.logger.trace(`Add file "${filePath}" to cache from South "${southOrigin?.name || southId}".`) - try { // Do not resolve promise if one of the connector fails. Otherwise, if a file is removed after a North fails, // the file can be lost. @@ -145,8 +142,10 @@ class OIBusEngine extends BaseEngine { } // 2. North connectors - this.activeNorths = northConfig.filter(({ enabled }) => enabled) - .map((northConfiguration) => this.createNorth(northConfiguration)) + this.activeNorths = northConfig.filter(({ enabled }) => enabled).map((northConfiguration) => { + const northLogger = this.loggerService.createChildLogger(`North:${northConfiguration.name}`) + return this.createNorth(northConfiguration, northLogger) + }) // Allows init/connect failure of a connector to not block other connectors await Promise.allSettled(this.activeNorths.map((north) => { const initAndConnect = async () => { @@ -161,41 +160,41 @@ class OIBusEngine extends BaseEngine { })) // 3. South connectors - this.activeSouths = southConfig.filter(({ enabled }) => enabled) - .map((southConfiguration) => { - const south = this.createSouth(southConfiguration) - if (south) { - // Associate the scanMode to all corresponding South connectors so the engine will know which South to - // activate when a scanMode has a tick. - if (southConfiguration.scanMode) { - if (!this.scanLists[southConfiguration.scanMode]) { - this.logger.error(`South connector ${southConfiguration.name} has an unknown scan mode: ${southConfiguration.scanMode}`) - } else if (!this.scanLists[southConfiguration.scanMode].includes(southConfiguration.id)) { - // Add the South for this scan only if not already there - this.scanLists[southConfiguration.scanMode].push(southConfiguration.id) - } - } else if (Array.isArray(southConfiguration.points)) { - if (southConfiguration.points.length > 0) { - southConfiguration.points.forEach((point) => { - if (point.scanMode !== 'listen') { - if (!this.scanLists[point.scanMode]) { - this.logger.error(`Point: ${point.pointId} in South connector ` + this.activeSouths = southConfig.filter(({ enabled }) => enabled).map((southConfiguration) => { + const southLogger = this.loggerService.createChildLogger(`South:${southConfiguration.name}`) + const south = this.createSouth(southConfiguration, southLogger) + if (south) { + // Associate the scanMode to all corresponding South connectors so the engine will know which South to + // activate when a scanMode has a tick. + if (southConfiguration.scanMode) { + if (!this.scanLists[southConfiguration.scanMode]) { + this.logger.error(`South connector ${southConfiguration.name} has an unknown scan mode: ${southConfiguration.scanMode}`) + } else if (!this.scanLists[southConfiguration.scanMode].includes(southConfiguration.id)) { + // Add the South for this scan only if not already there + this.scanLists[southConfiguration.scanMode].push(southConfiguration.id) + } + } else if (Array.isArray(southConfiguration.points)) { + if (southConfiguration.points.length > 0) { + southConfiguration.points.forEach((point) => { + if (point.scanMode !== 'listen') { + if (!this.scanLists[point.scanMode]) { + this.logger.error(`Point: ${point.pointId} in South connector ` + `${southConfiguration.name} has an unknown scan mode: ${point.scanMode}`) - } else if (!this.scanLists[point.scanMode].includes(southConfiguration.id)) { - // Add the South for this scan only if not already there - this.scanLists[point.scanMode].push(southConfiguration.id) - } + } else if (!this.scanLists[point.scanMode].includes(southConfiguration.id)) { + // Add the South for this scan only if not already there + this.scanLists[point.scanMode].push(southConfiguration.id) } - }) - } else { - this.logger.warn(`South "${southConfiguration.name}" has no point.`) - } + } + }) } else { - this.logger.error(`South "${southConfiguration.name}" has no scan mode defined.`) + this.logger.warn(`South "${southConfiguration.name}" has no point.`) } + } else { + this.logger.error(`South "${southConfiguration.name}" has no scan mode defined.`) } - return south - }) + } + return south + }) this.logger.debug(JSON.stringify(this.scanLists, null, ' ')) // Allows init/connect failure of a connector to not block other connectors await Promise.allSettled(this.activeSouths.map((south) => { diff --git a/src/engine/oibus-engine.spec.js b/src/engine/oibus-engine.spec.js index a99f6066cc..98b1ddeb40 100644 --- a/src/engine/oibus-engine.spec.js +++ b/src/engine/oibus-engine.spec.js @@ -14,6 +14,15 @@ EncryptionService.getInstance = () => ({ jest.mock('../service/configuration.service') jest.mock('../service/logger/logger.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let engine = null describe('OIBusEngine', () => { @@ -29,7 +38,8 @@ describe('OIBusEngine', () => { ConfigurationService.mockImplementation(() => mockConfigService) - engine = new OIBusEngine(mockConfigService) + const mockLoggerService = { createChildLogger: jest.fn(() => logger) } + engine = new OIBusEngine(mockConfigService, {}, mockLoggerService) await engine.initEngineServices(config.engine) }) diff --git a/src/frontend/components/oib-form/index.js b/src/frontend/components/oib-form/index.js index 463801421d..2187bbc4ac 100644 --- a/src/frontend/components/oib-form/index.js +++ b/src/frontend/components/oib-form/index.js @@ -11,7 +11,6 @@ import OibTitle from './oib-title.jsx' import OibAuthentication from './oib-authentication.jsx' import OibTimezone from './oib-timezone.jsx' import OibCron from './oib-cron.jsx' -import OibLogLevel from './oib-log-level.jsx' export { OibCheckbox, @@ -27,5 +26,4 @@ export { OibAuthentication, OibTimezone, OibCron, - OibLogLevel, } diff --git a/src/frontend/components/oib-form/oib-log-level.jsx b/src/frontend/components/oib-form/oib-log-level.jsx deleted file mode 100644 index 703255c859..0000000000 --- a/src/frontend/components/oib-form/oib-log-level.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Row, Col } from 'reactstrap' -import OibTitle from './oib-title.jsx' -import OibSelect from './oib-select.jsx' - -const OibLogLevel = ({ name, value, onChange, logOptions, help }) => ( - <> - - {help} - - - - - - - - - - - - - - - - -) - -OibLogLevel.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.object, - onChange: PropTypes.func.isRequired, - logOptions: PropTypes.arrayOf(String), - help: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), -} -OibLogLevel.defaultProps = { - value: { - consoleLevel: 'engine', - fileLevel: 'engine', - sqliteLevel: 'engine', - }, - logOptions: ['engine', 'trace', 'debug', 'info', 'warning', 'error', 'none'], - help: ( - <> -

OIBus can send logs to 4 different supports:

- -
    -
  • The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service
  • -
  • The File logs writes messages on the chosen folder of the OIBus machine
  • -
  • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs - using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
  • -
  • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
  • -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Possible levelsDescription
EngineUse global log level selected on engine
ErrorThese logs needs investigation in a production system
WarningUnexpected behavior that will not impact significantly the system
InfoInformation useful to the administrator
DebugInformation useful only to debug an issue
TraceInformation useful to debug an issue but they will significantly impact performances
-
-

- We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode - only to identify the origin of an issue. -

- - ), -} - -export default OibLogLevel diff --git a/src/frontend/engine/__snapshots__/engine.spec.jsx.snap b/src/frontend/engine/__snapshots__/engine.spec.jsx.snap index 9c75ead430..0af1f12ed2 100644 --- a/src/frontend/engine/__snapshots__/engine.spec.jsx.snap +++ b/src/frontend/engine/__snapshots__/engine.spec.jsx.snap @@ -550,9 +550,6 @@ exports[`Engine check Engine 1`] = `
  • Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -575,7 +572,7 @@ exports[`Engine check Engine 1`] = ` - None + Silent This logging is not activated @@ -681,6 +678,11 @@ exports[`Engine check Engine 1`] = ` > error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -3538,7 +3537,7 @@ exports[`Engine check Engine when config has no proxies 1`] = ` - None + Silent This logging is not activated @@ -3644,6 +3643,11 @@ exports[`Engine check Engine when config has no proxies 1`] = ` > error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -6087,7 +6088,7 @@ exports[`Engine check change OIBus name 1`] = ` - None + Silent This logging is not activated @@ -6193,6 +6194,11 @@ exports[`Engine check change OIBus name 1`] = ` > error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -9050,7 +9053,7 @@ exports[`Engine check change password 1`] = ` - None + Silent This logging is not activated @@ -9156,6 +9159,11 @@ exports[`Engine check change password 1`] = ` > error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -12013,7 +12018,7 @@ exports[`Engine check change port 1`] = ` - None + Silent This logging is not activated @@ -12119,6 +12124,11 @@ exports[`Engine check change port 1`] = ` > error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -14976,7 +14983,7 @@ exports[`Engine check change user 1`] = ` - None + Silent This logging is not activated @@ -15082,6 +15089,11 @@ exports[`Engine check change user 1`] = ` > error +
    error +
    error +
    Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The filename will always have the most recent log lines. The larger the appended number, the older the log file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. @@ -88,7 +85,7 @@ exports[`Logging check Logging 1`] = ` - None + Silent This logging is not activated @@ -194,6 +191,11 @@ exports[`Logging check Logging 1`] = ` > error +
    The level for the Console log
    , diff --git a/src/frontend/engine/logging/file-logging.jsx b/src/frontend/engine/logging/file-logging.jsx index 2f2ca4975b..51f1ee663d 100644 --- a/src/frontend/engine/logging/file-logging.jsx +++ b/src/frontend/engine/logging/file-logging.jsx @@ -7,7 +7,7 @@ const schema = { name: 'FileLogging' } schema.form = { level: { type: 'OibSelect', - options: ['trace', 'debug', 'info', 'warning', 'error', 'none'], + options: ['trace', 'debug', 'info', 'warning', 'error', 'silent'], md: 3, defaultValue: 'info', help:
    The level for the File log
    , diff --git a/src/frontend/engine/logging/logging.jsx b/src/frontend/engine/logging/logging.jsx index e1ce7559a0..641bcb325c 100644 --- a/src/frontend/engine/logging/logging.jsx +++ b/src/frontend/engine/logging/logging.jsx @@ -23,11 +23,6 @@ const Logging = ({ logParameters, onChange }) => ( will be added as a suffix of the logfile name.
  • Max files: Limit the number of files created by the logger.
  • -
  • - Tailable: If true, log files will be rolled based on max size and max files, but in ascending order. The - filename will always have the most recent log lines. The larger the appended number, the older the log - file. This option requires max files to be set, or it will be ignored. -
  • The sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs @@ -50,7 +45,7 @@ const Logging = ({ logParameters, onChange }) => ( - None + Silent This logging is not activated diff --git a/src/frontend/engine/logging/loki-logging.jsx b/src/frontend/engine/logging/loki-logging.jsx index 011a9fb82f..34f262c7b9 100644 --- a/src/frontend/engine/logging/loki-logging.jsx +++ b/src/frontend/engine/logging/loki-logging.jsx @@ -8,7 +8,7 @@ schema.form = { level: { type: 'OibSelect', md: 3, - options: ['trace', 'debug', 'info', 'warning', 'error', 'none'], + options: ['trace', 'debug', 'info', 'warning', 'error', 'silent'], defaultValue: 'info', help:
    The level for the Loki log
    , }, diff --git a/src/frontend/engine/logging/sqlite-logging.jsx b/src/frontend/engine/logging/sqlite-logging.jsx index f869b006d3..871eee813d 100644 --- a/src/frontend/engine/logging/sqlite-logging.jsx +++ b/src/frontend/engine/logging/sqlite-logging.jsx @@ -8,7 +8,7 @@ schema.form = { level: { type: 'OibSelect', md: 3, - options: ['trace', 'debug', 'info', 'warning', 'error', 'none'], + options: ['trace', 'debug', 'info', 'warning', 'error', 'silent'], defaultValue: 'info', help:
    The level for the Sqlite log
    , }, diff --git a/src/frontend/north/__snapshots__/configure-north.spec.jsx.snap b/src/frontend/north/__snapshots__/configure-north.spec.jsx.snap index f287fbc775..5167efce4f 100644 --- a/src/frontend/north/__snapshots__/configure-north.spec.jsx.snap +++ b/src/frontend/north/__snapshots__/configure-north.spec.jsx.snap @@ -125,317 +125,6 @@ exports[`ConfigureNorth check ConfigureNorth 1`] = ` -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    @@ -1083,317 +772,6 @@ exports[`ConfigureNorth check update 1`] = `
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    diff --git a/src/frontend/north/form/__snapshots__/north-form.spec.jsx.snap b/src/frontend/north/form/__snapshots__/north-form.spec.jsx.snap index 042abe05b9..3c4b3665f7 100644 --- a/src/frontend/north/form/__snapshots__/north-form.spec.jsx.snap +++ b/src/frontend/north/form/__snapshots__/north-form.spec.jsx.snap @@ -125,317 +125,6 @@ exports[`NorthForm check NorthForm with North connector: TestConsole 1`] = `
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    @@ -1040,317 +729,6 @@ exports[`NorthForm check NorthForm with empty North connector 1`] = ` class="col-md-2" />
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    diff --git a/src/frontend/north/form/north-form.jsx b/src/frontend/north/form/north-form.jsx index 102c7efd5e..486bb9c34d 100644 --- a/src/frontend/north/form/north-form.jsx +++ b/src/frontend/north/form/north-form.jsx @@ -7,7 +7,6 @@ import { OibTitle, OibCheckbox, OibInteger, - OibLogLevel, } from '../../components/oib-form' import SubscribedTo from './subscribed-to.jsx' import validation from './north.validation' @@ -96,11 +95,6 @@ const NorthForm = ({ north, northIndex, onChange }) => { /> -
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    @@ -919,317 +608,6 @@ exports[`ConfigureSouth check update 1`] = `
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    diff --git a/src/frontend/south/form/__snapshots__/south-form.spec.jsx.snap b/src/frontend/south/form/__snapshots__/south-form.spec.jsx.snap index 8030960097..412c8d7a7e 100644 --- a/src/frontend/south/form/__snapshots__/south-form.spec.jsx.snap +++ b/src/frontend/south/form/__snapshots__/south-form.spec.jsx.snap @@ -108,317 +108,6 @@ exports[`SouthForm check SouthForm with empty south 1`] = ` class="col-md-4" />
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    @@ -1053,317 +742,6 @@ exports[`SouthForm check SouthForm with south: TestFolderScanner 1`] = `
    -
    -
    - Logging parameters - -
    -
    -
    -
    -
    -
    -

    - OIBus can send logs to 4 different supports: -

    -
      -
    • - The Console logs messages on the terminal. It cannot be used when OIBus runs as a Windows service -
    • -
    • - The File logs writes messages on the chosen folder of the OIBus machine -
    • -
    • - The Sqlite logs will be used to store logs on the chosen database of the OIBus machine. This allows to see logs using the Logs menu. The maximum size can be defined so older message will be deleted automatically. -
    • -
    • - The Loki logs send the logs to a loki instance. This allows to access the logs remotely. -
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Possible levels - - Description -
    - Engine - - Use global log level selected on engine -
    - Error - - These logs needs investigation in a production system -
    - Warning - - Unexpected behavior that will not impact significantly the system -
    - Info - - Information useful to the administrator -
    - Debug - - Information useful only to debug an issue -
    - Trace - - Information useful to debug an issue but they will significantly impact performances -
    -
    -

    - We recommend to use the info or warning levels in normal operations and to put the debug or the trace mode only to identify the origin of an issue. -

    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    diff --git a/src/frontend/south/form/south-form.jsx b/src/frontend/south/form/south-form.jsx index 328a6a6f8b..02754ceaff 100644 --- a/src/frontend/south/form/south-form.jsx +++ b/src/frontend/south/form/south-form.jsx @@ -7,7 +7,6 @@ import { OibTitle, OibCheckbox, OibScanMode, - OibLogLevel, } from '../../components/oib-form' import OibForm from '../../components/oib-form/oib-form.jsx' import validation from './south.validation' @@ -112,11 +111,6 @@ const SouthForm = ({ south, southIndex, onChange }) => { )} - { - const baseDir = path.extname(configFile) ? path.parse(configFile).dir : configFile - await createFolder(baseDir) +const baseDir = path.extname(configFile) ? path.parse(configFile).dir : configFile - if (cluster.isMaster) { +if (cluster.isMaster) { + loggerService.start('OIBus-main', logParameters).then(async () => { + const mainLogger = loggerService.createChildLogger('main-thread') + await createFolder(baseDir) // Master role is nothing except launching a worker and relaunching another // one if exit is detected (typically to load a new configuration) - logger.info(`Starting OIBus version ${VERSION}.`) + mainLogger.info(`Starting OIBus version ${VERSION}.`) let restartCount = 0 let startTime = (new Date()).getTime() @@ -59,14 +57,14 @@ logger.changeParameters('OIBus-main', logParameters).then(async () => { cluster.on('exit', (sourceWorker, code, signal) => { if (signal) { - logger.info(`Worker ${sourceWorker.process.pid} was killed by signal: ${signal}`) + mainLogger.info(`Worker ${sourceWorker.process.pid} was killed by signal: ${signal}`) } else { - logger.error(`Worker ${sourceWorker.process.pid} exited with error code: ${code}`) + mainLogger.error(`Worker ${sourceWorker.process.pid} exited with error code: ${code}`) } // Check if we got a restart loop and go in safe mode restartCount += code > 0 ? 1 : 0 - logger.info(`Restart count: ${restartCount}`) + mainLogger.info(`Restart count: ${restartCount}`) const safeMode = restartCount >= MAX_RESTART_COUNT const endTime = (new Date()).getTime() @@ -81,35 +79,35 @@ logger.changeParameters('OIBus-main', logParameters).then(async () => { cluster.on('message', (_worker, msg) => { switch (msg.type) { case 'reload-oibus-engine': - Object.keys(cluster.workers) - .forEach((id) => { - cluster.workers[id].send({ type: 'reload-oibus-engine' }) - }) + Object.keys(cluster.workers).forEach((id) => { + cluster.workers[id].send({ type: 'reload-oibus-engine' }) + }) break case 'reload': - Object.keys(cluster.workers) - .forEach((id) => { - cluster.workers[id].send({ type: 'reload' }) - }) + Object.keys(cluster.workers).forEach((id) => { + cluster.workers[id].send({ type: 'reload' }) + }) break case 'shutdown': - Object.keys(cluster.workers) - .forEach((id) => { - cluster.workers[id].send({ type: 'shutdown' }) - }) + Object.keys(cluster.workers).forEach((id) => { + cluster.workers[id].send({ type: 'shutdown' }) + }) break case 'shutdown-ready': process.exit() break default: - logger.warn(`Unknown message type received from Worker: ${msg.type}`) + mainLogger.warn(`Unknown message type received from Worker: ${msg.type}`) } }) - } else { - process.chdir(baseDir) + }) +} else { + loggerService.start('OIBus-main', logParameters).then(async () => { + const forkLogger = loggerService.createChildLogger('forked-thread') + process.chdir(baseDir) // Migrate config file, if needed - migrationService.migrate(configFile, 'OIBus-main', logParameters).then(async () => { + migrationService.migrate(configFile, loggerService.createChildLogger('migration')).then(async () => { // this condition is reached only for a worker (i.e. not master) // so this is here where we start the web-server, OIBusEngine and HistoryQueryEngine @@ -126,71 +124,82 @@ logger.changeParameters('OIBus-main', logParameters).then(async () => { const safeMode = process.env.SAFE_MODE === 'true' - const oibusEngine = new OIBusEngine(configService, encryptionService) - const historyQueryEngine = new HistoryQueryEngine(configService, encryptionService) - const server = new Server(encryptionService, oibusEngine, historyQueryEngine) + const { engineConfig } = configService.getConfig() + const oibusLoggerService = new LoggerService('oibusLogger') + oibusLoggerService.setEncryptionService(encryptionService) + await oibusLoggerService.start(engineConfig.name, engineConfig.logParameters) + const oibusEngine = new OIBusEngine(configService, encryptionService, oibusLoggerService) + const historyQueryEngine = new HistoryQueryEngine(configService, encryptionService, oibusLoggerService) + const server = new Server( + encryptionService, + oibusEngine, + historyQueryEngine, + oibusLoggerService.createChildLogger('web-server'), + ) if (check) { - logger.warn('OIBus is running in check mode.') + forkLogger.warn('OIBus is running in check mode.') process.send({ type: 'shutdown-ready' }) } else { oibusEngine.start(safeMode).then(() => { - logger.info('OIBus engine fully started.') + forkLogger.info('OIBus engine fully started.') }) historyQueryEngine.start(safeMode).then(() => { - logger.info('History query engine fully started.') + forkLogger.info('History query engine fully started.') }) server.start().then(() => { - logger.info('OIBus web server fully started.') + forkLogger.info('OIBus web server fully started.') }) } // Catch Ctrl+C and properly stop the Engine process.on('SIGINT', () => { - logger.info('SIGINT (Ctrl+C) received. Stopping everything.') + forkLogger.info('SIGINT (Ctrl+C) received. Stopping everything.') const stopAll = [oibusEngine.stop(), historyQueryEngine.stop(), server.stop()] - Promise.allSettled(stopAll) - .then(() => { - process.exit() - }) + Promise.allSettled(stopAll).then(() => { + process.exit() + }) }) // Receive messages from the master process. process.on('message', async (msg) => { switch (msg.type) { case 'reload-oibus-engine': - logger.info('Reloading OIBus Engine') - await oibusEngine.stop() - await historyQueryEngine.stop() - await server.stop() - oibusEngine.start(safeMode).then(() => { - logger.info('OIBus engine fully started.') - }) - historyQueryEngine.start().then(() => { - logger.info('History engine fully started.') - }) - server.start().then(() => { - logger.info('OIBus web server fully started.') - }) + { + forkLogger.info('Reloading OIBus Engine') + await oibusEngine.stop() + await historyQueryEngine.stop() + await server.stop() + const { engineConfig: newEngineConfig } = configService.getConfig() + await oibusLoggerService.start(newEngineConfig.name, newEngineConfig.logParameters) + + oibusEngine.start(safeMode).then(() => { + forkLogger.info('OIBus engine fully started.') + }) + historyQueryEngine.start(safeMode).then(() => { + forkLogger.info('History engine fully started.') + }) + server.start().then(() => { + forkLogger.info('OIBus web server fully started.') + }) + } break case 'reload': - logger.info('Reloading OIBus') - Promise.allSettled([oibusEngine.stop(), historyQueryEngine.stop(), server.stop()]) - .then(() => { - process.exit() - }) + forkLogger.info('Reloading OIBus') + Promise.allSettled([oibusEngine.stop(), historyQueryEngine.stop(), server.stop()]).then(() => { + process.exit() + }) break case 'shutdown': - logger.info('Shutting down OIBus') - Promise.allSettled([oibusEngine.stop(), historyQueryEngine.stop(), server?.stop()]) - .then(() => { - process.send({ type: 'shutdown-ready' }) - }) + forkLogger.info('Shutting down OIBus') + Promise.allSettled([oibusEngine.stop(), historyQueryEngine.stop(), server?.stop()]).then(() => { + process.send({ type: 'shutdown-ready' }) + }) break default: - logger.warn(`Unknown message type received from Master: ${msg.type}`) + forkLogger.warn(`Unknown message type received from Master: ${msg.type}`) } }) }) - } -}) + }) +} diff --git a/src/migration/migration-rules.js b/src/migration/migration-rules.js index 1d2be5700e..da5f8bb281 100644 --- a/src/migration/migration-rules.js +++ b/src/migration/migration-rules.js @@ -1576,7 +1576,14 @@ module.exports = { 29: async (config, logger) => { const { httpRequest } = config.engine + for (const south of config.south) { + logger.info(`Deleting log parameters options for South "${south.id}".`) + delete south.logParameters + } for (const north of config.north) { + logger.info(`Deleting log parameters options for North "${north.id}".`) + delete north.logParameters + logger.info(`Add timeout field for North "${north.id}".`) north.caching.timeout = httpRequest.timeout @@ -1729,6 +1736,20 @@ module.exports = { config.engine.logParameters.fileLog.maxSize = Math.round(config.engine.logParameters.fileLog.maxSize / 1000000) } + logger.info('Changing engine log options "none" into "silent".') + if (config.engine.logParameters.consoleLog.level === 'none') { + config.engine.logParameters.consoleLog.level = 'silent' + } + if (config.engine.logParameters.fileLog.level === 'none') { + config.engine.logParameters.fileLog.level = 'silent' + } + if (config.engine.logParameters.sqliteLog.level === 'none') { + config.engine.logParameters.sqliteLog.level = 'silent' + } + if (config.engine.logParameters.lokiLog.level === 'none') { + config.engine.logParameters.lokiLog.level = 'silent' + } + if (config.engine.logParameters.sqliteLog.maxNumberOfLogs <= 100000) { logger.info('Changing minimum number of logs in SQLite database') config.engine.logParameters.sqliteLog.maxNumberOfLogs = 100000 diff --git a/src/migration/migration.service.js b/src/migration/migration.service.js index e408d840f0..ec2b6bd658 100644 --- a/src/migration/migration.service.js +++ b/src/migration/migration.service.js @@ -2,7 +2,6 @@ const fs = require('node:fs/promises') const path = require('node:path') const migrationRules = require('./migration-rules') -const LoggerService = require('../service/logger/logger.service') const REQUIRED_SCHEMA_VERSION = 29 const DEFAULT_VERSION = 1 @@ -53,15 +52,11 @@ const migrateImpl = async (configVersion, config, configFilePath, logger) => { /** * Migrate if needed. * @param {String} configFilePath - The config file path - * @param {String} oibusName - The name of this oibus - * @param {Object} logParameters - The log parameters to use (given by index.js) + * @param {Logger} logger - * @returns {Promise} - The result promise */ -const migrate = async (configFilePath, oibusName, logParameters) => { - const logger = new LoggerService('migration') +const migrate = async (configFilePath, logger) => { try { - await logger.changeParameters(oibusName, logParameters) - let fileStat try { fileStat = await fs.stat(configFilePath) @@ -74,6 +69,8 @@ const migrate = async (configFilePath, oibusName, logParameters) => { if (configVersion < REQUIRED_SCHEMA_VERSION) { logger.info(`Config file is not up-to-date. Starting migration from version ${configVersion} to ${REQUIRED_SCHEMA_VERSION}`) await migrateImpl(configVersion, config, configFilePath, logger) + } else { + logger.info('Config file is up-to-date.') } } } catch (migrationError) { diff --git a/src/north/north-amazon-s3/north-amazon-s3.js b/src/north/north-amazon-s3/north-amazon-s3.js index 3cd38a622c..ab84707c51 100644 --- a/src/north/north-amazon-s3/north-amazon-s3.js +++ b/src/north/north-amazon-s3/north-amazon-s3.js @@ -28,15 +28,18 @@ class NorthAmazonS3 extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleFiles = true diff --git a/src/north/north-amazon-s3/north-amazon-s3.spec.js b/src/north/north-amazon-s3/north-amazon-s3.spec.js index e76e986a62..f816bafacc 100644 --- a/src/north/north-amazon-s3/north-amazon-s3.spec.js +++ b/src/north/north-amazon-s3/north-amazon-s3.spec.js @@ -47,6 +47,15 @@ const proxies = [ }, ] +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let configuration = null let north = null @@ -82,8 +91,8 @@ describe('NorthAmazonS3', () => { }, subscribedTo: [], } - north = new AmazonS3(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new AmazonS3(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/north/north-connector.js b/src/north/north-connector.js index 533e16825d..fd692c98de 100644 --- a/src/north/north-connector.js +++ b/src/north/north-connector.js @@ -1,7 +1,6 @@ const path = require('node:path') const EncryptionService = require('../service/encryption.service') -const LoggerService = require('../service/logger/logger.service') const CertificateService = require('../service/certificate.service') const StatusService = require('../service/status.service') const ValueCache = require('../service/cache/value-cache.service') @@ -33,11 +32,13 @@ class NorthConnector { * @constructor * @param {Object} configuration - The North connector settings * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { this.canHandleValues = false this.canHandleFiles = false @@ -52,6 +53,7 @@ class NorthConnector { this.proxies = proxies this.encryptionService = EncryptionService.getInstance() + this.logger = logger // Variable initialized in start() this.baseFolder = null @@ -59,7 +61,6 @@ class NorthConnector { this.valueCache = null this.fileCache = null this.archiveService = null - this.logger = null this.certificate = null this.keyFile = null this.certFile = null @@ -75,17 +76,13 @@ class NorthConnector { /** * Initialize services (logger, certificate, status data) at startup * @param {String} baseFolder - The base cache folder - * @param {String} oibusName - The OIBus name - * @param {Object} defaultLogParameters - The default logs parameters + * @param {String} _oibusName - The OIBus name * @returns {Promise} - The result promise */ - async start(baseFolder, oibusName, defaultLogParameters) { + async start(baseFolder, _oibusName) { this.baseFolder = path.resolve(baseFolder, `north-${this.id}`) this.statusService = new StatusService() - this.logger = new LoggerService(`North:${this.name}`) - this.logger.setEncryptionService(this.encryptionService) - await this.logger.changeParameters(oibusName, defaultLogParameters, this.logParameters) await createFolder(this.baseFolder) diff --git a/src/north/north-connector.spec.js b/src/north/north-connector.spec.js index 1787b17c42..4cf1aae87e 100644 --- a/src/north/north-connector.spec.js +++ b/src/north/north-connector.spec.js @@ -7,7 +7,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../service/database.service') -jest.mock('../service/logger/logger.service') jest.mock('../service/status.service') jest.mock('../service/certificate.service') jest.mock('../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -17,6 +16,15 @@ jest.mock('../service/cache/value-cache.service') jest.mock('../service/cache/file-cache.service') jest.mock('../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let north = null @@ -43,8 +51,8 @@ describe('NorthConnector', () => { }, }, } - north = new NorthConnector(configuration, [{ name: 'proxyTest' }]) - await north.start('baseFolder', 'oibusName', {}) + north = new NorthConnector(configuration, [{ name: 'proxyTest' }], logger) + await north.start('baseFolder', 'oibusName') }) afterEach(() => { diff --git a/src/north/north-console/north-console.js b/src/north/north-console/north-console.js index 8fc427eeff..8fe6b77bd5 100644 --- a/src/north/north-console/north-console.js +++ b/src/north/north-console/north-console.js @@ -13,15 +13,18 @@ class Console extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true this.canHandleFiles = true diff --git a/src/north/north-console/north-console.spec.js b/src/north/north-console/north-console.spec.js index 02d66cf939..287604a3a2 100644 --- a/src/north/north-console/north-console.spec.js +++ b/src/north/north-console/north-console.spec.js @@ -11,7 +11,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -19,32 +18,41 @@ jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let north = null -beforeEach(async () => { - jest.resetAllMocks() - jest.useFakeTimers().setSystemTime(new Date(nowDateString)) - - configuration = { - settings: { verbose: false }, - caching: { - sendInterval: 1000, - retryInterval: 5000, - groupCount: 10000, - maxSendCount: 10000, - archive: { - enabled: true, - retentionDuration: 720, +describe('North Console', () => { + beforeEach(async () => { + jest.resetAllMocks() + jest.useFakeTimers().setSystemTime(new Date(nowDateString)) + + configuration = { + settings: { verbose: false }, + caching: { + sendInterval: 1000, + retryInterval: 5000, + groupCount: 10000, + maxSendCount: 10000, + archive: { + enabled: true, + retentionDuration: 720, + }, }, - }, - } - north = new Console(configuration, []) - await north.start('baseFolder', 'oibusName', {}) -}) + } + north = new Console(configuration, [], logger) + await north.start('baseFolder', 'oibusName') + }) -describe('North Console', () => { it('should be properly initialized', () => { expect(north.canHandleFiles).toBeTruthy() expect(north.canHandleValues).toBeTruthy() diff --git a/src/north/north-csv-to-http/north-csv-to-http.js b/src/north/north-csv-to-http/north-csv-to-http.js index 856f1179a6..fc0108bf3d 100644 --- a/src/north/north-csv-to-http/north-csv-to-http.js +++ b/src/north/north-csv-to-http/north-csv-to-http.js @@ -19,15 +19,18 @@ class NorthCsvToHttp extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleFiles = true diff --git a/src/north/north-csv-to-http/north-csv-to-http.spec.js b/src/north/north-csv-to-http/north-csv-to-http.spec.js index 72bcf49704..634517e144 100644 --- a/src/north/north-csv-to-http/north-csv-to-http.spec.js +++ b/src/north/north-csv-to-http/north-csv-to-http.spec.js @@ -14,7 +14,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -24,6 +23,15 @@ jest.mock('../../service/cache/archive.service') jest.mock('../../service/utils') jest.mock('../../service/http-request-static-functions') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let configuration = null let north = null @@ -77,18 +85,18 @@ describe('NorthCsvToHttp', () => { }, subscribedTo: [], } - north = new CsvToHttp(configuration, []) + north = new CsvToHttp(configuration, [], logger) }) it('should be properly initialized', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') expect(north.canHandleValues).toBeFalsy() expect(north.canHandleFiles).toBeTruthy() }) it('should properly reject file if type is other than csv', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') await expect(north.handleFile('filePath')).rejects .toThrowError('Invalid file format: .csv file expected. File "filePath" skipped.') @@ -109,7 +117,7 @@ describe('NorthCsvToHttp', () => { ['5', '2020-12-17 05:00'], ]) - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') await north.handleFile('csvToHttpTest.csv') @@ -117,7 +125,7 @@ describe('NorthCsvToHttp', () => { }) it('should properly test validity of header', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const jsonObject = {} @@ -142,7 +150,7 @@ describe('NorthCsvToHttp', () => { }) it('should properly send data (body.length <= bodyMaxLength)', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const httpBody = [] for (let i = 0; i < north.bodyMaxLength - 1; i += 1) { diff --git a/src/north/north-file-writer/north-file-writer.js b/src/north/north-file-writer/north-file-writer.js index 264fa1f059..9d7338f1cb 100644 --- a/src/north/north-file-writer/north-file-writer.js +++ b/src/north/north-file-writer/north-file-writer.js @@ -14,15 +14,18 @@ class NorthFileWriter extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true this.canHandleFiles = true diff --git a/src/north/north-file-writer/north-file-writer.spec.js b/src/north/north-file-writer/north-file-writer.spec.js index 10cd5f3c6b..d45c789292 100644 --- a/src/north/north-file-writer/north-file-writer.spec.js +++ b/src/north/north-file-writer/north-file-writer.spec.js @@ -8,7 +8,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -16,6 +15,15 @@ jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let north = null @@ -47,8 +55,8 @@ describe('NorthFileWriter', () => { }, subscribedTo: [], } - north = new FileWriter(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new FileWriter(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/north/north-influx-db/north-influx-db.js b/src/north/north-influx-db/north-influx-db.js index 3cb0cf2c7c..9305629905 100644 --- a/src/north/north-influx-db/north-influx-db.js +++ b/src/north/north-influx-db/north-influx-db.js @@ -40,15 +40,18 @@ class NorthInfluxDB extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true diff --git a/src/north/north-influx-db/north-influx-db.spec.js b/src/north/north-influx-db/north-influx-db.spec.js index 97a76f7073..c53e75d39a 100644 --- a/src/north/north-influx-db/north-influx-db.spec.js +++ b/src/north/north-influx-db/north-influx-db.spec.js @@ -6,7 +6,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -16,6 +15,15 @@ jest.mock('../../service/cache/archive.service') jest.mock('../../service/utils') jest.mock('../../service/http-request-static-functions') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' const values = [ { @@ -63,11 +71,11 @@ describe('North InfluxDB', () => { }, }, } - north = new InfluxDB(configuration, []) + north = new InfluxDB(configuration, [], logger) }) it('should call makeRequest and manage error', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') httpRequestStaticFunctions.httpSend.mockImplementation(() => { throw new Error('http error') @@ -83,7 +91,7 @@ describe('North InfluxDB', () => { }) it('should log error when there are not enough groups for placeholders in measurement', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') north.measurement = '%5$s' @@ -93,7 +101,7 @@ describe('North InfluxDB', () => { }) it('should log error when there are not enough groups for placeholders in tags', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') north.tags = 'site=%2$s,unit=%3$s,sensor=%5$s' await north.handleValues(values) @@ -102,7 +110,7 @@ describe('North InfluxDB', () => { }) it('should properly handle values with useDataKeyValue', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const valueWithDataLevel = [{ pointId: 'ANA/BL1RCP05', @@ -185,7 +193,7 @@ describe('North InfluxDB', () => { }) it('should properly retrieve timestamp with timestampPathInDataValue', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') north.timestampPathInDataValue = 'associatedTimestamp.timestamp' north.useDataKeyValue = true diff --git a/src/north/north-mongo-db/north-mongo-db.js b/src/north/north-mongo-db/north-mongo-db.js index 1da47f1a20..549dfbc962 100644 --- a/src/north/north-mongo-db/north-mongo-db.js +++ b/src/north/north-mongo-db/north-mongo-db.js @@ -15,15 +15,18 @@ class NorthMongoDB extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true diff --git a/src/north/north-mongo-db/north-mongo-db.spec.js b/src/north/north-mongo-db/north-mongo-db.spec.js index e794326180..a8a84a8020 100644 --- a/src/north/north-mongo-db/north-mongo-db.spec.js +++ b/src/north/north-mongo-db/north-mongo-db.spec.js @@ -9,7 +9,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -17,6 +16,15 @@ jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' const values = [ { @@ -64,11 +72,11 @@ describe('NorthMongoDB', () => { }, }, } - north = new MongoDB(configuration, []) + north = new MongoDB(configuration, [], logger) }) it('should properly connect and disconnect', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') expect(north.canHandleValues).toBeTruthy() expect(north.canHandleFiles).toBeFalsy() @@ -101,7 +109,7 @@ describe('NorthMongoDB', () => { }) it('should fail to connect', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(() => { @@ -120,7 +128,7 @@ describe('NorthMongoDB', () => { }) it('should fail to list collections', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), @@ -139,7 +147,7 @@ describe('NorthMongoDB', () => { }) it('should properly handle values', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') north.createCollection = true @@ -189,7 +197,7 @@ describe('NorthMongoDB', () => { }) it('should not create collection if it already exists', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const mongoDatabase = { listCollections: jest.fn(() => ({ toArray: jest.fn(() => ([{ name: 'collection1' }, { name: 'collection2' }])) })), @@ -212,7 +220,7 @@ describe('NorthMongoDB', () => { }) it('should properly manage collection creation errors', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') north.mongoDatabase = { collection: jest.fn(() => ({ @@ -249,7 +257,7 @@ describe('NorthMongoDB', () => { }) it('should properly handle values with index fields and collection values errors', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const insertManyFunction = jest.fn() const mongoDatabase = { diff --git a/src/north/north-mqtt/north-mqtt.js b/src/north/north-mqtt/north-mqtt.js index 63c9fa5a62..3006d3ddee 100644 --- a/src/north/north-mqtt/north-mqtt.js +++ b/src/north/north-mqtt/north-mqtt.js @@ -15,15 +15,18 @@ class NorthMQTT extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true @@ -63,11 +66,10 @@ class NorthMQTT extends NorthConnector { * Initialize services (logger, certificate, status data) at startup * @param {String} baseFolder - The base cache folder * @param {String} oibusName - The OIBus name - * @param {Object} defaultLogParameters - The default logs parameters * @returns {Promise} - The result promise */ - async start(baseFolder, oibusName, defaultLogParameters) { - await super.start(baseFolder, oibusName, defaultLogParameters) + async start(baseFolder, oibusName) { + await super.start(baseFolder, oibusName) this.clientId = `${oibusName}-${this.id}` } diff --git a/src/north/north-mqtt/north-mqtt.spec.js b/src/north/north-mqtt/north-mqtt.spec.js index 7671dad710..3326b790c1 100644 --- a/src/north/north-mqtt/north-mqtt.spec.js +++ b/src/north/north-mqtt/north-mqtt.spec.js @@ -10,13 +10,21 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Mock certificate service const CertificateService = jest.mock('../../service/certificate.service') @@ -59,8 +67,8 @@ describe('NorthMQTT', () => { }, subscribedTo: [], } - north = new MQTT(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new MQTT(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should properly connect', async () => { @@ -109,8 +117,8 @@ describe('NorthMQTT', () => { }, } - const mqttNorthCert = new MQTT(testMqttConfigWithFiles, []) - await mqttNorthCert.start('baseFolder', 'oibusName', {}) + const mqttNorthCert = new MQTT(testMqttConfigWithFiles, [], logger) + await mqttNorthCert.start('baseFolder', 'oibusName') mqttNorthCert.certificate = CertificateService await mqttNorthCert.connect() diff --git a/src/north/north-oianalytics/north-oianalytics.js b/src/north/north-oianalytics/north-oianalytics.js index 1eb0dd39bf..be9e9cfed9 100644 --- a/src/north/north-oianalytics/north-oianalytics.js +++ b/src/north/north-oianalytics/north-oianalytics.js @@ -13,15 +13,18 @@ class NorthOIAnalytics extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true this.canHandleFiles = true diff --git a/src/north/north-oianalytics/north-oianalytics.spec.js b/src/north/north-oianalytics/north-oianalytics.spec.js index 5c2cf2bd6a..1e2ff42216 100644 --- a/src/north/north-oianalytics/north-oianalytics.spec.js +++ b/src/north/north-oianalytics/north-oianalytics.spec.js @@ -9,7 +9,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -19,6 +18,15 @@ jest.mock('../../service/cache/archive.service') jest.mock('../../service/utils') jest.mock('../../service/http-request-static-functions') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let north = null @@ -56,8 +64,8 @@ describe('NorthOIAnalytics', () => { proxy: '', subscribedTo: [], } - north = new OIAnalytics(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new OIAnalytics(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/north/north-oiconnect/north-oiconnect.js b/src/north/north-oiconnect/north-oiconnect.js index 8b93d2fe0c..ae44790260 100644 --- a/src/north/north-oiconnect/north-oiconnect.js +++ b/src/north/north-oiconnect/north-oiconnect.js @@ -16,15 +16,18 @@ class NorthOIConnect extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true this.canHandleFiles = true @@ -51,11 +54,10 @@ class NorthOIConnect extends NorthConnector { * Initialize services (logger, certificate, status data) at startup * @param {String} baseFolder - The base cache folder * @param {String} oibusName - The OIBus name - * @param {Object} defaultLogParameters - The default logs parameters * @returns {Promise} - The result promise */ - async start(baseFolder, oibusName, defaultLogParameters) { - await super.start(baseFolder, oibusName, defaultLogParameters) + async start(baseFolder, oibusName) { + await super.start(baseFolder, oibusName) const name = `${oibusName}:${this.name}` this.valuesUrl = `${this.host}${this.valuesEndpoint}?name=${name}` this.fileUrl = `${this.host}${this.fileEndpoint}?name=${name}` diff --git a/src/north/north-oiconnect/north-oiconnect.spec.js b/src/north/north-oiconnect/north-oiconnect.spec.js index ad0cf2bff8..6d3ccc626f 100644 --- a/src/north/north-oiconnect/north-oiconnect.spec.js +++ b/src/north/north-oiconnect/north-oiconnect.spec.js @@ -8,7 +8,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -18,6 +17,15 @@ jest.mock('../../service/cache/archive.service') jest.mock('../../service/utils') jest.mock('../../service/http-request-static-functions') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let north = null @@ -54,8 +62,8 @@ describe('NorthOIConnect', () => { }, subscribedTo: [], } - north = new OIConnect(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new OIConnect(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/north/north-timescale-db/north-timescale-db.js b/src/north/north-timescale-db/north-timescale-db.js index 9cadd96a78..728dd44597 100644 --- a/src/north/north-timescale-db/north-timescale-db.js +++ b/src/north/north-timescale-db/north-timescale-db.js @@ -15,15 +15,18 @@ class NorthTimescaleDB extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true diff --git a/src/north/north-timescale-db/north-timescale-db.spec.js b/src/north/north-timescale-db/north-timescale-db.spec.js index 4fe2def2cd..ab3877ea7d 100644 --- a/src/north/north-timescale-db/north-timescale-db.spec.js +++ b/src/north/north-timescale-db/north-timescale-db.spec.js @@ -9,7 +9,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -17,6 +16,15 @@ jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' const values = [ { @@ -62,7 +70,7 @@ describe('North TimescaleDB', () => { }, subscribedTo: [], } - north = new TimescaleDB(configuration, []) + north = new TimescaleDB(configuration, [], logger) }) it('should properly handle values and publish them', async () => { @@ -72,7 +80,7 @@ describe('North TimescaleDB', () => { end: jest.fn(), } pg.Client.mockReturnValue(client) - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') await north.connect() expect(north.canHandleValues).toBeTruthy() @@ -98,7 +106,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle connection errors', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(() => { throw new Error('test') @@ -114,7 +122,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle values with publish error', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), @@ -135,7 +143,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle values with optional fields and table errors', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), query: jest.fn() } pg.Client.mockReturnValue(client) @@ -156,7 +164,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle values with optional fields', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), query: jest.fn() } pg.Client.mockReturnValue(client) @@ -182,7 +190,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle values with only optional fields and timestamp', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), query: jest.fn() } pg.Client.mockReturnValue(client) @@ -208,7 +216,7 @@ describe('North TimescaleDB', () => { }) it('should properly handle values with useDataKeyValue', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), @@ -245,7 +253,7 @@ describe('North TimescaleDB', () => { }) it('should properly retrieve timestamp with timestampPathInDataValue', async () => { - await north.start('baseFolder', 'oibusName', {}) + await north.start('baseFolder', 'oibusName') const client = { connect: jest.fn(), diff --git a/src/north/north-watsy/north-watsy.js b/src/north/north-watsy/north-watsy.js index a4c1944267..b8d1675f45 100644 --- a/src/north/north-watsy/north-watsy.js +++ b/src/north/north-watsy/north-watsy.js @@ -31,15 +31,18 @@ class NorthWATSY extends NorthConnector { * @constructor * @param {Object} configuration - The North connector configuration * @param {Object[]} proxies - The list of available proxies + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, proxies, + logger, ) { super( configuration, proxies, + logger, ) this.canHandleValues = true @@ -70,11 +73,10 @@ class NorthWATSY extends NorthConnector { * Initialize services (logger, certificate, status data) at startup * @param {String} baseFolder - The base cache folder * @param {String} oibusName - The OIBus name - * @param {Object} defaultLogParameters - The default logs parameters * @returns {Promise} - The result promise */ - async start(baseFolder, oibusName, defaultLogParameters) { - await super.start(baseFolder, oibusName, defaultLogParameters) + async start(baseFolder, oibusName) { + await super.start(baseFolder, oibusName) this.mqttTopic = initMQTTTopic(oibusName, this.host) } diff --git a/src/north/north-watsy/north-watsy.spec.js b/src/north/north-watsy/north-watsy.spec.js index 3dc925e1d9..deddcb1746 100644 --- a/src/north/north-watsy/north-watsy.spec.js +++ b/src/north/north-watsy/north-watsy.spec.js @@ -18,7 +18,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/certificate.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -26,6 +25,15 @@ jest.mock('../../service/cache/value-cache.service') jest.mock('../../service/cache/file-cache.service') jest.mock('../../service/cache/archive.service') +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let configuration = null let north = null @@ -61,8 +69,8 @@ describe('NorthWATSY', () => { }, subscribedTo: [], } - north = new WATSYConnect(configuration, []) - await north.start('baseFolder', 'oibusName', {}) + north = new WATSYConnect(configuration, [], logger) + await north.start('baseFolder', 'oibusName') }) it('should properly connect', async () => { diff --git a/src/service/cache/archive.service.js b/src/service/cache/archive.service.js index e11539cebc..f6d829a80c 100644 --- a/src/service/cache/archive.service.js +++ b/src/service/cache/archive.service.js @@ -14,7 +14,7 @@ const ARCHIVE_FOLDER = 'archive' class ArchiveService { /** * @param {String} northId - The North ID connector - * @param {Logger} logger - The logger + * @param {LoggerService} loggerService - The logger * @param {String} baseFolder - The North cache folder generated as north-connectorId. This base folder can * be in data-stream or history-query folder depending on the connector use case * @param {Boolean} enabled - If the archive mode for this North connector is enabled @@ -23,13 +23,13 @@ class ArchiveService { */ constructor( northId, - logger, + loggerService, baseFolder, enabled, retentionDuration, ) { this.northId = northId - this.logger = logger + this.loggerService = loggerService this.baseFolder = baseFolder this.enabled = enabled // Convert from hours to ms to compare with mtimeMs (file modified time in ms) @@ -72,17 +72,17 @@ class ArchiveService { // Move cache file into the archive folder try { await fs.rename(filePathInCache, archivePath) - this.logger.debug(`File "${filePathInCache}" moved to "${archivePath}".`) + this.loggerService.debug(`File "${filePathInCache}" moved to "${archivePath}".`) } catch (renameError) { - this.logger.error(renameError) + this.loggerService.error(renameError) } } else { // Delete original file try { await fs.unlink(filePathInCache) - this.logger.debug(`File "${filePathInCache}" removed from disk.`) + this.loggerService.debug(`File "${filePathInCache}" removed from disk.`) } catch (unlinkError) { - this.logger.error(unlinkError) + this.loggerService.error(unlinkError) } } } @@ -92,7 +92,7 @@ class ArchiveService { * @return {void} */ async refreshArchiveFolder() { - this.logger.debug('Parse archive folder to remove old files.') + this.loggerService.debug('Parse archive folder to remove old files.') // If a timeout already runs, clear it if (this.archiveTimeout) { clearTimeout(this.archiveTimeout) @@ -103,7 +103,7 @@ class ArchiveService { files = await fs.readdir(this.archiveFolder) } catch (error) { // If the archive folder doest not exist (removed by the user for example), an error is logged - this.logger.error(error) + this.loggerService.error(error) } if (files.length > 0) { const referenceDate = new Date().getTime() @@ -113,7 +113,7 @@ class ArchiveService { async () => this.removeFileIfTooOld(file, referenceDate, this.archiveFolder), ), Promise.resolve()) } else { - this.logger.debug(`The archive folder "${this.archiveFolder}" is empty. Nothing to delete.`) + this.loggerService.debug(`The archive folder "${this.archiveFolder}" is empty. Nothing to delete.`) } this.archiveTimeout = setTimeout(this.refreshArchiveFolder.bind(this), ARCHIVE_TIMEOUT) } @@ -131,14 +131,14 @@ class ArchiveService { // If a file is being written or corrupted, the stat method can fail an error is logged stats = await fs.stat(path.join(archiveFolder, filename)) } catch (error) { - this.logger.error(error) + this.loggerService.error(error) } if (stats && stats.mtimeMs + this.retentionDuration < referenceDate) { try { await fs.unlink(path.join(archiveFolder, filename)) - this.logger.debug(`File "${path.join(archiveFolder, filename)}" removed from archive.`) + this.loggerService.debug(`File "${path.join(archiveFolder, filename)}" removed from archive.`) } catch (unlinkError) { - this.logger.error(unlinkError) + this.loggerService.error(unlinkError) } } } diff --git a/src/service/cache/value-cache.service.js b/src/service/cache/value-cache.service.js index 598b3d4557..2a7cdcf0c5 100644 --- a/src/service/cache/value-cache.service.js +++ b/src/service/cache/value-cache.service.js @@ -19,7 +19,7 @@ const ERROR_FOLDER = 'values-errors' class ValueCacheService { /** * @param {String} northId - The North ID connector - * @param {Logger} logger - The logger + * @param {LoggerService} logger - The logger * @param {String} baseFolder - The North cache folder generated as north-connectorId. This base folder can * be in data-stream or history-query folder depending on the connector use case * @param {Function} northSendValuesCallback - Method used by the North to handle values diff --git a/src/service/logger/logger.service.js b/src/service/logger/logger.service.js index a43ededba0..3503e1ddaf 100644 --- a/src/service/logger/logger.service.js +++ b/src/service/logger/logger.service.js @@ -3,7 +3,6 @@ const path = require('node:path') const pino = require('pino') const LOG_FOLDER_NAME = 'logs' -const ENGINE_LOG_LEVEL_ENTRY = 'engine' const LOG_FILE_NAME = 'journal.log' const LOG_DB_NAME = 'journal.db' @@ -41,68 +40,44 @@ class LoggerService { * Run the appropriate pino log transports according to the configuration * @param {String} oibusName - The OIBus name * @param {Object} defaultLogParameters - The default logs parameters - * @param {Object} specificParameters - Override some log settings from a connector * @returns {Promise} - The result promise */ - async changeParameters(oibusName, defaultLogParameters, specificParameters = {}) { + async start(oibusName, defaultLogParameters) { const logParameters = JSON.parse(JSON.stringify(defaultLogParameters)) - - /** - * Replacing global log parameters by specific one if not set to engine level - */ - if (specificParameters.consoleLevel && specificParameters.consoleLevel !== ENGINE_LOG_LEVEL_ENTRY) { - logParameters.consoleLog.level = specificParameters.consoleLevel - } - if (specificParameters.fileLevel && specificParameters.fileLevel !== ENGINE_LOG_LEVEL_ENTRY) { - logParameters.fileLog.level = specificParameters.fileLevel - } - if (specificParameters.sqliteLevel && specificParameters.sqliteLevel !== ENGINE_LOG_LEVEL_ENTRY) { - logParameters.sqliteLog.level = specificParameters.sqliteLevel - } - if (specificParameters.lokiLevel && specificParameters.lokiLevel !== ENGINE_LOG_LEVEL_ENTRY) { - logParameters.lokiLog.level = specificParameters.lokiLevel - } - const targets = [] const { consoleLog, fileLog, sqliteLog, lokiLog } = logParameters - if (consoleLog.level !== 'none') { - targets.push({ target: 'pino-pretty', options: { colorize: true, singleLine: true }, level: consoleLog.level }) - } + targets.push({ target: 'pino-pretty', options: { colorize: true, singleLine: true }, level: consoleLog.level }) - if (fileLog.level !== 'none') { - const fileName = fileLog.fileName ? fileLog.fileName : path.resolve(LOG_FOLDER_NAME, LOG_FILE_NAME) - // TODO: add number of files in pino-roll - targets.push({ - target: 'pino-roll', - options: { - file: fileName, - size: fileLog.maxSize, - frequency: 'daily', - }, - level: fileLog.level, - }) - } + const filename = fileLog.fileName ? fileLog.fileName : path.resolve(LOG_FOLDER_NAME, LOG_FILE_NAME) + targets.push({ + target: 'pino-roll', + options: { + file: filename, + size: fileLog.maxSize, + }, + level: fileLog.level, + }) - if (sqliteLog.level !== 'none') { - const fileName = sqliteLog.fileName ? sqliteLog.fileName : path.resolve(LOG_FOLDER_NAME, LOG_DB_NAME) + if (sqliteLog) { + const sqlDatabaseName = sqliteLog.fileName ? sqliteLog.fileName : path.resolve(LOG_FOLDER_NAME, LOG_DB_NAME) targets.push({ target: path.join(__dirname, 'sqlite-transport.js'), options: { - fileName, - maxFileSize: sqliteLog.maxSize, + fileName: sqlDatabaseName, + maxNumberOfLogs: sqliteLog.maxNumberOfLogs, }, level: sqliteLog.level, }) } - if (lokiLog.level !== 'none') { + if (lokiLog?.lokiAddress) { try { targets.push({ target: path.join(__dirname, 'loki-transport.js'), options: { username: lokiLog.username, - password: await this.encryptionService.decryptText(lokiLog.password), + password: lokiLog.password ? await this.encryptionService.decryptText(lokiLog.password) : null, tokenAddress: lokiLog.tokenAddress, lokiAddress: lokiLog.lokiAddress, oibusName, @@ -117,46 +92,25 @@ class LoggerService { } } - if (targets.length > 0) { - this.logger = await pino({ - mixin: this.pinoMixin.bind(this), - base: undefined, - level: 'trace', // default to trace since each transport has its defined level - timestamp: pino.stdTimeFunctions.isoTime, - transport: { targets }, - }) - } + this.logger = await pino({ + mixin: this.pinoMixin.bind(this), + base: undefined, + level: 'trace', // default to trace since each transport has its defined level + timestamp: pino.stdTimeFunctions.isoTime, + transport: { targets }, + }) + } + + createChildLogger(scope) { + return this.logger.child({ scope }) } /** * Mixin method to add parameters to the logs for Pino logger - * @returns {{scope: String, source: String}} - Add scope and source to the log + * @returns {{ source: String}} - Add scope and source to the log */ pinoMixin() { - return { - source: this.getSource(), - scope: this.scope, - } - } - - error(message) { - this.logger?.error(message.stack || message) - } - - warn(message) { - this.logger?.warn(message) - } - - info(message) { - this.logger?.info(message) - } - - debug(message) { - this.logger?.debug(message) - } - - trace(message) { - this.logger?.trace(message) + return { source: this.getSource() } } /** diff --git a/src/service/logger/logger.service.spec.js b/src/service/logger/logger.service.spec.js index b5f3352d37..664283d067 100644 --- a/src/service/logger/logger.service.spec.js +++ b/src/service/logger/logger.service.spec.js @@ -20,14 +20,13 @@ describe('Logger', () => { fileLog: { level: 'warn', fileName: 'test-journal.log', - maxSize: 1000000, + maxSize: 10, numberOfFiles: 5, - tailable: true, }, sqliteLog: { level: 'info', fileName: 'test-journal.db', - maxSize: 1234, + maxNumberOfLogs: 1234, }, lokiLog: { @@ -53,8 +52,7 @@ describe('Logger', () => { target: 'pino-roll', options: { file: settings.logParameters.fileLog.fileName, - frequency: 'daily', - size: 1000000, + size: 10, }, level: settings.logParameters.fileLog.level, }, @@ -62,7 +60,7 @@ describe('Logger', () => { target: path.join(__dirname, 'sqlite-transport.js'), options: { fileName: settings.logParameters.sqliteLog.fileName, - maxFileSize: settings.logParameters.sqliteLog.maxSize, + maxNumberOfLogs: settings.logParameters.sqliteLog.maxNumberOfLogs, }, level: settings.logParameters.sqliteLog.level, }, @@ -80,7 +78,7 @@ describe('Logger', () => { }, ] - await logger.changeParameters(settings.name, settings.logParameters) + await logger.start(settings.name, settings.logParameters) expect(logger.scope).toEqual('main') expect(pino).toHaveBeenCalledTimes(1) @@ -93,76 +91,6 @@ describe('Logger', () => { }) }) - it('should be properly initialized', async () => { - logger = new LoggerService() - logger.setEncryptionService(encryptionService) - - await logger.changeParameters(settings.name, { - consoleLog: { level: 'none' }, - fileLog: { level: 'none' }, - sqliteLog: { level: 'none' }, - lokiLog: { level: 'none' }, - }) - - expect(pino).not.toHaveBeenCalled() - }) - - it('should be properly initialized with overwritten settings', async () => { - logger = new LoggerService('specific-logger') - logger.setEncryptionService(encryptionService) - - const specificParameters = { - consoleLevel: 'trace', - fileLevel: 'trace', - sqliteLevel: 'trace', - lokiLevel: 'trace', - } - const expectedTargets = [ - { target: 'pino-pretty', options: { colorize: true, singleLine: true }, level: specificParameters.consoleLevel }, - { - target: 'pino-roll', - options: { - file: settings.logParameters.fileLog.fileName, - frequency: 'daily', - size: 1000000, - }, - level: specificParameters.fileLevel, - }, - { - target: path.join(__dirname, 'sqlite-transport.js'), - options: { - fileName: settings.logParameters.sqliteLog.fileName, - maxFileSize: settings.logParameters.sqliteLog.maxSize, - }, - level: specificParameters.sqliteLevel, - }, - { - target: path.join(__dirname, 'loki-transport.js'), - options: { - username: settings.logParameters.lokiLog.username, - password: settings.logParameters.lokiLog.password, - tokenAddress: settings.logParameters.lokiLog.tokenAddress, - lokiAddress: settings.logParameters.lokiLog.lokiAddress, - oibusName: settings.name, - interval: settings.logParameters.lokiLog.interval, - }, - level: specificParameters.lokiLevel, - }, - ] - - await logger.changeParameters(settings.name, settings.logParameters, specificParameters) - expect(logger.scope).toEqual('specific-logger') - - expect(pino).toHaveBeenCalledTimes(1) - expect(pino).toHaveBeenCalledWith({ - mixin: expect.any(Function), - base: undefined, - level: 'trace', - timestamp: pino.stdTimeFunctions.isoTime, - transport: { targets: expectedTargets }, - }) - }) - it('should be properly initialized with loki error and standard file names', async () => { jest.spyOn(console, 'error').mockImplementationOnce(() => {}) logger = new LoggerService('specific-logger') @@ -171,54 +99,19 @@ describe('Logger', () => { settings.logParameters.fileLog.fileName = undefined settings.logParameters.sqliteLog.fileName = undefined - await logger.changeParameters(settings.name, settings.logParameters) + await logger.start(settings.name, settings.logParameters) expect(console.error).toHaveBeenCalledTimes(1) expect(console.error).toHaveBeenCalledWith(new Error('decrypt-error')) }) - it('should properly log messages', async () => { - logger = new LoggerService() - logger.logger = { - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - } - - logger.error('error') - logger.warn('warn') - logger.info('info') - logger.debug('debug') - logger.trace('trace') - - expect(logger.logger.error).toHaveBeenCalledTimes(1) - expect(logger.logger.error).toHaveBeenCalledWith('error') - expect(logger.logger.warn).toHaveBeenCalledTimes(1) - expect(logger.logger.warn).toHaveBeenCalledWith('warn') - expect(logger.logger.info).toHaveBeenCalledTimes(1) - expect(logger.logger.info).toHaveBeenCalledWith('info') - expect(logger.logger.debug).toHaveBeenCalledTimes(1) - expect(logger.logger.debug).toHaveBeenCalledWith('debug') - expect(logger.logger.trace).toHaveBeenCalledTimes(1) - expect(logger.logger.trace).toHaveBeenCalledWith('trace') - - logger.error({ stack: 'stack message' }) - expect(logger.logger.error).toHaveBeenCalledTimes(2) - expect(logger.logger.error).toHaveBeenCalledWith('stack message') - }) - it('should properly get additional parameter from mixin', () => { logger = new LoggerService('specific-logger') logger.getSource = jest.fn(() => 'my file source') const mixinResults = logger.pinoMixin() - expect(mixinResults).toEqual({ - scope: 'specific-logger', - source: 'my file source', - }) + expect(mixinResults).toEqual({ source: 'my file source' }) }) it('should properly get source', () => { diff --git a/src/service/logger/sqlite-transport.js b/src/service/logger/sqlite-transport.js index 0100b8cfff..578f17da4d 100644 --- a/src/service/logger/sqlite-transport.js +++ b/src/service/logger/sqlite-transport.js @@ -2,8 +2,9 @@ const build = require('pino-abstract-transport') const db = require('better-sqlite3') const LOGS_TABLE_NAME = 'logs' -const NUMBER_OF_RECORDS_TO_DELETE = 10000 const DEFAULT_MAX_NUMBER_OF_LOGS = 2000000 +const CLEAN_UP_INTERVAL = 24 * 3600 * 1000 // One day + const LEVEL_FORMAT = { 10: 'trace', 20: 'debug', 30: 'info', 40: 'warn', 50: 'error', 60: 'fatal' } /** @@ -16,6 +17,7 @@ class SqliteTransport { this.fileName = options.fileName || ':memory:' this.tableName = options.tableName || LOGS_TABLE_NAME this.maxNumberOfLogs = options.maxNumberOfLogs || DEFAULT_MAX_NUMBER_OF_LOGS + this.removeOldLogsTimeout = null } /** @@ -25,10 +27,6 @@ class SqliteTransport { */ log = (payload) => { this.addLog(payload.time, payload.level, payload.scope, payload.source, payload.msg) - const numberOfLogs = this.countLogs() - if (numberOfLogs > this.maxNumberOfLogs) { - this.deleteOldLogs() - } } /** @@ -59,15 +57,20 @@ class SqliteTransport { * Delete old logs. * @return {void} */ - deleteOldLogs = () => { - const query = `DELETE + deleteOldLogsIfDatabaseTooLarge = () => { + const numberOfLogs = this.countLogs() + if (numberOfLogs > this.maxNumberOfLogs) { + const query = `DELETE FROM ${this.tableName} WHERE id IN ( SELECT id FROM ${this.tableName} ORDER BY id LIMIT ?);` - this.database.prepare(query).run(NUMBER_OF_RECORDS_TO_DELETE) + // Remove the excess of logs and one tenth of the max allowed size + const numberOfRecordToDelete = (numberOfLogs - this.maxNumberOfLogs) + this.maxNumberOfLogs / 10 + this.database.prepare(query).run(numberOfRecordToDelete) + } } /** @@ -84,6 +87,8 @@ class SqliteTransport { source TEXT, message TEXT);` this.database.prepare(query).run() + this.deleteOldLogsIfDatabaseTooLarge() + this.removeOldLogsTimeout = setInterval(this.deleteOldLogsIfDatabaseTooLarge.bind(this), CLEAN_UP_INTERVAL) } /** @@ -91,6 +96,7 @@ class SqliteTransport { * @returns {void} */ end = () => { + clearInterval(this.removeOldLogsTimeout) if (this.database) { this.database.close() } diff --git a/src/south/south-ads/south-ads.js b/src/south/south-ads/south-ads.js index 7762233d76..026e38f3f2 100644 --- a/src/south/south-ads/south-ads.js +++ b/src/south/south-ads/south-ads.js @@ -14,17 +14,20 @@ class SouthADS extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: true, diff --git a/src/south/south-ads/south-ads.spec.js b/src/south/south-ads/south-ads.spec.js index ae362dd08b..258ae3a29d 100644 --- a/src/south/south-ads/south-ads.spec.js +++ b/src/south/south-ads/south-ads.spec.js @@ -625,7 +625,14 @@ const GVLTestBadType = { }, } // End of global variables - +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let south = null @@ -775,8 +782,8 @@ describe('South ADS', () => { }, ], } - south = new ADS(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new ADS(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/south/south-connector.js b/src/south/south-connector.js index c9e56dc513..063079ade9 100644 --- a/src/south/south-connector.js +++ b/src/south/south-connector.js @@ -1,7 +1,6 @@ const path = require('node:path') const EncryptionService = require('../service/encryption.service') const databaseService = require('../service/database.service') -const LoggerService = require('../service/logger/logger.service') const CertificateService = require('../service/certificate.service') const StatusService = require('../service/status.service') const { generateIntervals, delay, createFolder } = require('../service/utils') @@ -36,6 +35,7 @@ class SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @param {Object} supportedModes - The supported modes * @return {void} */ @@ -43,6 +43,7 @@ class SouthConnector { configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, supportedModes = {}, ) { this.handlesPoints = false @@ -61,6 +62,7 @@ class SouthConnector { this.scanGroups = configuration.settings.scanGroups this.encryptionService = EncryptionService.getInstance() + this.logger = logger this.supportedModes = supportedModes this.numberOfRetrievedFiles = 0 @@ -84,19 +86,14 @@ class SouthConnector { /** * Initialize services (logger, certificate, status data) at startup * @param {String} baseFolder - The base cache folder - * @param {String} oibusName - The OIBus name - * @param {Object} defaultLogParameters - The default logs parameters + * @param {String} _oibusName - The OIBus name * @returns {Promise} - The result promise */ - async start(baseFolder, oibusName, defaultLogParameters) { + async start(baseFolder, _oibusName) { this.baseFolder = path.resolve(baseFolder, `south-${this.id}`) this.statusService = new StatusService() - this.logger = new LoggerService(`South:${this.name}`) - this.logger.setEncryptionService(this.encryptionService) - await this.logger.changeParameters(oibusName, defaultLogParameters, this.logParameters) - this.certificate = new CertificateService(this.logger) await this.certificate.init(this.keyFile, this.certFile, this.caFile) @@ -362,6 +359,8 @@ class SouthConnector { */ async addValues(values) { if (values.length > 0) { + // When coming from an external source, the south won't be found. + this.logger.trace(`Add ${values.length} values to cache from South "${this.name}".`) await this.engineAddValuesCallback(this.id, values) this.numberOfRetrievedValues += values.length this.statusService.updateStatusDataStream({ @@ -379,6 +378,8 @@ class SouthConnector { * @returns {Promise} - The result promise */ async addFile(filePath, preserveFiles) { + this.logger.trace(`Add file "${filePath}" to cache from South "${this.name}".`) + await this.engineAddFilesCallback(this.id, filePath, preserveFiles) this.numberOfRetrievedFiles += 1 this.statusService.updateStatusDataStream({ diff --git a/src/south/south-connector.spec.js b/src/south/south-connector.spec.js index 4eadf6d5d0..8afc6ed0fc 100644 --- a/src/south/south-connector.spec.js +++ b/src/south/south-connector.spec.js @@ -17,9 +17,17 @@ jest.mock('../service/utils', () => ({ const addValues = jest.fn() const addFiles = jest.fn() +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Mock services jest.mock('../service/database.service') -jest.mock('../service/logger/logger.service') jest.mock('../service/status.service') jest.mock('../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -33,8 +41,8 @@ describe('SouthConnector', () => { jest.useFakeTimers().setSystemTime(new Date(nowDateString)) configuration = { id: 'id', name: 'south', type: 'test', settings: {} } - south = new SouthConnector(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new SouthConnector(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) it('should be properly initialized without support, without scan mode', async () => { diff --git a/src/south/south-folder-scanner/south-folder-scanner.js b/src/south/south-folder-scanner/south-folder-scanner.js index d455e8882e..31ea164d0c 100644 --- a/src/south/south-folder-scanner/south-folder-scanner.js +++ b/src/south/south-folder-scanner/south-folder-scanner.js @@ -16,17 +16,20 @@ class SouthFolderScanner extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: false, diff --git a/src/south/south-folder-scanner/south-folder-scanner.spec.js b/src/south/south-folder-scanner/south-folder-scanner.spec.js index 5a66c360cf..86915293bd 100644 --- a/src/south/south-folder-scanner/south-folder-scanner.spec.js +++ b/src/south/south-folder-scanner/south-folder-scanner.spec.js @@ -18,10 +18,18 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let south = null @@ -48,8 +56,8 @@ describe('SouthFolderScanner', () => { points: [], scanMode: 'every10Second', } - south = new FolderScanner(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new FolderScanner(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') databaseService.getConfig.mockClear() }) diff --git a/src/south/south-modbus/south-modbus.js b/src/south/south-modbus/south-modbus.js index 50843ebf3c..127f61c38a 100644 --- a/src/south/south-modbus/south-modbus.js +++ b/src/south/south-modbus/south-modbus.js @@ -17,17 +17,20 @@ class SouthModbus extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: true, diff --git a/src/south/south-modbus/south-modbus.spec.js b/src/south/south-modbus/south-modbus.spec.js index a8783c4ad5..c2b6dbda95 100644 --- a/src/south/south-modbus/south-modbus.spec.js +++ b/src/south/south-modbus/south-modbus.spec.js @@ -24,10 +24,18 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Method used to flush promises called in setTimeout const flushPromises = () => new Promise(jest.requireActual('timers').setImmediate) let configuration = null @@ -108,8 +116,8 @@ describe('SouthModbus', () => { }, ], } - south = new Modbus(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new Modbus(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { diff --git a/src/south/south-mqtt/south-mqtt.js b/src/south/south-mqtt/south-mqtt.js index 6ff3da16a1..7b21aaaf0b 100644 --- a/src/south/south-mqtt/south-mqtt.js +++ b/src/south/south-mqtt/south-mqtt.js @@ -16,17 +16,20 @@ class SouthMQTT extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: true, supportLastPoint: false, diff --git a/src/south/south-mqtt/south-mqtt.spec.js b/src/south/south-mqtt/south-mqtt.spec.js index 09a643c7b6..be1f7f64dc 100644 --- a/src/south/south-mqtt/south-mqtt.spec.js +++ b/src/south/south-mqtt/south-mqtt.spec.js @@ -23,10 +23,18 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const mqttStream = new Stream() mqttStream.subscribe = jest.fn() let configuration = null @@ -80,8 +88,8 @@ describe('SouthMQTT', () => { }, ], } - south = new MQTT(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new MQTT(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) it('should be properly initialized with correct timezone', () => { @@ -106,8 +114,8 @@ describe('SouthMQTT', () => { timestampTimezone: 'invalid', }, } - const mqttInvalidSouth = new MQTT(testMqttConfig, addValues, addFiles) - await mqttInvalidSouth.start('baseFolder', 'oibusName', {}) + const mqttInvalidSouth = new MQTT(testMqttConfig, addValues, addFiles, logger) + await mqttInvalidSouth.start('baseFolder', 'oibusName') expect(mqttInvalidSouth.url).toEqual(testMqttConfig.settings.url) expect(mqttInvalidSouth.qos).toEqual(testMqttConfig.settings.qos) @@ -189,8 +197,8 @@ describe('SouthMQTT', () => { }, } - const mqttSouthWithFiles = new MQTT(testMqttConfigWithFiles, addValues, addFiles) - await mqttSouthWithFiles.start('baseFolder', 'oibusName', {}) + const mqttSouthWithFiles = new MQTT(testMqttConfigWithFiles, addValues, addFiles, logger) + await mqttSouthWithFiles.start('baseFolder', 'oibusName') mqttSouthWithFiles.certificate = CertificateService await mqttSouthWithFiles.connect() diff --git a/src/south/south-opchda/south-opchda.js b/src/south/south-opchda/south-opchda.js index a02a2a4719..7572af50b9 100644 --- a/src/south/south-opchda/south-opchda.js +++ b/src/south/south-opchda/south-opchda.js @@ -21,17 +21,20 @@ class SouthOPCHDA extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: false, diff --git a/src/south/south-opchda/south-opchda.spec.js b/src/south/south-opchda/south-opchda.spec.js index 562f1e5d6d..de2f6ae0c6 100644 --- a/src/south/south-opchda/south-opchda.spec.js +++ b/src/south/south-opchda/south-opchda.spec.js @@ -20,11 +20,19 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/deferred-promise') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let configuration = null let south = null @@ -83,8 +91,8 @@ describe('South OPCHDA', () => { scanMode: 'every10Second', }], } - south = new OPCHDA(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new OPCHDA(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) afterAll(() => { diff --git a/src/south/south-opcua-da/south-opcua-da.js b/src/south/south-opcua-da/south-opcua-da.js index e807b7f895..27204428c4 100644 --- a/src/south/south-opcua-da/south-opcua-da.js +++ b/src/south/south-opcua-da/south-opcua-da.js @@ -20,17 +20,20 @@ class SouthOPCUADA extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: true, diff --git a/src/south/south-opcua-da/south-opcua-da.spec.js b/src/south/south-opcua-da/south-opcua-da.spec.js index 08869c9549..287cfe5602 100644 --- a/src/south/south-opcua-da/south-opcua-da.spec.js +++ b/src/south/south-opcua-da/south-opcua-da.spec.js @@ -25,10 +25,18 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let south = null @@ -61,8 +69,8 @@ describe('SouthOPCUADA', () => { scanMode: 'every10Second', }], } - south = new OPCUA_DA(configuration, addValues, addFiles) - await south.start('baseFolder', 'oibusName', {}) + south = new OPCUA_DA(configuration, addValues, addFiles, logger) + await south.start('baseFolder', 'oibusName') }) it('should be properly initialized', () => { @@ -186,8 +194,8 @@ describe('SouthOPCUADA', () => { }], } - const opcuaSouthTest = new OPCUA_DA(testOpcuaConfig, addValues, addFiles) - await opcuaSouthTest.start('baseFolder', 'oibusName', {}) + const opcuaSouthTest = new OPCUA_DA(testOpcuaConfig, addValues, addFiles, logger) + await opcuaSouthTest.start('baseFolder', 'oibusName') await opcuaSouthTest.connect() opcuaSouthTest.connected = true opcuaSouthTest.session = { readVariableValue: jest.fn(), close: jest.fn() } diff --git a/src/south/south-opcua-ha/south-opcua-ha.js b/src/south/south-opcua-ha/south-opcua-ha.js index 44a2d2439a..7cac9bd443 100644 --- a/src/south/south-opcua-ha/south-opcua-ha.js +++ b/src/south/south-opcua-ha/south-opcua-ha.js @@ -27,17 +27,20 @@ class SouthOPCUAHA extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: false, diff --git a/src/south/south-opcua-ha/south-opcua-ha.spec.js b/src/south/south-opcua-ha/south-opcua-ha.spec.js index da33f0f5b1..69f434d077 100644 --- a/src/south/south-opcua-ha/south-opcua-ha.spec.js +++ b/src/south/south-opcua-ha/south-opcua-ha.spec.js @@ -34,10 +34,18 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + let configuration = null let south = null @@ -78,11 +86,11 @@ describe('SouthOPCUAHA', () => { scanMode: 'every10Second', }], } - south = new OPCUA_HA(configuration, addValues, addFiles) + south = new OPCUA_HA(configuration, addValues, addFiles, logger) }) it('should be properly initialized', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') expect(south.url).toEqual(configuration.settings.url) expect(south.retryInterval).toEqual(configuration.settings.retryInterval) expect(south.maxReadInterval).toEqual(configuration.settings.maxReadInterval) @@ -91,7 +99,7 @@ describe('SouthOPCUAHA', () => { // Test no new certificate manager creation south.clientCertificateManager.state = -1 - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') expect(south.clientCertificateManager.state).toEqual(-1) }) @@ -129,7 +137,7 @@ describe('SouthOPCUAHA', () => { } const expectedUserIdentity = { type: 0 } - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connectToOpcuaServer() expect(south.connected).toBeTruthy() @@ -156,7 +164,7 @@ describe('SouthOPCUAHA', () => { south.username = 'username' south.password = 'password' - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() delete configuration.settings.username @@ -173,7 +181,7 @@ describe('SouthOPCUAHA', () => { it('should properly connect to OPCUA server with certificate', async () => { const setTimeoutSpy = jest.spyOn(global, 'setTimeout') - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const expectedOptions = { applicationName: 'OIBus', clientName: 'southId', @@ -213,7 +221,7 @@ describe('SouthOPCUAHA', () => { throw new Error('connection error') }) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connectToOpcuaServer() expect(south.connected).toBeFalsy() @@ -222,7 +230,7 @@ describe('SouthOPCUAHA', () => { }) it('should properly format and sent values', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') south.addValues = jest.fn() const startTime = new Date('2020-01-01T00:00:00.000Z') @@ -328,7 +336,7 @@ describe('SouthOPCUAHA', () => { }) it('should log a message when zero value is received', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const nodesToRead = [ { pointId: 'point1' }, @@ -341,7 +349,7 @@ describe('SouthOPCUAHA', () => { }) it('should properly read history value with response error', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const isNot = jest.fn(() => true) south.session = { performMessageTransaction: jest.fn(() => ({ @@ -366,7 +374,7 @@ describe('SouthOPCUAHA', () => { }) it('should properly read history value with aggregateFn option', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const isNot = jest.fn(() => true) south.session = { performMessageTransaction: jest.fn(() => ({ @@ -427,7 +435,7 @@ describe('SouthOPCUAHA', () => { }) it('should properly read history value with response success', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const isNot = jest.fn(() => false) south.session = { performMessageTransaction: jest.fn(() => ({ @@ -466,7 +474,7 @@ describe('SouthOPCUAHA', () => { }) it('should properly read history value of many points with response success', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') const isNot = jest.fn(() => true) const badHistoryResult = { historyData: { dataValues: [] }, @@ -523,7 +531,7 @@ describe('SouthOPCUAHA', () => { it('should returns the requested number of node IDs after historyQuery', async () => { databaseService.getConfig.mockReturnValue('2020-02-02T02:02:02.222Z') databaseService.createConfigDatabase.mockReturnValue('configDatabase') - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.maxReadInterval = 24 * 60 * 60 * 1000 @@ -600,7 +608,7 @@ describe('SouthOPCUAHA', () => { }) it('should catch historyQuery errors', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.connected = true south.currentlyOnScan[configuration.settings.scanGroups[0].scanMode] = 0 @@ -611,7 +619,7 @@ describe('SouthOPCUAHA', () => { }) it('should call internalDisconnect on historyQuery errors', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.connected = true south.currentlyOnScan[configuration.settings.scanGroups[0].scanMode] = 0 @@ -624,7 +632,7 @@ describe('SouthOPCUAHA', () => { }) it('should nat call internalDisconnect on historyQuery errors when disconnecting', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.connected = true south.currentlyOnScan[configuration.settings.scanGroups[0].scanMode] = 0 @@ -640,7 +648,7 @@ describe('SouthOPCUAHA', () => { it('should properly disconnect when trying to connect', async () => { const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout') - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.reconnectTimeout = true south.connected = false @@ -656,7 +664,7 @@ describe('SouthOPCUAHA', () => { it('should properly disconnect when connected', async () => { const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout') - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.reconnectTimeout = false south.connected = true diff --git a/src/south/south-rest/south-rest.js b/src/south/south-rest/south-rest.js index c54703a489..9bee0a5a7f 100644 --- a/src/south/south-rest/south-rest.js +++ b/src/south/south-rest/south-rest.js @@ -22,17 +22,20 @@ class SouthRest extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: false, diff --git a/src/south/south-rest/south-rest.spec.js b/src/south/south-rest/south-rest.spec.js index 3d62cd2b48..5150cc719b 100644 --- a/src/south/south-rest/south-rest.spec.js +++ b/src/south/south-rest/south-rest.spec.js @@ -37,10 +37,18 @@ const addFiles = jest.fn() // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + const nowDateString = '2020-02-02T02:02:02.222Z' let configuration = null let south = null @@ -103,12 +111,12 @@ describe('SouthRest', () => { }, scanMode: 'every10Seconds', } - south = new RestApi(configuration, addValues, addFiles) + south = new RestApi(configuration, addValues, addFiles, logger) }) it('should create RestApi connector and connect', async () => { databaseService.getConfig.mockReturnValue(null) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') expect(south.requestMethod).toEqual(configuration.settings.requestMethod) expect(south.host).toEqual(configuration.settings.host) @@ -138,14 +146,14 @@ describe('SouthRest', () => { throw new Error('mkdir error test') }) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') expect(south.logger.error).toHaveBeenCalledWith(new Error('mkdir error test')) }) it('should fail to scan', async () => { fetch.mockReturnValue(Promise.resolve({ ok: false, status: 400, statusText: 'statusText' })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() await expect(south.historyQuery( @@ -242,7 +250,7 @@ describe('SouthRest', () => { resolve(endpointResult) }), })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() await south.historyQuery(configuration.scanMode, new Date('2020-01-01T00:00:00.000Z'), new Date('2021-01-01T00:00:00.000Z')) @@ -276,7 +284,7 @@ describe('SouthRest', () => { resolve(endpointResult) }), })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() await south.historyQuery(configuration.scanMode, new Date('2019-10-03T13:36:38.590Z'), new Date('2019-10-03T15:36:38.590Z')) @@ -285,7 +293,7 @@ describe('SouthRest', () => { }) it('should use http get with body function with self signed certificates accepted and without authentication', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.body = '{ startTime: @StartTime, endTime: @EndTime }' @@ -314,7 +322,7 @@ describe('SouthRest', () => { }) it('should use http get with body function with ISO dates', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.body = '{ startTime: @StartTime, endTime: @EndTime }' @@ -342,7 +350,7 @@ describe('SouthRest', () => { }) it('should use http get with body function with numerical dates', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.body = '{ startTime: @StartTime, endTime: @EndTime }' @@ -382,7 +390,7 @@ describe('SouthRest', () => { }), })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.requestMethod = 'PUT' @@ -420,7 +428,7 @@ describe('SouthRest', () => { }), })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.variableDateFormat = 'number' @@ -459,7 +467,7 @@ describe('SouthRest', () => { resolve([]) }), })) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() await expect(south.historyQuery( diff --git a/src/south/south-sql/south-sql.js b/src/south/south-sql/south-sql.js index 4371deb783..451944045d 100644 --- a/src/south/south-sql/south-sql.js +++ b/src/south/south-sql/south-sql.js @@ -31,17 +31,20 @@ class SouthSQL extends SouthConnector { * @param {Object} configuration - The South connector configuration * @param {Function} engineAddValuesCallback - The Engine add values callback * @param {Function} engineAddFilesCallback - The Engine add file callback + * @param {Object} logger - The Pino child logger to use * @return {void} */ constructor( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, ) { super( configuration, engineAddValuesCallback, engineAddFilesCallback, + logger, { supportListen: false, supportLastPoint: false, diff --git a/src/south/south-sql/south-sql.spec.js b/src/south/south-sql/south-sql.spec.js index 0199c46656..259ef53251 100644 --- a/src/south/south-sql/south-sql.spec.js +++ b/src/south/south-sql/south-sql.spec.js @@ -43,9 +43,17 @@ jest.mock('node:fs/promises') const addValues = jest.fn() const addFiles = jest.fn() +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} + // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -92,7 +100,7 @@ describe('SouthSQL', () => { scanMode: 'every10Second', points: [], } - south = new SQL(settings, addValues, addFiles) + south = new SQL(settings, addValues, addFiles, logger) }) it('should properly connect and set lastCompletedAt from database', async () => { @@ -121,8 +129,8 @@ describe('SouthSQL', () => { const tempConfig = { ...settings } tempConfig.startTime = '2020-02-02 02:02:02' - const tempSqlSouth = new SQL(tempConfig, addValues, addFiles) - await tempSqlSouth.start('baseFolder', 'oibusName', {}) + const tempSqlSouth = new SQL(tempConfig, addValues, addFiles, logger) + await tempSqlSouth.start('baseFolder', 'oibusName') await tempSqlSouth.connect() expect(tempSqlSouth.lastCompletedAt[settings.scanMode]).toEqual(new Date('2020-02-02 02:02:02')) @@ -137,8 +145,8 @@ describe('SouthSQL', () => { databasePath: undefined, }, } - const badSqlSouth = new SQL(badConfig, addValues, addFiles) - await badSqlSouth.start('baseFolder', 'oibusName', {}) + const badSqlSouth = new SQL(badConfig, addValues, addFiles, logger) + await badSqlSouth.start('baseFolder', 'oibusName') expect(badSqlSouth.logger.error).toHaveBeenCalledWith('Invalid timezone supplied: "undefined".') @@ -147,7 +155,7 @@ describe('SouthSQL', () => { it('should properly connect and set lastCompletedAt to now', async () => { databaseService.getConfig.mockReturnValue(null) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() expect(databaseService.createConfigDatabase).toBeCalledWith(path.resolve(`baseFolder/south-${south.id}/cache.db`)) @@ -156,7 +164,7 @@ describe('SouthSQL', () => { }) it('should quit historyQuery if timezone is invalid', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.timezone = undefined @@ -168,7 +176,7 @@ describe('SouthSQL', () => { }) it('should interact with MSSQL server if driver is mssql', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mssql' @@ -201,7 +209,7 @@ describe('SouthSQL', () => { }) it('should interact with MSSQL server and catch request error', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mssql' @@ -228,7 +236,7 @@ describe('SouthSQL', () => { const endTime = new Date('2019-10-03T13:40:40.400Z') utils.generateReplacementParameters.mockReturnValue([startTime, endTime]) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mysql' @@ -284,7 +292,7 @@ describe('SouthSQL', () => { }) it('should interact with MySQL server and catch request error', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mysql' @@ -316,7 +324,7 @@ describe('SouthSQL', () => { const endTime = new Date('2019-10-03T13:40:40.400Z') utils.generateReplacementParameters.mockReturnValue([startTime, endTime]) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'postgresql' @@ -359,7 +367,7 @@ describe('SouthSQL', () => { }) it('should interact with PostgreSQL server and catch request error', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'postgresql' @@ -382,7 +390,7 @@ describe('SouthSQL', () => { }) it('should interact with Oracle server if driver is oracle', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'oracle' @@ -425,7 +433,7 @@ describe('SouthSQL', () => { }) it('should interact with Oracle server and catch request error', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'oracle' @@ -449,7 +457,7 @@ describe('SouthSQL', () => { it('should interact with SQLite database server if driver is sqlite', async () => { const all = jest.fn(() => ([])) mockDatabase.prepare.mockReturnValue({ all }) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'sqlite' @@ -466,7 +474,7 @@ describe('SouthSQL', () => { mockDatabase.prepare = () => { throw new Error('test') } - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'sqlite' @@ -479,7 +487,7 @@ describe('SouthSQL', () => { }) it('should log an error if an invalid driver is specified', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'invalid' @@ -492,7 +500,7 @@ describe('SouthSQL', () => { }) it('should not send file on emtpy result', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mysql' @@ -505,7 +513,7 @@ describe('SouthSQL', () => { }) it('should send an uncompressed file when the result is not empty and compression is false', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mysql' @@ -539,7 +547,7 @@ describe('SouthSQL', () => { }) it('should send a compressed file when the result is not empty and compression is true', async () => { - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() south.driver = 'mysql' diff --git a/src/south/south-sql/south-sql.test.js b/src/south/south-sql/south-sql.test.js index a857d08271..296f4c1022 100644 --- a/src/south/south-sql/south-sql.test.js +++ b/src/south/south-sql/south-sql.test.js @@ -10,7 +10,6 @@ jest.mock('node:fs/promises') // Mock services jest.mock('../../service/database.service') -jest.mock('../../service/logger/logger.service') jest.mock('../../service/status.service') jest.mock('../../service/encryption.service', () => ({ getInstance: () => ({ decryptText: (password) => password }) })) @@ -21,6 +20,14 @@ const engine = { addValues: jest.fn(), addFile: jest.fn(), } +// Mock logger +const logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), +} describe('MySQL Integration test', () => { const configuration = testConfig.south[0] @@ -52,9 +59,9 @@ describe('MySQL Integration test', () => { }) it('should retrieve some values in the MySQL database', async () => { - const south = new SQL(configuration, engine) + const south = new SQL(configuration, engine, logger) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() expect(south.connected).toEqual(true) @@ -102,9 +109,9 @@ describe('PostgreSQL Integration test', () => { }) it('should retrieve some values in the PostgreSQL database', async () => { - const south = new SQL(configuration, engine) + const south = new SQL(configuration, engine, logger) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() expect(south.connected).toEqual(true) @@ -158,9 +165,9 @@ describe('MSSQL Integration test', () => { }) it('should retrieve some values in the MSSQL database', async () => { - const south = new SQL(configuration, engine) + const south = new SQL(configuration, engine, logger) - await south.start('baseFolder', 'oibusName', {}) + await south.start('baseFolder', 'oibusName') await south.connect() expect(south.connected).toEqual(true) diff --git a/src/web-server/web-server.js b/src/web-server/web-server.js index cfcb2780d2..77d15b1c6e 100644 --- a/src/web-server/web-server.js +++ b/src/web-server/web-server.js @@ -7,7 +7,6 @@ const respond = require('koa-respond') const authCrypto = require('./middlewares/auth') const ipFilter = require('./middlewares/ip-filter') const clientController = require('./controllers/web-client.controller') -const LoggerService = require('../service/logger/logger.service') const router = require('./routes') /** @@ -37,16 +36,16 @@ class Server { * @param {EncryptionService} encryptionService - The encryption service * @param {OIBusEngine} oibusEngine - The OIBus engine * @param {HistoryQueryEngine} historyQueryEngine - The HistoryQuery engine + * @param {Object} logger - Logger to use * @return {void} */ - constructor(encryptionService, oibusEngine, historyQueryEngine) { + constructor(encryptionService, oibusEngine, historyQueryEngine, logger) { this.encryptionService = encryptionService // capture the engine and logger under app for reuse in routes. this.engine = oibusEngine this.historyQueryEngine = historyQueryEngine - this.logger = new LoggerService('web-server') + this.logger = logger this.webServer = null // store the listening web server - this.logger.setEncryptionService(this.encryptionService) } /** @@ -169,11 +168,8 @@ class Server { this.app.use(router.allowedMethods()) this.app.use(clientController.serveClient) - // Set the logger with the same settings as the engine - this.logger.changeParameters(engineConfig.name, engineConfig.logParameters).then(() => { - this.webServer = this.app.listen(this.port, () => { - this.logger.info(`Web server started on ${this.port}`) - }) + this.webServer = this.app.listen(this.port, () => { + this.logger.info(`Web server started on ${this.port}`) }) }