mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-06 19:00:13 +00:00
fix: Add settings for finer control over debug log level (#22426)
* Add settings for finer control over debug log level. * Updates from feedback.
This commit is contained in:
+18
-18
@@ -4,7 +4,7 @@ import utils from '../util/utils';
|
||||
import Extension from './extension';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
import debounce from 'debounce';
|
||||
import * as zigbeeHerdsman from 'zigbee-herdsman/dist';
|
||||
import {Zcl} from 'zigbee-herdsman';
|
||||
import bind from 'bind-decorator';
|
||||
import Device from '../model/device';
|
||||
import Group from '../model/group';
|
||||
@@ -97,12 +97,12 @@ const pollOnMessage: PollOnMessage = [
|
||||
read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']},
|
||||
// When the bound devices/members of group have the following manufacturerIDs
|
||||
manufacturerIDs: [
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
|
||||
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
Zcl.ManufacturerCode.ATMEL,
|
||||
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
|
||||
],
|
||||
manufacturerNames: [
|
||||
'GLEDOPTO',
|
||||
@@ -133,12 +133,12 @@ const pollOnMessage: PollOnMessage = [
|
||||
},
|
||||
read: {cluster: 'genOnOff', attributes: ['onOff']},
|
||||
manufacturerIDs: [
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
|
||||
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
Zcl.ManufacturerCode.ATMEL,
|
||||
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
|
||||
],
|
||||
manufacturerNames: [
|
||||
'GLEDOPTO',
|
||||
@@ -165,11 +165,11 @@ const pollOnMessage: PollOnMessage = [
|
||||
},
|
||||
},
|
||||
manufacturerIDs: [
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
|
||||
Zcl.ManufacturerCode.ATMEL,
|
||||
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
|
||||
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
|
||||
Zcl.ManufacturerCode.TELINK_MICRO,
|
||||
// Note: ManufacturerCode.BUSCH_JAEGER is left out intentionally here as their devices don't support colors
|
||||
],
|
||||
manufacturerNames: [
|
||||
|
||||
+38
-17
@@ -61,23 +61,40 @@ export default class Bridge extends Extension {
|
||||
'config/log_level': this.configLogLevel,
|
||||
};
|
||||
|
||||
const mqtt = this.mqtt;
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
class EventTransport extends Transport {
|
||||
log(info: {message: string, level: string}, next: () => void): void {
|
||||
if (info.level !== 'debug') {
|
||||
const payload = stringify({message: info.message, level: info.level});
|
||||
if (payload !== self.lastBridgeLoggingPayload) {
|
||||
self.lastBridgeLoggingPayload = payload;
|
||||
mqtt.publish(`bridge/logging`, payload, {}, settings.get().mqtt.base_topic, true);
|
||||
}
|
||||
}
|
||||
next();
|
||||
const debugToMQTTFrontend = settings.get().advanced.log_debug_to_mqtt_frontend;
|
||||
const baseTopic = settings.get().mqtt.base_topic;
|
||||
|
||||
const bridgeLogging = (message: string, level: string, namespace: string): void => {
|
||||
const payload = stringify({message, level, namespace});
|
||||
|
||||
if (payload !== this.lastBridgeLoggingPayload) {
|
||||
this.lastBridgeLoggingPayload = payload;
|
||||
this.mqtt.publish(`bridge/logging`, payload, {}, baseTopic, true);
|
||||
}
|
||||
};
|
||||
|
||||
if (debugToMQTTFrontend) {
|
||||
class DebugEventTransport extends Transport {
|
||||
log(info: {message: string, level: string, namespace: string}, next: () => void): void {
|
||||
bridgeLogging(info.message, info.level, info.namespace);
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
this.logTransport = new DebugEventTransport();
|
||||
} else {
|
||||
class EventTransport extends Transport {
|
||||
log(info: {message: string, level: string, namespace: string}, next: () => void): void {
|
||||
if (info.level !== 'debug') {
|
||||
bridgeLogging(info.message, info.level, info.namespace);
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
this.logTransport = new EventTransport();
|
||||
}
|
||||
|
||||
this.logTransport = new EventTransport();
|
||||
logger.addTransport(this.logTransport);
|
||||
|
||||
this.zigbee2mqttVersion = await utils.getZigbee2MQTTVersion();
|
||||
@@ -174,18 +191,22 @@ export default class Bridge extends Extension {
|
||||
if (restartRequired) this.restartRequired = true;
|
||||
|
||||
// Apply some settings on-the-fly.
|
||||
if (newSettings.hasOwnProperty('permit_join')) {
|
||||
if (newSettings.permit_join != undefined) {
|
||||
await this.zigbee.permitJoin(newSettings.permit_join);
|
||||
}
|
||||
|
||||
if (newSettings.hasOwnProperty('homeassistant')) {
|
||||
if (newSettings.homeassistant != undefined) {
|
||||
await this.enableDisableExtension(newSettings.homeassistant, 'HomeAssistant');
|
||||
}
|
||||
|
||||
if (newSettings.hasOwnProperty('advanced') && newSettings.advanced.hasOwnProperty('log_level')) {
|
||||
if (newSettings.advanced?.log_level != undefined) {
|
||||
logger.setLevel(newSettings.advanced.log_level);
|
||||
}
|
||||
|
||||
if (newSettings.advanced?.log_debug_namespace_ignore != undefined) {
|
||||
logger.setDebugNamespaceIgnore(newSettings.advanced.log_debug_namespace_ignore);
|
||||
}
|
||||
|
||||
logger.info('Successfully changed options');
|
||||
this.publishInfo();
|
||||
return utils.getResponse(message, {restart_required: this.restartRequired}, null);
|
||||
|
||||
Vendored
+2
@@ -196,6 +196,8 @@ declare global {
|
||||
log_file: string,
|
||||
log_level: 'debug' | 'info' | 'error' | 'warn',
|
||||
log_syslog: KeyValue,
|
||||
log_debug_to_mqtt_frontend: boolean,
|
||||
log_debug_namespace_ignore: string,
|
||||
pan_id: number | 'GENERATE',
|
||||
ext_pan_id: number[] | 'GENERATE',
|
||||
channel: number,
|
||||
|
||||
@@ -16,6 +16,7 @@ class Logger {
|
||||
private readonly directory: string;
|
||||
private readonly logger: winston.Logger;
|
||||
private readonly fileTransport: winston.transports.FileTransportInstance;
|
||||
private debugNamespaceIgnoreRegex?: RegExp;
|
||||
|
||||
constructor() {
|
||||
// What transports to enable
|
||||
@@ -120,6 +121,8 @@ class Logger {
|
||||
this.logger.add(new winston.transports.Syslog(options));
|
||||
}
|
||||
|
||||
this.setDebugNamespaceIgnore(settings.get().advanced.log_debug_namespace_ignore);
|
||||
|
||||
this.info(logging);
|
||||
}
|
||||
|
||||
@@ -136,6 +139,14 @@ class Logger {
|
||||
this.logger.remove(transport);
|
||||
}
|
||||
|
||||
public getDebugNamespaceIgnore(): string {
|
||||
return this.debugNamespaceIgnoreRegex?.toString().slice(1, -1)/* remove slashes */ ?? '';
|
||||
}
|
||||
|
||||
public setDebugNamespaceIgnore(value: string): void {
|
||||
this.debugNamespaceIgnoreRegex = value != '' ? new RegExp(value) : undefined;
|
||||
}
|
||||
|
||||
// TODO refactor Z2M level to 'warning' to simplify logic
|
||||
public getLevel(): LogLevel | 'warn' {
|
||||
return this.level === 'warning' ? 'warn' : this.level;
|
||||
@@ -162,6 +173,9 @@ class Logger {
|
||||
if (this.level !== 'debug') {
|
||||
return;
|
||||
}
|
||||
if (this.debugNamespaceIgnoreRegex?.test(namespace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(message, {namespace});
|
||||
}
|
||||
|
||||
@@ -497,6 +497,23 @@
|
||||
"description": "Logging level",
|
||||
"default": "info"
|
||||
},
|
||||
"log_debug_to_mqtt_frontend": {
|
||||
"type": "boolean",
|
||||
"title": "Log debug to MQTT and frontend",
|
||||
"description": "Log debug level to MQTT and frontend (may decrease overall performance)",
|
||||
"requiresRestart": true,
|
||||
"default": false
|
||||
},
|
||||
"log_debug_namespace_ignore": {
|
||||
"type": "string",
|
||||
"title": "Log debug namespace ignore",
|
||||
"description": "Do not log these namespaces (regex-based) for debug level",
|
||||
"default": "",
|
||||
"examples": [
|
||||
"^zhc:legacy:fz:(tuya|moes)",
|
||||
"^zhc:legacy:fz:(tuya|moes)|^zh:ember:uart:|^zh:controller"
|
||||
]
|
||||
},
|
||||
"log_syslog": {
|
||||
"type": "object",
|
||||
"title": "syslog",
|
||||
|
||||
@@ -83,6 +83,8 @@ const defaults: RecursivePartial<Settings> = {
|
||||
log_file: 'log.log',
|
||||
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
|
||||
log_syslog: {},
|
||||
log_debug_to_mqtt_frontend: false,
|
||||
log_debug_namespace_ignore: '',
|
||||
pan_id: 0x1a62,
|
||||
ext_pan_id: [0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD],
|
||||
channel: 11,
|
||||
|
||||
+42
-2
File diff suppressed because one or more lines are too long
@@ -178,4 +178,66 @@ describe('Logger', () => {
|
||||
expect(error).toHaveBeenCalledWith('error', {namespace: 'z2m'});
|
||||
expect(error).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
'^zhc:legacy:fz:(tuya|moes)',
|
||||
new RegExp(/^zhc:legacy:fz:(tuya|moes)/),
|
||||
[
|
||||
{ ns: 'zhc:legacy:fz:tuya_device12', match: true },
|
||||
{ ns: 'zhc:legacy:fz:moes_dimmer', match: true },
|
||||
{ ns: 'zhc:legacy:fz:not_moes', match: false },
|
||||
{ ns: 'zhc:legacy:fz', match: false },
|
||||
{ ns: 'zhc:legacy:fz:', match: false },
|
||||
{ ns: '1zhc:legacy:fz:tuya_device12', match: false },
|
||||
]
|
||||
],
|
||||
[
|
||||
'^zhc:legacy:fz:(tuya|moes)|^zh:ember:uart:|^zh:controller',
|
||||
new RegExp(/^zhc:legacy:fz:(tuya|moes)|^zh:ember:uart:|^zh:controller/),
|
||||
[
|
||||
{ ns: 'zh:ember:uart:ash', match: true },
|
||||
{ ns: 'zh:ember:uart', match: false },
|
||||
{ ns: 'zh:controller', match: true },
|
||||
{ ns: 'zh:controller:', match: true },
|
||||
{ ns: 'azh:controller:', match: false },
|
||||
]
|
||||
],
|
||||
[
|
||||
'',
|
||||
undefined,
|
||||
[
|
||||
{ ns: 'zhc:legacy:fz:tuya_device12', match: false },
|
||||
{ ns: 'zhc:legacy:fz:moes_dimmer', match: false },
|
||||
{ ns: 'zhc:legacy:fz:not_moes', match: false },
|
||||
{ ns: 'zhc:legacy:fz', match: false },
|
||||
{ ns: 'zhc:legacy:fz:', match: false },
|
||||
{ ns: '1zhc:legacy:fz:tuya_device12', match: false },
|
||||
{ ns: 'zh:ember:uart:ash', match: false },
|
||||
{ ns: 'zh:ember:uart', match: false },
|
||||
{ ns: 'zh:controller', match: false },
|
||||
{ ns: 'zh:controller:', match: false },
|
||||
{ ns: 'azh:controller:', match: false },
|
||||
]
|
||||
],
|
||||
])('Sets namespace ignore for debug level %s', (ignore, expected, tests) => {
|
||||
const logger = require('../lib/util/logger').default;
|
||||
logger.setLevel('debug');
|
||||
const debugSpy = jest.spyOn(logger.winston, 'debug');
|
||||
logger.setDebugNamespaceIgnore(ignore);
|
||||
expect(logger.debugNamespaceIgnoreRegex).toStrictEqual(expected);
|
||||
expect(logger.getDebugNamespaceIgnore()).toStrictEqual(ignore);
|
||||
|
||||
for (const test of tests) {
|
||||
logger.debug('Test message', test.ns);
|
||||
|
||||
if (test.match) {
|
||||
expect(debugSpy).not.toHaveBeenCalled();
|
||||
} else {
|
||||
expect(debugSpy).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
debugSpy.mockClear();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
+7
-4
@@ -1,4 +1,5 @@
|
||||
let level = 'info';
|
||||
let debugNamespaceIgnore = '';
|
||||
|
||||
let transports = [];
|
||||
|
||||
@@ -13,10 +14,10 @@ const callTransports = (level, message, namespace) => {
|
||||
|
||||
const mock = {
|
||||
init: jest.fn(),
|
||||
info: jest.fn().mockImplementation((msg) => callTransports('info', msg)),
|
||||
warning: jest.fn().mockImplementation((msg) => callTransports('warning', msg)),
|
||||
error: jest.fn().mockImplementation((msg) => callTransports('error', msg)),
|
||||
debug: jest.fn().mockImplementation((msg) => callTransports('debug', msg)),
|
||||
info: jest.fn().mockImplementation((msg, namespace = 'z2m') => callTransports('info', msg, namespace)),
|
||||
warning: jest.fn().mockImplementation((msg, namespace = 'z2m') => callTransports('warning', msg, namespace)),
|
||||
error: jest.fn().mockImplementation((msg, namespace = 'z2m') => callTransports('error', msg, namespace)),
|
||||
debug: jest.fn().mockImplementation((msg, namespace = 'z2m') => callTransports('debug', msg, namespace)),
|
||||
cleanup: jest.fn(),
|
||||
logOutput: jest.fn(),
|
||||
add: (transport) => transports.push(transport),
|
||||
@@ -26,6 +27,8 @@ const mock = {
|
||||
},
|
||||
setLevel: (newLevel) => {level = newLevel},
|
||||
getLevel: () => level,
|
||||
setDebugNamespaceIgnore: (newIgnore) => {debugNamespaceIgnore = newIgnore},
|
||||
getDebugNamespaceIgnore: () => debugNamespaceIgnore,
|
||||
setTransportsEnabled: (value) => {transportsEnabled = value},
|
||||
end: jest.fn(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user