feat: Unified logging across z2m/zh/zhc (#21984)

* Unified logging across z2m/zh/zhc

* Updates

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
Nerivec
2024-04-02 22:33:51 +02:00
committed by GitHub
co-authored by Koen Kanters
parent bd0a41107a
commit 73566dff98
25 changed files with 182 additions and 183 deletions
+7 -6
View File
@@ -8,7 +8,8 @@ import utils from './util/utils';
import stringify from 'json-stable-stringify-without-jsonify'; import stringify from 'json-stable-stringify-without-jsonify';
import assert from 'assert'; import assert from 'assert';
import bind from 'bind-decorator'; import bind from 'bind-decorator';
import * as zhc from 'zigbee-herdsman-converters'; import {setLogger as zhcSetLogger} from 'zigbee-herdsman-converters';
import {setLogger as zhSetLogger} from 'zigbee-herdsman';
// Extensions // Extensions
import ExtensionFrontend from './extension/frontend'; import ExtensionFrontend from './extension/frontend';
@@ -60,6 +61,8 @@ export class Controller {
constructor(restartCallback: () => void, exitCallback: (code: number, restart: boolean) => void) { constructor(restartCallback: () => void, exitCallback: (code: number, restart: boolean) => void) {
logger.init(); logger.init();
zhSetLogger(logger);
zhcSetLogger(logger);
this.eventBus = new EventBus(); this.eventBus = new EventBus();
this.zigbee = new Zigbee(this.eventBus); this.zigbee = new Zigbee(this.eventBus);
this.mqtt = new MQTT(this.eventBus); this.mqtt = new MQTT(this.eventBus);
@@ -92,8 +95,6 @@ export class Controller {
/* istanbul ignore next */ /* istanbul ignore next */
settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs), settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs),
].filter((n) => n); ].filter((n) => n);
zhc.setLogger(logger);
} }
async start(): Promise<void> { async start(): Promise<void> {
@@ -138,9 +139,9 @@ export class Controller {
// Enable zigbee join // Enable zigbee join
try { try {
if (settings.get().permit_join) { if (settings.get().permit_join) {
logger.warn('`permit_join` set to `true` in configuration.yaml.'); logger.warning('`permit_join` set to `true` in configuration.yaml.');
logger.warn('Allowing new devices to join.'); logger.warning('Allowing new devices to join.');
logger.warn('Set `permit_join` to `false` once you joined all devices.'); logger.warning('Set `permit_join` to `false` once you joined all devices.');
} }
await this.zigbee.permitJoin(settings.get().permit_join); await this.zigbee.permitJoin(settings.get().permit_join);
+1 -1
View File
@@ -87,7 +87,7 @@ export default class Availability extends Extension {
logger.debug(`Successfully pinged '${device.name}' (attempt ${i + 1}/${attempts})`); logger.debug(`Successfully pinged '${device.name}' (attempt ${i + 1}/${attempts})`);
break; break;
} catch (error) { } catch (error) {
logger.warn(`Failed to ping '${device.name}' (attempt ${i + 1}/${attempts}, ${error.message})`); logger.warning(`Failed to ping '${device.name}' (attempt ${i + 1}/${attempts}, ${error.message})`);
// Try again in 3 seconds. // Try again in 3 seconds.
const lastAttempt = i - 1 === attempts; const lastAttempt = i - 1 === attempts;
!lastAttempt && await utils.sleep(3); !lastAttempt && await utils.sleep(3);
+2 -2
View File
@@ -394,7 +394,7 @@ export default class Bind extends Extension {
await endpoint.configureReporting(bind.cluster.name, items); await endpoint.configureReporting(bind.cluster.name, items);
logger.info(`Successfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`); logger.info(`Successfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
} catch (error) { } catch (error) {
logger.warn(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}'`); logger.warning(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
} }
} }
} }
@@ -436,7 +436,7 @@ export default class Bind extends Extension {
await endpoint.configureReporting(cluster, items); await endpoint.configureReporting(cluster, items);
logger.info(`Successfully disabled reporting for '${entity}' cluster '${cluster}'`); logger.info(`Successfully disabled reporting for '${entity}' cluster '${cluster}'`);
} catch (error) { } catch (error) {
logger.warn(`Failed to disable reporting for '${entity}' cluster '${cluster}'`); logger.warning(`Failed to disable reporting for '${entity}' cluster '${cluster}'`);
} }
} }
+1 -1
View File
@@ -35,7 +35,7 @@ export default class Configure extends Extension {
} }
if (!device.definition || !device.definition.configure) { if (!device.definition || !device.definition.configure) {
logger.warn(`Skipping configure of '${device.name}', device does not require this.`); logger.warning(`Skipping configure of '${device.name}', device does not require this.`);
return; return;
} }
+1 -1
View File
@@ -128,7 +128,7 @@ export default class HomeAssistant extends Extension {
override async start(): Promise<void> { override async start(): Promise<void> {
if (!settings.get().advanced.cache_state) { if (!settings.get().advanced.cache_state) {
logger.warn('In order for Home Assistant integration to work properly set `cache_state: true'); logger.warning('In order for Home Assistant integration to work properly set `cache_state: true');
} }
this.zigbee2MQTTVersion = (await utils.getZigbee2MQTTVersion(false)).version; this.zigbee2MQTTVersion = (await utils.getZigbee2MQTTVersion(false)).version;
+1 -1
View File
@@ -445,7 +445,7 @@ export default class BridgeLegacy extends Extension {
stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}), stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}),
); );
} else { } else {
logger.warn('Failed to factory reset device through Touchlink'); logger.warning('Failed to factory reset device through Touchlink');
this.mqtt.publish( this.mqtt.publish(
'bridge/log', 'bridge/log',
stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}), stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}),
@@ -36,7 +36,7 @@ export default class DeviceGroupMembership extends Extension {
); );
if (!response) { if (!response) {
logger.warn(`Couldn't get group membership of ${device.ieeeAddr}`); logger.warning(`Couldn't get group membership of ${device.ieeeAddr}`);
return; return;
} }
+4 -4
View File
@@ -39,16 +39,16 @@ export default class SoftReset extends Extension {
} }
private async handleTimeout(): Promise<void> { private async handleTimeout(): Promise<void> {
logger.warn('Soft reset timeout triggered'); logger.warning('Soft reset timeout triggered');
try { try {
await this.zigbee.reset('soft'); await this.zigbee.reset('soft');
logger.warn('Soft reset ZNP due to timeout'); logger.warning('Soft reset ZNP due to timeout');
} catch (error) { } catch (error) {
logger.warn('Soft reset failed, trying stop/start'); logger.warning('Soft reset failed, trying stop/start');
await this.zigbee.stop(); await this.zigbee.stop();
logger.warn('Zigbee stopped'); logger.warning('Zigbee stopped');
try { try {
await this.zigbee.start(); await this.zigbee.start();
+104 -122
View File
@@ -7,20 +7,8 @@ import fx from 'mkdir-recursive';
import {rimrafSync} from 'rimraf'; import {rimrafSync} from 'rimraf';
import assert from 'assert'; import assert from 'assert';
const colorizer = winston.format.colorize(); const LOG_LEVELS = ['error', 'warning', 'info', 'debug'] as const;
type LogLevel = typeof LOG_LEVELS[number];
type Z2MLogLevel = 'warn' | 'debug' | 'info' | 'error';
type WinstonLogLevel = 'warning' | 'debug' | 'info' | 'error';
const z2mToWinstonLevel = (level: Z2MLogLevel): WinstonLogLevel => level === 'warn' ? 'warning' : level;
const winstonToZ2mLevel = (level: WinstonLogLevel): Z2MLogLevel => level === 'warning' ? 'warn' : level;
const levelWithCompensatedLength: {[s: string]: string} = {
'info': 'info ',
'error': 'error',
'warn': 'warn ',
'debug': 'debug',
};
let logger: winston.Logger; let logger: winston.Logger;
let fileTransport : winston.transport; let fileTransport : winston.transport;
@@ -32,14 +20,36 @@ let transportsToUse: winston.transport[];
function init(): void { function init(): void {
// What transports to enable // What transports to enable
output = settings.get().advanced.log_output; output = settings.get().advanced.log_output;
// Directory to log to // Directory to log to
const timestamp = moment(Date.now()).format('YYYY-MM-DD.HH-mm-ss'); const timestamp = moment(Date.now()).format('YYYY-MM-DD.HH-mm-ss');
directory = settings.get().advanced.log_directory.replace('%TIMESTAMP%', timestamp); directory = settings.get().advanced.log_directory.replace('%TIMESTAMP%', timestamp);
logFilename = settings.get().advanced.log_file.replace('%TIMESTAMP%', timestamp); logFilename = settings.get().advanced.log_file.replace('%TIMESTAMP%', timestamp);
// Determine the log level.
const settingLevel = settings.get().advanced.log_level;
// workaround for syslog<>npm level conflict
const level = settingLevel === 'warn' ? 'warning' : settingLevel;
assert(LOG_LEVELS.includes(level), `'${level}' is not valid log_level, use one of '${LOG_LEVELS.join(', ')}'`);
const timestampFormat = (): string => moment().format(settings.get().advanced.timestamp_format);
// Setup default console logger
transportsToUse = [
new winston.transports.Console({
level,
silent: !output.includes('console'),
// winston.config.syslog.levels sets 'warning' as 'red'
format: winston.format.combine(
winston.format.colorize({colors: {debug: 'blue', info: 'green', warning: 'yellow', error: 'red'}}),
winston.format.printf(/* istanbul ignore next */(info) => {
return `[${info.timestamp}] ${info.level}: \t${info.namespace}: ${info.message}`;
}),
),
}),
];
// Make sure that log directory exists when not logging to stdout only
if (output.includes('file')) { if (output.includes('file')) {
// Make sure that log directory exists when not logging to stdout only
fx.mkdirSync(directory); fx.mkdirSync(directory);
if (settings.get().advanced.log_symlink_current) { if (settings.get().advanced.log_symlink_current) {
@@ -51,62 +61,24 @@ function init(): void {
} }
fs.symlinkSync(actual, current); fs.symlinkSync(actual, current);
} }
}
// Determine the log level. // Add file logger when enabled
const z2mLevel = settings.get().advanced.log_level; // NOTE: the initiation of the logger, even when not added as transport tries to create the logging directory
const validLevels = ['info', 'error', 'warn', 'debug']; const transportFileOptions: KeyValue = {
assert(validLevels.includes(z2mLevel), filename: path.join(directory, logFilename),
`'${z2mLevel}' is not valid log_level, use one of '${validLevels.join(', ')}'`); json: false,
const level = z2mToWinstonLevel(z2mLevel);
const timestampFormat = (): string => moment().format(settings.get().advanced.timestamp_format);
// Setup default console logger
transportsToUse = [
new winston.transports.Console({
level, level,
silent: !output.includes('console'), format: winston.format.printf(/* istanbul ignore next */(info) => {
format: winston.format.combine( return `[${info.timestamp}] ${info.level}: \t${info.namespace}: ${info.message}`;
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
const plainPrefix = `Zigbee2MQTT:${levelWithCompensatedLength[l]}`;
let prefix = plainPrefix;
if (process.stdout.isTTY) {
prefix = colorizer.colorize(l, plainPrefix);
}
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
}),
),
}),
];
// Add file logger when enabled
// NOTE: the initiation of the logger, even when not added as transport tries to create the logging directory
const transportFileOptions: KeyValue = {
filename: path.join(directory, logFilename),
json: false,
level,
format: winston.format.combine(
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
return `${levelWithCompensatedLength[l]} ${timestamp.split('.')[0]}: ${message}`;
}), }),
), };
};
if (settings.get().advanced.log_rotation) { if (settings.get().advanced.log_rotation) {
transportFileOptions.tailable = true; transportFileOptions.tailable = true;
transportFileOptions.maxFiles = 3; // Keep last 3 files transportFileOptions.maxFiles = 3; // Keep last 3 files
transportFileOptions.maxsize = 10000000; // 10MB transportFileOptions.maxsize = 10000000; // 10MB
} }
if (output.includes('file')) {
fileTransport = new winston.transports.File(transportFileOptions); fileTransport = new winston.transports.File(transportFileOptions);
transportsToUse.push(fileTransport); transportsToUse.push(fileTransport);
} }
@@ -115,19 +87,78 @@ function init(): void {
if (output.includes('syslog')) { if (output.includes('syslog')) {
// eslint-disable-next-line // eslint-disable-next-line
require('winston-syslog').Syslog; require('winston-syslog').Syslog;
const options: KeyValue = { const options: KeyValue = {
app_name: 'Zigbee2MQTT', app_name: 'Zigbee2MQTT',
format: winston.format.printf(/* istanbul ignore next */(info) => { format: winston.format.printf(/* istanbul ignore next */(info) => {
return `${info.message}`; return `${info.namespace}: ${info.message}`;
}), }),
...settings.get().advanced.log_syslog, ...settings.get().advanced.log_syslog,
}; };
if (options.hasOwnProperty('type')) options.type = options.type.toString();
// @ts-ignore if (options.hasOwnProperty('type')) {
options.type = options.type.toString();
}
// @ts-expect-error custom transport
transportsToUse.push(new winston.transports.Syslog(options)); transportsToUse.push(new winston.transports.Syslog(options));
} }
logger = winston.createLogger({transports: transportsToUse, levels: winston.config.syslog.levels}); logger = winston.createLogger({
format: winston.format.combine(
winston.format.errors({stack: true}),
winston.format.timestamp({format: timestampFormat}),
),
transports: transportsToUse,
levels: winston.config.syslog.levels,
});
}
function addTransport(transport: winston.transport): void {
transport.level = transportsToUse[0].level;
logger.add(transport);
}
// TODO refactor Z2M level to 'warning' to simplify logic
function getLevel(): LogLevel | 'warn' {
let level = transportsToUse[0].level;
if (level === 'warning') {
level = 'warn';
}
return transportsToUse[0].level as LogLevel | 'warn';
}
function setLevel(level: LogLevel | 'warn'): void {
if (level === 'warn') {
level = 'warning';
}
logger.transports.forEach((transport) => transport.level = level);
}
function warning(message: string, namespace: string = 'z2m'): void {
// @ts-expect-error syslog level
logger.warning(message, {namespace});
}
function info(message: string, namespace: string = 'z2m'): void {
logger.info(message, {namespace});
}
function debug(message: string, namespace: string = 'z2m'): void {
logger.debug(message, {namespace});
}
function error(message: string, namespace: string = 'z2m'): void {
logger.error(message, {namespace});
}
// Print to user what logging is enabled
function logOutput(): void {
const filename = output.includes('file') ? ` (filename: ${logFilename})` : ``;
info(`Logging to ${output.join(', ')}${filename}.`);
} }
// Cleanup any old log directory. // Cleanup any old log directory.
@@ -143,61 +174,12 @@ function cleanup(): void {
directories.sort((a: KeyValue, b: KeyValue) => b.birth - a.birth); directories.sort((a: KeyValue, b: KeyValue) => b.birth - a.birth);
directories = directories.slice(10, directories.length); directories = directories.slice(10, directories.length);
directories.forEach((dir) => { directories.forEach((dir) => {
logger.debug(`Removing old log directory '${dir.path}'`); debug(`Removing old log directory '${dir.path}'`);
rimrafSync(dir.path); rimrafSync(dir.path);
}); });
} }
} }
// Print to user what logging is enabled
function logOutput(): void {
if (output.includes('file')) {
if (output.includes('console')) {
logger.info(`Logging to console and directory: '${directory}' filename: ${logFilename}`);
} else {
logger.info(`Logging to directory: '${directory}' filename: ${logFilename}`);
}
cleanup();
} else if (output.includes('console')) {
logger.info(`Logging to console only'`);
}
}
function addTransport(transport: winston.transport): void {
transport.level = transportsToUse[0].level;
logger.add(transport);
}
function getLevel(): Z2MLogLevel {
return winstonToZ2mLevel(transportsToUse[0].level as WinstonLogLevel);
}
function setLevel(level: Z2MLogLevel): void {
logger.transports.forEach((transport) => transport.level = z2mToWinstonLevel(level as Z2MLogLevel));
}
function warn(message: string): void {
// @ts-expect-error
logger.warning(message);
}
function warning(message: string): void {
// @ts-expect-error
logger.warning(message);
}
function info(message: string): void {
logger.info(message);
}
function debug(message: string): void {
logger.debug(message);
}
function error(message: string): void {
logger.error(message);
}
// Workaround for https://github.com/winstonjs/winston/issues/1629. // Workaround for https://github.com/winstonjs/winston/issues/1629.
// https://github.com/Koenkk/zigbee2mqtt/pull/10905 // https://github.com/Koenkk/zigbee2mqtt/pull/10905
/* istanbul ignore next */ /* istanbul ignore next */
@@ -221,6 +203,6 @@ async function end(): Promise<void> {
} }
export default { export default {
init, logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, end, init, logOutput, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, end,
winston: (): winston.Logger => logger, winston: (): winston.Logger => logger,
}; };
+1 -1
View File
@@ -80,7 +80,7 @@ const defaults: RecursivePartial<Settings> = {
log_symlink_current: false, log_symlink_current: false,
log_output: ['console', 'file'], log_output: ['console', 'file'],
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'), log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
log_file: 'log.txt', log_file: 'log.log',
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info', log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
log_syslog: {}, log_syslog: {},
pan_id: 0x1a62, pan_id: 0x1a62,
+5 -5
View File
@@ -58,7 +58,7 @@ export default class Zigbee {
let startResult; let startResult;
try { try {
this.herdsman = new Controller(herdsmanSettings, logger); this.herdsman = new Controller(herdsmanSettings);
startResult = await this.herdsman.start(); startResult = await this.herdsman.start();
} catch (error) { } catch (error) {
logger.error(`Error while starting zigbee-herdsman`); logger.error(`Error while starting zigbee-herdsman`);
@@ -103,7 +103,7 @@ export default class Zigbee {
}); });
this.herdsman.on('deviceLeave', (data: ZHEvents.DeviceLeavePayload) => { this.herdsman.on('deviceLeave', (data: ZHEvents.DeviceLeavePayload) => {
const name = settings.getDevice(data.ieeeAddr)?.friendly_name || data.ieeeAddr; const name = settings.getDevice(data.ieeeAddr)?.friendly_name || data.ieeeAddr;
logger.warn(`Device '${name}' left the network`); logger.warning(`Device '${name}' left the network`);
this.eventBus.emitDeviceLeave({ieeeAddr: data.ieeeAddr, name}); this.eventBus.emitDeviceLeave({ieeeAddr: data.ieeeAddr, name});
}); });
this.herdsman.on('message', async (data: ZHEvents.MessagePayload) => { this.herdsman.on('message', async (data: ZHEvents.MessagePayload) => {
@@ -134,11 +134,11 @@ export default class Zigbee {
}; };
if (passlist.length > 0) { if (passlist.length > 0) {
if (!passlist.includes(device.ieeeAddr)) { if (!passlist.includes(device.ieeeAddr)) {
logger.warn(`Device which is not on passlist connected (${device.ieeeAddr}), removing...`); logger.warning(`Device which is not on passlist connected (${device.ieeeAddr}), removing...`);
await remove(device); await remove(device);
} }
} else if (blocklist.includes(device.ieeeAddr)) { } else if (blocklist.includes(device.ieeeAddr)) {
logger.warn(`Device on blocklist is connected (${device.ieeeAddr}), removing...`); logger.warning(`Device on blocklist is connected (${device.ieeeAddr}), removing...`);
await remove(device); await remove(device);
} }
} }
@@ -162,7 +162,7 @@ export default class Zigbee {
const {vendor, description, model} = data.device.definition; const {vendor, description, model} = data.device.definition;
logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`); logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`);
} else { } else {
logger.warn(`Device '${name}' with Zigbee model '${data.device.zh.modelID}' and manufacturer name ` + logger.warning(`Device '${name}' with Zigbee model '${data.device.zh.modelID}' and manufacturer name ` +
`'${data.device.zh.manufacturerName}' is NOT supported, ` + `'${data.device.zh.manufacturerName}' is NOT supported, ` +
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`please follow https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`); `please follow https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`);
+1 -1
View File
@@ -10,7 +10,7 @@ import Availability from '../lib/extension/availability';
import flushPromises from './lib/flushPromises'; import flushPromises from './lib/flushPromises';
import stringify from 'json-stable-stringify-without-jsonify'; import stringify from 'json-stable-stringify-without-jsonify';
const mocks = [MQTT.publish, logger.warn, logger.debug]; const mocks = [MQTT.publish, logger.warning, logger.debug];
const devices = zigbeeHerdsman.devices; const devices = zigbeeHerdsman.devices;
zigbeeHerdsman.returnDevices.push( zigbeeHerdsman.returnDevices.push(
...[devices.bulb_color.ieeeAddr, devices.bulb_color_2.ieeeAddr, devices.coordinator.ieeeAddr, devices.remote.ieeeAddr]) ...[devices.bulb_color.ieeeAddr, devices.bulb_color_2.ieeeAddr, devices.coordinator.ieeeAddr, devices.remote.ieeeAddr])
+3 -3
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -8,7 +8,7 @@ const flushPromises = require('./lib/flushPromises');
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const stringify = require('json-stable-stringify-without-jsonify'); const stringify = require('json-stable-stringify-without-jsonify');
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
describe('Configure', () => { describe('Configure', () => {
let controller; let controller;
@@ -202,7 +202,7 @@ describe('Configure', () => {
it('Legacy api: Should skip reconfigure when device does not require this', async () => { it('Legacy api: Should skip reconfigure when device does not require this', async () => {
await MQTT.events.message('zigbee2mqtt/bridge/configure', '0x0017882104a44559'); await MQTT.events.message('zigbee2mqtt/bridge/configure', '0x0017882104a44559');
await flushPromises(); await flushPromises();
expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'TS0601_thermostat', device does not require this.`) expect(logger.warning).toHaveBeenCalledWith(`Skipping configure of 'TS0601_thermostat', device does not require this.`)
}); });
it('Should not configure when interview not completed', async () => { it('Should not configure when interview not completed', async () => {
+1 -1
View File
@@ -53,7 +53,7 @@ describe('Controller', () => {
it('Start controller', async () => { it('Start controller', async () => {
await controller.start(); await controller.start();
expect(zigbeeHerdsman.constructor).toHaveBeenCalledWith({"network":{"panID":6754,"extendedPanID":[221,221,221,221,221,221,221,221],"channelList":[11],"networkKey":[1,3,5,7,9,11,13,15,0,2,4,6,8,10,12,13]},"databasePath":path.join(data.mockDir, "database.db"), "databaseBackupPath":path.join(data.mockDir, "database.db.backup"),"backupPath":path.join(data.mockDir, "coordinator_backup.json"),"acceptJoiningDeviceHandler": expect.any(Function),adapter: {concurrent: null, delay: null, disableLED: false}, "serialPort":{"baudRate":undefined,"rtscts":undefined,"path":"/dev/dummy"}}, logger); expect(zigbeeHerdsman.constructor).toHaveBeenCalledWith({"network":{"panID":6754,"extendedPanID":[221,221,221,221,221,221,221,221],"channelList":[11],"networkKey":[1,3,5,7,9,11,13,15,0,2,4,6,8,10,12,13]},"databasePath":path.join(data.mockDir, "database.db"), "databaseBackupPath":path.join(data.mockDir, "database.db.backup"),"backupPath":path.join(data.mockDir, "coordinator_backup.json"),"acceptJoiningDeviceHandler": expect.any(Function),adapter: {concurrent: null, delay: null, disableLED: false}, "serialPort":{"baudRate":undefined,"rtscts":undefined,"path":"/dev/dummy"}});
expect(zigbeeHerdsman.start).toHaveBeenCalledTimes(1); expect(zigbeeHerdsman.start).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0); expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1);
+2 -2
View File
@@ -993,9 +993,9 @@ describe('HomeAssistant extension', () => {
it('Should warn when starting with cache_state false', async () => { it('Should warn when starting with cache_state false', async () => {
settings.set(['advanced', 'cache_state'], false); settings.set(['advanced', 'cache_state'], false);
logger.warn.mockClear(); logger.warning.mockClear();
await resetExtension(); await resetExtension();
expect(logger.warn).toHaveBeenCalledWith("In order for Home Assistant integration to work properly set `cache_state: true"); expect(logger.warning).toHaveBeenCalledWith("In order for Home Assistant integration to work properly set `cache_state: true");
}); });
it('Should set missing values to null', async () => { it('Should set missing values to null', async () => {
+2 -2
View File
@@ -25,7 +25,7 @@ describe('Bridge legacy', () => {
data.writeDefaultConfiguration(); data.writeDefaultConfiguration();
settings.reRead(); settings.reRead();
logger.info.mockClear(); logger.info.mockClear();
logger.warn.mockClear(); logger.warning.mockClear();
}); });
afterAll(async () => { afterAll(async () => {
@@ -478,6 +478,6 @@ describe('Bridge legacy', () => {
MQTT.events.message('zigbee2mqtt/bridge/config/touchlink/factory_reset', ''); MQTT.events.message('zigbee2mqtt/bridge/config/touchlink/factory_reset', '');
await flushPromises(); await flushPromises();
expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1); expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith('Failed to factory reset device through Touchlink'); expect(logger.warning).toHaveBeenCalledWith('Failed to factory reset device through Touchlink');
}); });
}); });
+1 -1
View File
@@ -16,7 +16,7 @@ const Controller = require('../../lib/controller');
const flushPromises = require('../lib/flushPromises'); const flushPromises = require('../lib/flushPromises');
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
describe('Report', () => { describe('Report', () => {
let controller; let controller;
+34 -19
View File
@@ -69,6 +69,25 @@ describe('Logger', () => {
logger.logOutput(); logger.logOutput();
logger.setLevel('debug'); logger.setLevel('debug');
expect(logger.getLevel()).toBe('debug'); expect(logger.getLevel()).toBe('debug');
logger.setLevel('info');
expect(logger.getLevel()).toBe('info');
logger.setLevel('warning');
expect(logger.getLevel()).toBe('warning');
logger.setLevel('warn');
expect(logger.getLevel()).toBe('warning');
logger.setLevel('error');
expect(logger.getLevel()).toBe('error');
});
it('Set warning when log level is warn', () => {
settings.set(['advanced', 'log_level'], 'warn');
settings.reRead();
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
expect(logger.getLevel()).toBe('warning');
settings.set(['advanced', 'log_level'], 'info');
settings.reRead();
}); });
it('Add transport', () => { it('Add transport', () => {
@@ -84,15 +103,6 @@ describe('Logger', () => {
expect(logger.winston().transports.length).toBe(3); expect(logger.winston().transports.length).toBe(3);
}); });
it('Set and get log level warn <-> warning', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
logger.setLevel('warn');
expect(logger.winston().transports[0].level).toBe('warning');
expect(logger.getLevel()).toBe('warn');
});
it('Logger should be console and file by default', () => { it('Logger should be console and file by default', () => {
const logger = require('../lib/util/logger').default; const logger = require('../lib/util/logger').default;
logger.init(); logger.init();
@@ -164,24 +174,29 @@ describe('Logger', () => {
it('Log', () => { it('Log', () => {
const logger = require('../lib/util/logger').default; const logger = require('../lib/util/logger').default;
logger.init(); logger.init();
const warn = jest.spyOn(logger.winston(), 'warning'); logger.setLevel('debug');
logger.warn('warn');
expect(warn).toHaveBeenCalledWith('warn');
const debug = jest.spyOn(logger.winston(), 'debug'); const debug = jest.spyOn(logger.winston(), 'debug');
logger.debug('debug'); logger.debug('debug');
expect(debug).toHaveBeenCalledWith('debug'); expect(debug).toHaveBeenCalledWith('debug', {namespace: 'z2m'});
expect(debug).toHaveBeenCalledTimes(1);
const warning = jest.spyOn(logger.winston(), 'warning');
logger.warning('warning');
expect(warning).toHaveBeenCalledWith('warning');
const info = jest.spyOn(logger.winston(), 'info'); const info = jest.spyOn(logger.winston(), 'info');
logger.info('info'); logger.info('info');
expect(info).toHaveBeenCalledWith('info'); expect(info).toHaveBeenCalledWith('info', {namespace: 'z2m'});
expect(info).toHaveBeenCalledTimes(1);
const warning = jest.spyOn(logger.winston(), 'warning');
logger.warning('warning');
expect(warning).toHaveBeenCalledWith('warning', {namespace: 'z2m'});
expect(warning).toHaveBeenCalledTimes(1);
const error = jest.spyOn(logger.winston(), 'error'); const error = jest.spyOn(logger.winston(), 'error');
logger.error('error'); logger.error('error');
expect(error).toHaveBeenCalledWith('error'); expect(error).toHaveBeenCalledWith('error', {namespace: 'z2m'});
logger.error(new Error('error'));// test for stack=true
expect(error).toHaveBeenCalledWith('error', {namespace: 'z2m'});
expect(error).toHaveBeenCalledTimes(2);
}); });
}); });
+1 -1
View File
@@ -17,7 +17,7 @@ const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings'); const settings = require('../lib/util/settings');
const Controller = require('../lib/controller'); const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises'); const flushPromises = require('./lib/flushPromises');
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
const setTimeoutNative = setTimeout; const setTimeoutNative = setTimeout;
describe('Networkmap', () => { describe('Networkmap', () => {
+1 -1
View File
@@ -8,7 +8,7 @@ const settings = require('../lib/util/settings');
const Controller = require('../lib/controller'); const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises'); const flushPromises = require('./lib/flushPromises');
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const mockOnEvent = jest.fn(); const mockOnEvent = jest.fn();
+1 -1
View File
@@ -9,7 +9,7 @@ const settings = require('../lib/util/settings');
const Controller = require('../lib/controller'); const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises'); const flushPromises = require('./lib/flushPromises');
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
const expectNothingPublished = () => { const expectNothingPublished = () => {
Object.values(zigbeeHerdsman.devices).forEach((d) => { Object.values(zigbeeHerdsman.devices).forEach((d) => {
+1 -1
View File
@@ -8,7 +8,7 @@ const settings = require('../lib/util/settings');
const Controller = require('../lib/controller'); const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises'); const flushPromises = require('./lib/flushPromises');
const mocksClear = [MQTT.publish, logger.warn, logger.debug]; const mocksClear = [MQTT.publish, logger.warning, logger.debug];
describe('Receive', () => { describe('Receive', () => {
let controller; let controller;
+3 -3
View File
@@ -3,10 +3,10 @@ let level = 'info';
const transports = []; const transports = [];
let transportsEnabled = false; let transportsEnabled = false;
const callTransports = (level, message) => { const callTransports = (level, message, namespace) => {
if (transportsEnabled) { if (transportsEnabled) {
for (const transport of transports) { for (const transport of transports) {
transport.log({level, message}, () => {}); transport.log({level, message, namespace}, () => {});
} }
} }
} }
@@ -14,7 +14,7 @@ const callTransports = (level, message) => {
const mock = { const mock = {
init: jest.fn(), init: jest.fn(),
info: jest.fn().mockImplementation((msg) => callTransports('info', msg)), info: jest.fn().mockImplementation((msg) => callTransports('info', msg)),
warn: jest.fn().mockImplementation((msg) => callTransports('warn', msg)), warning: jest.fn().mockImplementation((msg) => callTransports('warning', msg)),
error: jest.fn().mockImplementation((msg) => callTransports('error', msg)), error: jest.fn().mockImplementation((msg) => callTransports('error', msg)),
debug: jest.fn().mockImplementation((msg) => callTransports('debug', msg)), debug: jest.fn().mockImplementation((msg) => callTransports('debug', msg)),
cleanup: jest.fn(), cleanup: jest.fn(),
+1
View File
@@ -261,6 +261,7 @@ const mock = {
const mockConstructor = jest.fn().mockImplementation(() => mock); const mockConstructor = jest.fn().mockImplementation(() => mock);
jest.mock('zigbee-herdsman', () => ({ jest.mock('zigbee-herdsman', () => ({
...jest.requireActual('zigbee-herdsman'),
Controller: mockConstructor, Controller: mockConstructor,
Zcl: {ManufacturerCode: {SIGNIFY_NETHERLANDS_B_V: 4107}}, Zcl: {ManufacturerCode: {SIGNIFY_NETHERLANDS_B_V: 4107}},
})); }));