From 5c6ac954803085f8622f75b0bde3dc63b720b7aa Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Thu, 30 Jan 2025 10:54:30 +0100 Subject: [PATCH 1/3] feat: build ZWaveLoggerBase from traits, make ZWaveLogContainer generic --- packages/config/src/ConfigManager.ts | 3 +- packages/config/src/Logger.ts | 2 +- packages/core/src/index.ts | 2 ++ packages/core/src/index_browser.ts | 2 ++ packages/core/src/log/Controller.ts | 15 +++++----- packages/core/src/log/ZWaveLoggerBase.ts | 12 ++++++++ packages/core/src/log/shared.ts | 30 +++++++------------ packages/core/src/log/traits.ts | 17 +++++++++++ packages/serial/src/log/Logger.ts | 2 +- .../src/serialport/ZWaveSerialStream.ts | 3 +- .../serial/src/zniffer/ZnifferSerialStream.ts | 3 +- packages/zwave-js/src/lib/driver/Driver.ts | 4 +-- packages/zwave-js/src/lib/log/Driver.ts | 9 ++++-- packages/zwave-js/src/lib/log/Zniffer.ts | 5 +++- packages/zwave-js/src/lib/zniffer/Zniffer.ts | 4 +-- 15 files changed, 74 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/log/ZWaveLoggerBase.ts create mode 100644 packages/core/src/log/traits.ts diff --git a/packages/config/src/ConfigManager.ts b/packages/config/src/ConfigManager.ts index e563db2aeedc..c5c0a4386130 100644 --- a/packages/config/src/ConfigManager.ts +++ b/packages/config/src/ConfigManager.ts @@ -8,6 +8,7 @@ import { getErrorMessage, pathExists } from "@zwave-js/shared"; import { type FileSystem } from "@zwave-js/shared/bindings"; import path from "pathe"; import { ConfigLogger } from "./Logger.js"; +import { type ConfigLogContext } from "./Logger_safe.js"; import { type ManufacturersMap, loadManufacturersInternal, @@ -34,7 +35,7 @@ import { export interface ConfigManagerOptions { bindings?: FileSystem; - logContainer?: ZWaveLogContainer; + logContainer?: ZWaveLogContainer; deviceConfigPriorityDir?: string; deviceConfigExternalDir?: string; } diff --git a/packages/config/src/Logger.ts b/packages/config/src/Logger.ts index b4cfcce56036..7c8d26d0ef5d 100644 --- a/packages/config/src/Logger.ts +++ b/packages/config/src/Logger.ts @@ -10,7 +10,7 @@ import { } from "./Logger_safe.js"; export class ConfigLogger extends ZWaveLoggerBase { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: ZWaveLogContainer) { super(loggers, CONFIG_LABEL); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5b61912b6d87..108793f84a73 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,8 +6,10 @@ export * from "./error/ZWaveError.js"; export * from "./fsm/FSM.js"; export * from "./log/Controller.definitions.js"; export * from "./log/Controller.js"; +export * from "./log/ZWaveLoggerBase.js"; export * from "./log/shared.js"; export * from "./log/shared_safe.js"; +export type * from "./log/traits.js"; export * from "./qr/index.js"; export * from "./reflection/decorators.js"; export * from "./registries/index.js"; diff --git a/packages/core/src/index_browser.ts b/packages/core/src/index_browser.ts index d9e3ea0f94d3..a5843595db1a 100644 --- a/packages/core/src/index_browser.ts +++ b/packages/core/src/index_browser.ts @@ -6,7 +6,9 @@ export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; export * from "./fsm/FSM.js"; export * from "./log/Controller.definitions.js"; +export * from "./log/ZWaveLoggerBase.js"; export * from "./log/shared_safe.js"; +export type * from "./log/traits.js"; export * from "./qr/index.js"; export * from "./registries/DeviceClasses.js"; export * from "./registries/Indicators.js"; diff --git a/packages/core/src/log/Controller.ts b/packages/core/src/log/Controller.ts index df21dc24bf38..9a5331d2d5c7 100644 --- a/packages/core/src/log/Controller.ts +++ b/packages/core/src/log/Controller.ts @@ -20,14 +20,15 @@ import { type LogValueArgs, VALUE_LOGLEVEL, } from "./Controller.definitions.js"; -import { type ZWaveLogContainer, ZWaveLoggerBase } from "./shared.js"; +import { ZWaveLoggerBase } from "./ZWaveLoggerBase.js"; +import { type ZWaveLogContainer } from "./shared.js"; import { tagify } from "./shared_safe.js"; import { getDirectionPrefix, getNodeTag } from "./shared_safe.js"; export class ControllerLogger extends ZWaveLoggerBase implements LogNode { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: ZWaveLogContainer) { super(loggers, CONTROLLER_LABEL); } @@ -87,7 +88,7 @@ export class ControllerLogger extends ZWaveLoggerBase const { level, message, direction, endpoint } = messageOrOptions; const actualLevel = level || CONTROLLER_LOGLEVEL; if (!this.container.isLoglevelVisible(actualLevel)) return; - if (!this.container.shouldLogNode(nodeId)) return; + if (!this.container.isNodeLoggingVisible(nodeId)) return; const context: ControllerNodeLogContext = { nodeId, @@ -135,7 +136,7 @@ export class ControllerLogger extends ZWaveLoggerBase args: LogValueArgs, ): void { if (!this.isValueLogVisible()) return; - if (!this.container.shouldLogNode(args.nodeId)) return; + if (!this.container.isNodeLoggingVisible(args.nodeId)) return; const context: ControllerValueLogContext = { nodeId: args.nodeId, change, @@ -208,7 +209,7 @@ export class ControllerLogger extends ZWaveLoggerBase /** Prints a log message for updated metadata of a value id */ public metadataUpdated(args: LogValueArgs): void { if (!this.isValueLogVisible()) return; - if (!this.container.shouldLogNode(args.nodeId)) return; + if (!this.container.isNodeLoggingVisible(args.nodeId)) return; const context: ControllerValueLogContext = { nodeId: args.nodeId, commandClass: args.commandClass, @@ -248,7 +249,7 @@ export class ControllerLogger extends ZWaveLoggerBase /** Logs the interview progress of a node */ public interviewStage(node: Interviewable): void { if (!this.isControllerLogVisible()) return; - if (!this.container.shouldLogNode(node.id)) return; + if (!this.container.isNodeLoggingVisible(node.id)) return; this.logger.log({ level: CONTROLLER_LOGLEVEL, @@ -271,7 +272,7 @@ export class ControllerLogger extends ZWaveLoggerBase /** Logs the interview progress of a node */ public interviewStart(node: Interviewable): void { if (!this.isControllerLogVisible()) return; - if (!this.container.shouldLogNode(node.id)) return; + if (!this.container.isNodeLoggingVisible(node.id)) return; const message = `Beginning interview - last completed stage: ${ InterviewStage[node.interviewStage] diff --git a/packages/core/src/log/ZWaveLoggerBase.ts b/packages/core/src/log/ZWaveLoggerBase.ts new file mode 100644 index 000000000000..7f87a412571a --- /dev/null +++ b/packages/core/src/log/ZWaveLoggerBase.ts @@ -0,0 +1,12 @@ +import { type LogContext } from "./shared_safe.js"; +import type { LogContainer, ZWaveLogger } from "./traits.js"; + +export class ZWaveLoggerBase { + constructor(loggers: LogContainer, logLabel: string) { + this.container = loggers; + this.logger = this.container.getLogger(logLabel); + } + + public logger: ZWaveLogger; + public container: LogContainer; +} diff --git a/packages/core/src/log/shared.ts b/packages/core/src/log/shared.ts index 6393aa98b622..25bde369d44c 100644 --- a/packages/core/src/log/shared.ts +++ b/packages/core/src/log/shared.ts @@ -1,7 +1,7 @@ import type { Format, TransformFunction } from "logform"; import path from "pathe"; import { MESSAGE, configs } from "triple-beam"; -import winston, { type Logger } from "winston"; +import winston from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; import type Transport from "winston-transport"; import type { ConsoleTransportInstance } from "winston/lib/winston/transports"; @@ -21,6 +21,8 @@ import { timestampPadding, timestampPaddingShort, } from "./shared_safe.js"; +import { type LogContainer } from "./traits.js"; +import { type ZWaveLogger } from "./traits.js"; const { combine, timestamp, label } = winston.format; @@ -50,17 +52,10 @@ export const nonUndefinedLogConfigKeys = [ "forceConsole", ] as const; -export class ZWaveLoggerBase { - constructor(loggers: ZWaveLogContainer, logLabel: string) { - this.container = loggers; - this.logger = this.container.getLogger(logLabel); - } - - public logger: ZWaveLogger; - public container: ZWaveLogContainer; -} - -export class ZWaveLogContainer extends winston.Container { +export class ZWaveLogContainer + extends winston.Container + implements LogContainer +{ private fileTransport: DailyRotateFile | undefined; private consoleTransport: ConsoleTransportInstance | undefined; private loglevelVisibleCache = new Map(); @@ -81,7 +76,7 @@ export class ZWaveLogContainer extends winston.Container { this.updateConfiguration(config); } - public getLogger(label: string): ZWaveLogger { + public getLogger(label: string): ZWaveLogger { if (!this.has(label)) { this.add(label, { transports: this.getAllTransports(), @@ -92,7 +87,7 @@ export class ZWaveLogContainer extends winston.Container { }); } - return this.get(label) as unknown as ZWaveLogger; + return this.get(label) as unknown as ZWaveLogger; } public updateConfiguration(config: Partial): void { @@ -294,7 +289,7 @@ export class ZWaveLogContainer extends winston.Container { /** * Checks the log configuration whether logs should be written for a given node id */ - public shouldLogNode(nodeId: number): boolean { + public isNodeLoggingVisible(nodeId: number): boolean { // If no filters are set, every node gets logged if (!this.logConfig.nodeFilter) return true; return this.logConfig.nodeFilter.includes(nodeId); @@ -461,8 +456,3 @@ export function restoreSilence( consoleTransport.silent = original; } } -export type ZWaveLogger = - & Omit - & { - log: (info: ZWaveLogInfo) => void; - }; diff --git a/packages/core/src/log/traits.ts b/packages/core/src/log/traits.ts new file mode 100644 index 000000000000..1c1a5602a442 --- /dev/null +++ b/packages/core/src/log/traits.ts @@ -0,0 +1,17 @@ +import { type LogContext, type ZWaveLogInfo } from "./shared_safe.js"; + +export interface LogVisibility { + isLoglevelVisible(loglevel: string): boolean; + isNodeLoggingVisible(nodeId: number): boolean; +} + +export interface GetLogger { + getLogger(label: string): ZWaveLogger; +} + +export interface ZWaveLogger { + log: (info: ZWaveLogInfo) => void; +} +export type LogContainer = + & GetLogger + & LogVisibility; diff --git a/packages/serial/src/log/Logger.ts b/packages/serial/src/log/Logger.ts index 9a652381c2dd..344db709082e 100644 --- a/packages/serial/src/log/Logger.ts +++ b/packages/serial/src/log/Logger.ts @@ -13,7 +13,7 @@ import { } from "./Logger_safe.js"; export class SerialLogger extends ZWaveLoggerBase { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: ZWaveLogContainer) { super(loggers, SERIAL_LABEL); } diff --git a/packages/serial/src/serialport/ZWaveSerialStream.ts b/packages/serial/src/serialport/ZWaveSerialStream.ts index ef6a4e8e7a20..480299fb8570 100644 --- a/packages/serial/src/serialport/ZWaveSerialStream.ts +++ b/packages/serial/src/serialport/ZWaveSerialStream.ts @@ -17,6 +17,7 @@ import type { UnderlyingSource, } from "node:stream/web"; import { SerialLogger } from "../log/Logger.js"; +import { type SerialLogContext } from "../log/Logger_safe.js"; import { MessageHeaders } from "../message/MessageHeaders.js"; import { type ZWaveSerialFrame } from "../parsers/ZWaveSerialFrame.js"; import { ZWaveSerialParser } from "../plumbing/ZWaveSerialParser.js"; @@ -43,7 +44,7 @@ export function isZWaveSerialBindingFactory( export class ZWaveSerialStreamFactory { constructor( binding: ZWaveSerialBindingFactory, - loggers: ZWaveLogContainer, + loggers: ZWaveLogContainer, ) { this.binding = binding; this.logger = new SerialLogger(loggers); diff --git a/packages/serial/src/zniffer/ZnifferSerialStream.ts b/packages/serial/src/zniffer/ZnifferSerialStream.ts index 66c102f3a895..93123bc04dc8 100644 --- a/packages/serial/src/zniffer/ZnifferSerialStream.ts +++ b/packages/serial/src/zniffer/ZnifferSerialStream.ts @@ -17,6 +17,7 @@ import type { UnderlyingSource, } from "node:stream/web"; import { SerialLogger } from "../log/Logger.js"; +import { type SerialLogContext } from "../log/Logger_safe.js"; import { ZnifferParser } from "../parsers/ZnifferParser.js"; import { type ZnifferSerialFrame } from "../parsers/ZnifferSerialFrame.js"; import { type ZWaveSerialBindingFactory } from "../serialport/ZWaveSerialStream.js"; @@ -25,7 +26,7 @@ import { type ZWaveSerialBindingFactory } from "../serialport/ZWaveSerialStream. export class ZnifferSerialStreamFactory { constructor( binding: ZWaveSerialBindingFactory, - loggers: ZWaveLogContainer, + loggers: ZWaveLogContainer, ) { this.binding = binding; this.logger = new SerialLogger(loggers); diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index e091fb4eb812..848ff706913e 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -208,7 +208,7 @@ import path from "pathe"; import { PACKAGE_NAME, PACKAGE_VERSION } from "../_version.js"; import { ZWaveController } from "../controller/Controller.js"; import { InclusionState, RemoveNodeReason } from "../controller/Inclusion.js"; -import { DriverLogger } from "../log/Driver.js"; +import { type DriverLogContext, DriverLogger } from "../log/Driver.js"; import type { Endpoint } from "../node/Endpoint.js"; import type { ZWaveNode } from "../node/Node.js"; import { @@ -927,7 +927,7 @@ export class Driver extends TypedEventTarget ); } - private _logContainer: ZWaveLogContainer; + private _logContainer: ZWaveLogContainer; private _driverLog: DriverLogger; /** @internal */ public get driverLog(): DriverLogger { diff --git a/packages/zwave-js/src/lib/log/Driver.ts b/packages/zwave-js/src/lib/log/Driver.ts index be5335fee4ac..e6085471ed9b 100644 --- a/packages/zwave-js/src/lib/log/Driver.ts +++ b/packages/zwave-js/src/lib/log/Driver.ts @@ -31,7 +31,10 @@ export interface DriverLogContext extends LogContext<"driver"> { } export class DriverLogger extends ZWaveLoggerBase { - constructor(private readonly driver: Driver, loggers: ZWaveLogContainer) { + constructor( + private readonly driver: Driver, + loggers: ZWaveLogContainer, + ) { super(loggers, DRIVER_LABEL); } @@ -118,7 +121,9 @@ export class DriverLogger extends ZWaveLoggerBase { ): void { if (!this.isDriverLogVisible()) return; if (nodeId == undefined) nodeId = message.getNodeId(); - if (nodeId != undefined && !this.container.shouldLogNode(nodeId)) { + if ( + nodeId != undefined && !this.container.isNodeLoggingVisible(nodeId) + ) { return; } diff --git a/packages/zwave-js/src/lib/log/Zniffer.ts b/packages/zwave-js/src/lib/log/Zniffer.ts index c018e91571a1..d5ffea080e41 100644 --- a/packages/zwave-js/src/lib/log/Zniffer.ts +++ b/packages/zwave-js/src/lib/log/Zniffer.ts @@ -35,7 +35,10 @@ export interface ZnifferLogContext extends LogContext<"zniffer"> { } export class ZnifferLogger extends ZWaveLoggerBase { - constructor(private readonly zniffer: Zniffer, loggers: ZWaveLogContainer) { + constructor( + private readonly zniffer: Zniffer, + loggers: ZWaveLogContainer, + ) { super(loggers, ZNIFFER_LABEL); } diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 7ebe3f613b75..2c4efb26756b 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -76,7 +76,7 @@ import { createDeferredPromise, } from "alcalzone-shared/deferred-promise"; import { type ZWaveOptions } from "../driver/ZWaveOptions.js"; -import { ZnifferLogger } from "../log/Zniffer.js"; +import { type ZnifferLogContext, ZnifferLogger } from "../log/Zniffer.js"; import { type CorruptedFrame, type Frame, @@ -314,7 +314,7 @@ export class Zniffer extends TypedEventTarget { return this._supportedFrequencies; } - private _logContainer: ZWaveLogContainer; + private _logContainer: ZWaveLogContainer; private znifferLog: ZnifferLogger; /** The security managers for each node */ From 08d8ee0df51a7e61a3932c3489d50a13fbff786e Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 31 Jan 2025 09:57:51 +0100 Subject: [PATCH 2/3] refactor: pluggable LogContainer --- packages/config/src/ConfigManager.ts | 8 +- packages/config/src/Logger.ts | 4 +- packages/core/src/bindings/log/node.ts | 281 +++++++++++++++++ packages/core/src/index.ts | 5 +- packages/core/src/index_browser.ts | 10 +- packages/core/src/log/Controller.ts | 4 +- packages/core/src/log/ZWaveLoggerBase.ts | 4 +- packages/core/src/log/shared.ts | 292 +----------------- packages/core/src/log/shared_safe.ts | 25 ++ packages/core/src/log/traits.ts | 21 +- .../src/rules/no-forbidden-imports.ts | 1 + packages/serial/src/log/Logger.ts | 4 +- packages/serial/src/mock/MockPort.ts | 4 +- .../src/serialport/ZWaveSerialStream.ts | 5 +- .../serial/src/zniffer/ZnifferSerialStream.ts | 5 +- packages/zwave-js/src/lib/driver/Driver.ts | 50 +-- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 6 + packages/zwave-js/src/lib/log/Driver.ts | 4 +- packages/zwave-js/src/lib/log/Zniffer.ts | 4 +- packages/zwave-js/src/lib/zniffer/Zniffer.ts | 20 +- 20 files changed, 399 insertions(+), 358 deletions(-) create mode 100644 packages/core/src/bindings/log/node.ts diff --git a/packages/config/src/ConfigManager.ts b/packages/config/src/ConfigManager.ts index c5c0a4386130..0a104c49f650 100644 --- a/packages/config/src/ConfigManager.ts +++ b/packages/config/src/ConfigManager.ts @@ -1,14 +1,14 @@ import { + type LogContainer, ZWaveError, ZWaveErrorCodes, - ZWaveLogContainer, isZWaveError, } from "@zwave-js/core"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { getErrorMessage, pathExists } from "@zwave-js/shared"; import { type FileSystem } from "@zwave-js/shared/bindings"; import path from "pathe"; import { ConfigLogger } from "./Logger.js"; -import { type ConfigLogContext } from "./Logger_safe.js"; import { type ManufacturersMap, loadManufacturersInternal, @@ -35,7 +35,7 @@ import { export interface ConfigManagerOptions { bindings?: FileSystem; - logContainer?: ZWaveLogContainer; + logContainer?: LogContainer; deviceConfigPriorityDir?: string; deviceConfigExternalDir?: string; } @@ -44,7 +44,7 @@ export class ConfigManager { public constructor(options: ConfigManagerOptions = {}) { this._fs = options.bindings; this.logger = new ConfigLogger( - options.logContainer ?? new ZWaveLogContainer({ enabled: false }), + options.logContainer ?? createZWaveLogContainer({ enabled: false }), ); this.deviceConfigPriorityDir = options.deviceConfigPriorityDir; this.deviceConfigExternalDir = options.deviceConfigExternalDir; diff --git a/packages/config/src/Logger.ts b/packages/config/src/Logger.ts index 7c8d26d0ef5d..a258bd37d6f6 100644 --- a/packages/config/src/Logger.ts +++ b/packages/config/src/Logger.ts @@ -1,5 +1,5 @@ import { - type ZWaveLogContainer, + type LogContainer, ZWaveLoggerBase, getDirectionPrefix, } from "@zwave-js/core"; @@ -10,7 +10,7 @@ import { } from "./Logger_safe.js"; export class ConfigLogger extends ZWaveLoggerBase { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: LogContainer) { super(loggers, CONFIG_LABEL); } diff --git a/packages/core/src/bindings/log/node.ts b/packages/core/src/bindings/log/node.ts new file mode 100644 index 000000000000..5adf33248ee9 --- /dev/null +++ b/packages/core/src/bindings/log/node.ts @@ -0,0 +1,281 @@ +import path from "pathe"; +import { configs } from "triple-beam"; +import winston from "winston"; +import DailyRotateFile from "winston-daily-rotate-file"; +import type Transport from "winston-transport"; +import type { ConsoleTransportInstance } from "winston/lib/winston/transports"; +import { + createDefaultTransportFormat, + createLoggerFormat, +} from "../../log/shared.js"; +import { + type LogConfig, + type LogContext, + type LogFactory, + nonUndefinedLogConfigKeys, + stringToNodeList, +} from "../../log/shared_safe.js"; +import { type LogContainer, type ZWaveLogger } from "../../log/traits.js"; + +const isTTY = process.stdout.isTTY; +const isUnitTest = process.env.NODE_ENV === "test"; + +const loglevels = configs.npm.levels; + +function getTransportLoglevel(): string { + return process.env.LOGLEVEL! in loglevels ? process.env.LOGLEVEL! : "debug"; +} + +/** Performs a reverse lookup of the numeric loglevel */ +function loglevelFromNumber(numLevel: number | undefined): string | undefined { + if (numLevel == undefined) return; + for (const [level, value] of Object.entries(loglevels)) { + if (value === numLevel) return level; + } +} + +class ZWaveLogContainer extends winston.Container + implements LogContainer +{ + private fileTransport: DailyRotateFile | undefined; + private consoleTransport: ConsoleTransportInstance | undefined; + private loglevelVisibleCache = new Map(); + + private logConfig: LogConfig & { level: string } = { + enabled: true, + level: getTransportLoglevel(), + logToFile: !!process.env.LOGTOFILE, + maxFiles: 7, + nodeFilter: stringToNodeList(process.env.LOG_NODES), + transports: undefined as any, + filename: path.join(process.cwd(), `zwavejs_%DATE%.log`), + forceConsole: false, + }; + + constructor(config: Partial = {}) { + super(); + this.updateConfiguration(config); + } + + public getLogger(label: string): ZWaveLogger { + if (!this.has(label)) { + this.add(label, { + transports: this.getAllTransports(), + format: createLoggerFormat(label), + // Accept all logs, no matter what. The individual loggers take care + // of filtering the wrong loglevels + level: "silly", + }); + } + + return this.get(label) as unknown as ZWaveLogger; + } + + public updateConfiguration(config: Partial): void { + // Avoid overwriting configuration settings with undefined if they shouldn't be + for (const key of nonUndefinedLogConfigKeys) { + if (key in config && config[key] === undefined) { + delete config[key]; + } + } + const changedLoggingTarget = (config.logToFile != undefined + && config.logToFile !== this.logConfig.logToFile) + || (config.forceConsole != undefined + && config.forceConsole !== this.logConfig.forceConsole); + + if (typeof config.level === "number") { + config.level = loglevelFromNumber(config.level); + } + const changedLogLevel = config.level != undefined + && config.level !== this.logConfig.level; + + if ( + config.filename != undefined + && !config.filename.includes("%DATE%") + ) { + config.filename += "_%DATE%.log"; + } + const changedFilename = config.filename != undefined + && config.filename !== this.logConfig.filename; + + if (config.maxFiles != undefined) { + if ( + typeof config.maxFiles !== "number" + || config.maxFiles < 1 + || config.maxFiles > 365 + ) { + delete config.maxFiles; + } + } + const changedMaxFiles = config.maxFiles != undefined + && config.maxFiles !== this.logConfig.maxFiles; + + this.logConfig = Object.assign(this.logConfig, config); + + // If the loglevel changed, our cached "is visible" info is out of date + if (changedLogLevel) { + this.loglevelVisibleCache.clear(); + } + + // When the log target (console, file, filename) was changed, recreate the internal transports + // because at least the filename does not update dynamically + // Also do this when configuring the logger for the first time + const recreateInternalTransports = (this.fileTransport == undefined + && this.consoleTransport == undefined) + || changedLoggingTarget + || changedFilename + || changedMaxFiles; + + if (recreateInternalTransports) { + this.fileTransport?.destroy(); + this.fileTransport = undefined; + this.consoleTransport?.destroy(); + this.consoleTransport = undefined; + } + + // When the internal transports or the custom transports were changed, we need to update the loggers + if (recreateInternalTransports || config.transports != undefined) { + this.loggers.forEach((logger) => + logger.configure({ transports: this.getAllTransports() }) + ); + } + } + + public getConfiguration(): LogConfig { + return this.logConfig; + } + + /** Tests whether a log using the given loglevel will be logged */ + public isLoglevelVisible(loglevel: string): boolean { + // If we are not connected to a TTY, not logging to a file and don't have any custom transports, we won't see anything + if ( + !this.fileTransport + && !this.consoleTransport + && (!this.logConfig.transports + || this.logConfig.transports.length === 0) + ) { + return false; + } + + if (!this.loglevelVisibleCache.has(loglevel)) { + this.loglevelVisibleCache.set( + loglevel, + loglevel in loglevels + && loglevels[loglevel] <= loglevels[this.logConfig.level], + ); + } + return this.loglevelVisibleCache.get(loglevel)!; + } + + public destroy(): void { + for (const key in this.loggers) { + this.close(key); + } + + this.fileTransport = undefined; + this.consoleTransport = undefined; + this.logConfig.transports = []; + } + + private getAllTransports(): Transport[] { + return [ + ...this.getInternalTransports(), + ...(this.logConfig.transports ?? []), + ]; + } + + private getInternalTransports(): Transport[] { + const ret: Transport[] = []; + + // If logging is disabled, don't log to any of the default transports + if (!this.logConfig.enabled) { + return ret; + } + + // Log to file only when opted in + if (this.logConfig.logToFile) { + if (!this.fileTransport) { + this.fileTransport = this.createFileTransport(); + } + ret.push(this.fileTransport); + } + + // Console logs can be noise, so only log to console... + if ( + // when in production + !isUnitTest + // and stdout is a TTY while we're not already logging to a file + && ((isTTY && !this.logConfig.logToFile) + // except when the user explicitly wants to + || this.logConfig.forceConsole) + ) { + if (!this.consoleTransport) { + this.consoleTransport = this.createConsoleTransport(); + } + ret.push(this.consoleTransport); + } + + return ret; + } + + private createConsoleTransport(): ConsoleTransportInstance { + return new winston.transports.Console({ + format: createDefaultTransportFormat( + // Only colorize the output if logging to a TTY, otherwise we'll get + // ansi color codes in logfiles or redirected shells + isTTY || isUnitTest, + // Only use short timestamps if logging to a TTY + isTTY, + ), + silent: this.isConsoleTransportSilent(), + }); + } + + private isConsoleTransportSilent(): boolean { + return process.env.NODE_ENV === "test" || !this.logConfig.enabled; + } + + private isFileTransportSilent(): boolean { + return !this.logConfig.enabled; + } + + private createFileTransport(): DailyRotateFile { + const ret = new DailyRotateFile({ + filename: this.logConfig.filename, + auditFile: `${ + this.logConfig.filename + .replace("_%DATE%", "_logrotate") + .replace(/\.log$/, "") + }.json`, + datePattern: "YYYY-MM-DD", + createSymlink: true, + symlinkName: path + .basename(this.logConfig.filename) + .replace(`_%DATE%`, "_current"), + zippedArchive: true, + maxFiles: `${this.logConfig.maxFiles}d`, + format: createDefaultTransportFormat(false, false), + silent: this.isFileTransportSilent(), + }); + ret.on("new", (newFilename: string) => { + console.log(`Logging to file: + ${newFilename}`); + }); + ret.on("error", (err: Error) => { + console.error(`Error in file stream rotator: ${err.message}`); + }); + return ret; + } + + /** + * Checks the log configuration whether logs should be written for a given node id + */ + public isNodeLoggingVisible(nodeId: number): boolean { + // If no filters are set, every node gets logged + if (!this.logConfig.nodeFilter) return true; + return this.logConfig.nodeFilter.includes(nodeId); + } +} + +export const log: LogFactory = (config?: Partial) => + new ZWaveLogContainer(config); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 108793f84a73..97fe958299c5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/consistent-type-exports */ export * from "./crypto/index.js"; export * from "./definitions/index.js"; export * from "./dsk/index.js"; @@ -19,7 +18,7 @@ export * from "./security/Manager2Types.js"; export * from "./security/ctr_drbg.js"; export * from "./test/assertZWaveError.js"; export type * from "./traits/index.js"; -export * from "./util/_Types.js"; +export type * from "./util/_Types.js"; export * from "./util/compareVersions.js"; export { deflateSync, gunzipSync } from "./util/compression.js"; export * from "./util/config.js"; @@ -35,4 +34,4 @@ export * from "./values/Metadata.js"; export * from "./values/Primitive.js"; export * from "./values/Timeout.js"; export * from "./values/ValueDB.js"; -export * from "./values/_Types.js"; +export type * from "./values/_Types.js"; diff --git a/packages/core/src/index_browser.ts b/packages/core/src/index_browser.ts index a5843595db1a..7c3e141c390b 100644 --- a/packages/core/src/index_browser.ts +++ b/packages/core/src/index_browser.ts @@ -6,19 +6,17 @@ export * from "./dsk/index.js"; export * from "./error/ZWaveError.js"; export * from "./fsm/FSM.js"; export * from "./log/Controller.definitions.js"; +export * from "./log/Controller.js"; export * from "./log/ZWaveLoggerBase.js"; export * from "./log/shared_safe.js"; export type * from "./log/traits.js"; export * from "./qr/index.js"; -export * from "./registries/DeviceClasses.js"; -export * from "./registries/Indicators.js"; -export * from "./registries/Meters.js"; -export * from "./registries/Notifications.js"; -export * from "./registries/Scales.js"; -export * from "./registries/Sensors.js"; +export * from "./reflection/decorators.js"; +export * from "./registries/index.js"; export * from "./security/Manager.js"; export * from "./security/Manager2.js"; export * from "./security/Manager2Types.js"; +export * from "./security/ctr_drbg.js"; export type * from "./traits/index.js"; export type * from "./util/_Types.js"; export * from "./util/compareVersions.js"; diff --git a/packages/core/src/log/Controller.ts b/packages/core/src/log/Controller.ts index 9a5331d2d5c7..5943211fdb9c 100644 --- a/packages/core/src/log/Controller.ts +++ b/packages/core/src/log/Controller.ts @@ -21,14 +21,14 @@ import { VALUE_LOGLEVEL, } from "./Controller.definitions.js"; import { ZWaveLoggerBase } from "./ZWaveLoggerBase.js"; -import { type ZWaveLogContainer } from "./shared.js"; import { tagify } from "./shared_safe.js"; import { getDirectionPrefix, getNodeTag } from "./shared_safe.js"; +import { type LogContainer } from "./traits.js"; export class ControllerLogger extends ZWaveLoggerBase implements LogNode { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: LogContainer) { super(loggers, CONTROLLER_LABEL); } diff --git a/packages/core/src/log/ZWaveLoggerBase.ts b/packages/core/src/log/ZWaveLoggerBase.ts index 7f87a412571a..de4aabcf9464 100644 --- a/packages/core/src/log/ZWaveLoggerBase.ts +++ b/packages/core/src/log/ZWaveLoggerBase.ts @@ -2,11 +2,11 @@ import { type LogContext } from "./shared_safe.js"; import type { LogContainer, ZWaveLogger } from "./traits.js"; export class ZWaveLoggerBase { - constructor(loggers: LogContainer, logLabel: string) { + constructor(loggers: LogContainer, logLabel: string) { this.container = loggers; this.logger = this.container.getLogger(logLabel); } public logger: ZWaveLogger; - public container: LogContainer; + public container: LogContainer; } diff --git a/packages/core/src/log/shared.ts b/packages/core/src/log/shared.ts index 25bde369d44c..bf8f7c236089 100644 --- a/packages/core/src/log/shared.ts +++ b/packages/core/src/log/shared.ts @@ -1,313 +1,23 @@ import type { Format, TransformFunction } from "logform"; -import path from "pathe"; -import { MESSAGE, configs } from "triple-beam"; +import { MESSAGE } from "triple-beam"; import winston from "winston"; -import DailyRotateFile from "winston-daily-rotate-file"; -import type Transport from "winston-transport"; -import type { ConsoleTransportInstance } from "winston/lib/winston/transports"; import { colorizer } from "./Colorizer.js"; import { CONTROL_CHAR_WIDTH, LOG_WIDTH, - type LogContext, type ZWaveLogInfo, calculateFirstLineLength, channelPadding, directionPrefixPadding, messageFitsIntoOneLine, messageToLines, - stringToNodeList, timestampFormatShort, timestampPadding, timestampPaddingShort, } from "./shared_safe.js"; -import { type LogContainer } from "./traits.js"; -import { type ZWaveLogger } from "./traits.js"; const { combine, timestamp, label } = winston.format; -const loglevels = configs.npm.levels; -const isTTY = process.stdout.isTTY; -const isUnitTest = process.env.NODE_ENV === "test"; - -export interface LogConfig { - enabled: boolean; - level: string | number; - transports: Transport[]; - logToFile: boolean; - maxFiles: number; - nodeFilter?: number[]; - filename: string; - forceConsole: boolean; -} -/** @internal */ - -export const nonUndefinedLogConfigKeys = [ - "enabled", - "level", - "transports", - "logToFile", - "maxFiles", - "filename", - "forceConsole", -] as const; - -export class ZWaveLogContainer - extends winston.Container - implements LogContainer -{ - private fileTransport: DailyRotateFile | undefined; - private consoleTransport: ConsoleTransportInstance | undefined; - private loglevelVisibleCache = new Map(); - - private logConfig: LogConfig & { level: string } = { - enabled: true, - level: getTransportLoglevel(), - logToFile: !!process.env.LOGTOFILE, - maxFiles: 7, - nodeFilter: stringToNodeList(process.env.LOG_NODES), - transports: undefined as any, - filename: path.join(process.cwd(), `zwavejs_%DATE%.log`), - forceConsole: false, - }; - - constructor(config: Partial = {}) { - super(); - this.updateConfiguration(config); - } - - public getLogger(label: string): ZWaveLogger { - if (!this.has(label)) { - this.add(label, { - transports: this.getAllTransports(), - format: createLoggerFormat(label), - // Accept all logs, no matter what. The individual loggers take care - // of filtering the wrong loglevels - level: "silly", - }); - } - - return this.get(label) as unknown as ZWaveLogger; - } - - public updateConfiguration(config: Partial): void { - // Avoid overwriting configuration settings with undefined if they shouldn't be - for (const key of nonUndefinedLogConfigKeys) { - if (key in config && config[key] === undefined) { - delete config[key]; - } - } - const changedLoggingTarget = (config.logToFile != undefined - && config.logToFile !== this.logConfig.logToFile) - || (config.forceConsole != undefined - && config.forceConsole !== this.logConfig.forceConsole); - - if (typeof config.level === "number") { - config.level = loglevelFromNumber(config.level); - } - const changedLogLevel = config.level != undefined - && config.level !== this.logConfig.level; - - if ( - config.filename != undefined - && !config.filename.includes("%DATE%") - ) { - config.filename += "_%DATE%.log"; - } - const changedFilename = config.filename != undefined - && config.filename !== this.logConfig.filename; - - if (config.maxFiles != undefined) { - if ( - typeof config.maxFiles !== "number" - || config.maxFiles < 1 - || config.maxFiles > 365 - ) { - delete config.maxFiles; - } - } - const changedMaxFiles = config.maxFiles != undefined - && config.maxFiles !== this.logConfig.maxFiles; - - this.logConfig = Object.assign(this.logConfig, config); - - // If the loglevel changed, our cached "is visible" info is out of date - if (changedLogLevel) { - this.loglevelVisibleCache.clear(); - } - - // When the log target (console, file, filename) was changed, recreate the internal transports - // because at least the filename does not update dynamically - // Also do this when configuring the logger for the first time - const recreateInternalTransports = (this.fileTransport == undefined - && this.consoleTransport == undefined) - || changedLoggingTarget - || changedFilename - || changedMaxFiles; - - if (recreateInternalTransports) { - this.fileTransport?.destroy(); - this.fileTransport = undefined; - this.consoleTransport?.destroy(); - this.consoleTransport = undefined; - } - - // When the internal transports or the custom transports were changed, we need to update the loggers - if (recreateInternalTransports || config.transports != undefined) { - this.loggers.forEach((logger) => - logger.configure({ transports: this.getAllTransports() }) - ); - } - } - - public getConfiguration(): LogConfig { - return this.logConfig; - } - - /** Tests whether a log using the given loglevel will be logged */ - public isLoglevelVisible(loglevel: string): boolean { - // If we are not connected to a TTY, not logging to a file and don't have any custom transports, we won't see anything - if ( - !this.fileTransport - && !this.consoleTransport - && (!this.logConfig.transports - || this.logConfig.transports.length === 0) - ) { - return false; - } - - if (!this.loglevelVisibleCache.has(loglevel)) { - this.loglevelVisibleCache.set( - loglevel, - loglevel in loglevels - && loglevels[loglevel] <= loglevels[this.logConfig.level], - ); - } - return this.loglevelVisibleCache.get(loglevel)!; - } - - public destroy(): void { - for (const key in this.loggers) { - this.close(key); - } - - this.fileTransport = undefined; - this.consoleTransport = undefined; - this.logConfig.transports = []; - } - - private getAllTransports(): Transport[] { - return [ - ...this.getInternalTransports(), - ...(this.logConfig.transports ?? []), - ]; - } - - private getInternalTransports(): Transport[] { - const ret: Transport[] = []; - - // If logging is disabled, don't log to any of the default transports - if (!this.logConfig.enabled) { - return ret; - } - - // Log to file only when opted in - if (this.logConfig.logToFile) { - if (!this.fileTransport) { - this.fileTransport = this.createFileTransport(); - } - ret.push(this.fileTransport); - } - - // Console logs can be noise, so only log to console... - if ( - // when in production - !isUnitTest - // and stdout is a TTY while we're not already logging to a file - && ((isTTY && !this.logConfig.logToFile) - // except when the user explicitly wants to - || this.logConfig.forceConsole) - ) { - if (!this.consoleTransport) { - this.consoleTransport = this.createConsoleTransport(); - } - ret.push(this.consoleTransport); - } - - return ret; - } - - private createConsoleTransport(): ConsoleTransportInstance { - return new winston.transports.Console({ - format: createDefaultTransportFormat( - // Only colorize the output if logging to a TTY, otherwise we'll get - // ansi color codes in logfiles or redirected shells - isTTY || isUnitTest, - // Only use short timestamps if logging to a TTY - isTTY, - ), - silent: this.isConsoleTransportSilent(), - }); - } - - private isConsoleTransportSilent(): boolean { - return process.env.NODE_ENV === "test" || !this.logConfig.enabled; - } - - private isFileTransportSilent(): boolean { - return !this.logConfig.enabled; - } - - private createFileTransport(): DailyRotateFile { - const ret = new DailyRotateFile({ - filename: this.logConfig.filename, - auditFile: `${ - this.logConfig.filename - .replace("_%DATE%", "_logrotate") - .replace(/\.log$/, "") - }.json`, - datePattern: "YYYY-MM-DD", - createSymlink: true, - symlinkName: path - .basename(this.logConfig.filename) - .replace(`_%DATE%`, "_current"), - zippedArchive: true, - maxFiles: `${this.logConfig.maxFiles}d`, - format: createDefaultTransportFormat(false, false), - silent: this.isFileTransportSilent(), - }); - ret.on("new", (newFilename: string) => { - console.log(`Logging to file: - ${newFilename}`); - }); - ret.on("error", (err: Error) => { - console.error(`Error in file stream rotator: ${err.message}`); - }); - return ret; - } - - /** - * Checks the log configuration whether logs should be written for a given node id - */ - public isNodeLoggingVisible(nodeId: number): boolean { - // If no filters are set, every node gets logged - if (!this.logConfig.nodeFilter) return true; - return this.logConfig.nodeFilter.includes(nodeId); - } -} - -function getTransportLoglevel(): string { - return process.env.LOGLEVEL! in loglevels ? process.env.LOGLEVEL! : "debug"; -} - -/** Performs a reverse lookup of the numeric loglevel */ -function loglevelFromNumber(numLevel: number | undefined): string | undefined { - if (numLevel == undefined) return; - for (const [level, value] of Object.entries(loglevels)) { - if (value === numLevel) return level; - } -} - /** Creates the common logger format for all loggers under a given channel */ export function createLoggerFormat(channel: string): Format { return combine( diff --git a/packages/core/src/log/shared_safe.ts b/packages/core/src/log/shared_safe.ts index 510f77b5d172..57a999a164ae 100644 --- a/packages/core/src/log/shared_safe.ts +++ b/packages/core/src/log/shared_safe.ts @@ -1,4 +1,6 @@ import type { TransformableInfo } from "logform"; +import type Transport from "winston-transport"; +import { type LogContainer } from "./traits.js"; export const timestampFormatShort = "HH:mm:ss.SSS"; export const timestampPaddingShort = " ".repeat( @@ -143,3 +145,26 @@ export function messageRecordToLines(message: MessageRecord): string[] { .map((line) => line.trimEnd()) ); } +export interface LogConfig { + enabled: boolean; + level: string | number; + transports: Transport[]; + logToFile: boolean; + maxFiles: number; + nodeFilter?: number[]; + filename: string; + forceConsole: boolean; +} + +/** @internal */ +export const nonUndefinedLogConfigKeys = [ + "enabled", + "level", + "transports", + "logToFile", + "maxFiles", + "filename", + "forceConsole", +] as const; + +export type LogFactory = (config?: Partial) => LogContainer; diff --git a/packages/core/src/log/traits.ts b/packages/core/src/log/traits.ts index 1c1a5602a442..e269d7fc8889 100644 --- a/packages/core/src/log/traits.ts +++ b/packages/core/src/log/traits.ts @@ -1,17 +1,26 @@ -import { type LogContext, type ZWaveLogInfo } from "./shared_safe.js"; +import { + type LogConfig, + type LogContext, + type ZWaveLogInfo, +} from "./shared_safe.js"; export interface LogVisibility { isLoglevelVisible(loglevel: string): boolean; isNodeLoggingVisible(nodeId: number): boolean; } -export interface GetLogger { - getLogger(label: string): ZWaveLogger; +export interface GetLogger { + getLogger( + label: string, + ): ZWaveLogger; } export interface ZWaveLogger { log: (info: ZWaveLogInfo) => void; } -export type LogContainer = - & GetLogger - & LogVisibility; + +export interface LogContainer extends GetLogger, LogVisibility { + updateConfiguration(config: Partial): void; + getConfiguration(): LogConfig; + destroy(): void; +} diff --git a/packages/eslint-plugin/src/rules/no-forbidden-imports.ts b/packages/eslint-plugin/src/rules/no-forbidden-imports.ts index 9d3cec86221d..af52099b6764 100644 --- a/packages/eslint-plugin/src/rules/no-forbidden-imports.ts +++ b/packages/eslint-plugin/src/rules/no-forbidden-imports.ts @@ -10,6 +10,7 @@ const whitelistedImports = [ "fflate", "dayjs", "nrf-intel-hex", + "triple-beam", "alcalzone-shared/arrays", "alcalzone-shared/async", "alcalzone-shared/comparable", diff --git a/packages/serial/src/log/Logger.ts b/packages/serial/src/log/Logger.ts index 344db709082e..e8c0954483e8 100644 --- a/packages/serial/src/log/Logger.ts +++ b/packages/serial/src/log/Logger.ts @@ -1,6 +1,6 @@ import { type DataDirection, - type ZWaveLogContainer, + type LogContainer, ZWaveLoggerBase, getDirectionPrefix, } from "@zwave-js/core"; @@ -13,7 +13,7 @@ import { } from "./Logger_safe.js"; export class SerialLogger extends ZWaveLoggerBase { - constructor(loggers: ZWaveLogContainer) { + constructor(loggers: LogContainer) { super(loggers, SERIAL_LABEL); } diff --git a/packages/serial/src/mock/MockPort.ts b/packages/serial/src/mock/MockPort.ts index 17f2756acc7a..faa3b8829600 100644 --- a/packages/serial/src/mock/MockPort.ts +++ b/packages/serial/src/mock/MockPort.ts @@ -1,4 +1,4 @@ -import { ZWaveLogContainer } from "@zwave-js/core"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import type { UnderlyingSink, UnderlyingSource } from "node:stream/web"; import { type ZWaveSerialBindingFactory, @@ -74,7 +74,7 @@ export async function createAndOpenMockedZWaveSerialPort(): Promise<{ const port = new MockPort(); const factory = new ZWaveSerialStreamFactory( port.factory(), - new ZWaveLogContainer({ enabled: false }), + createZWaveLogContainer({ enabled: false }), ); const serial = await factory.createStream(); return { port, serial }; diff --git a/packages/serial/src/serialport/ZWaveSerialStream.ts b/packages/serial/src/serialport/ZWaveSerialStream.ts index 480299fb8570..aa7a00778e04 100644 --- a/packages/serial/src/serialport/ZWaveSerialStream.ts +++ b/packages/serial/src/serialport/ZWaveSerialStream.ts @@ -9,7 +9,7 @@ // 0 --> --> Parsers --> read // 1 └─────────────────┘ └─────────────────┘ └── -import { type ZWaveLogContainer } from "@zwave-js/core"; +import { type LogContainer } from "@zwave-js/core"; import { noop } from "@zwave-js/shared"; import type { ReadableWritablePair, @@ -17,7 +17,6 @@ import type { UnderlyingSource, } from "node:stream/web"; import { SerialLogger } from "../log/Logger.js"; -import { type SerialLogContext } from "../log/Logger_safe.js"; import { MessageHeaders } from "../message/MessageHeaders.js"; import { type ZWaveSerialFrame } from "../parsers/ZWaveSerialFrame.js"; import { ZWaveSerialParser } from "../plumbing/ZWaveSerialParser.js"; @@ -44,7 +43,7 @@ export function isZWaveSerialBindingFactory( export class ZWaveSerialStreamFactory { constructor( binding: ZWaveSerialBindingFactory, - loggers: ZWaveLogContainer, + loggers: LogContainer, ) { this.binding = binding; this.logger = new SerialLogger(loggers); diff --git a/packages/serial/src/zniffer/ZnifferSerialStream.ts b/packages/serial/src/zniffer/ZnifferSerialStream.ts index 93123bc04dc8..ad054b819c37 100644 --- a/packages/serial/src/zniffer/ZnifferSerialStream.ts +++ b/packages/serial/src/zniffer/ZnifferSerialStream.ts @@ -9,7 +9,7 @@ // 0 --> --> Parsers --> read // 1 └─────────────────┘ └─────────────────┘ └── -import { type ZWaveLogContainer } from "@zwave-js/core"; +import { type LogContainer } from "@zwave-js/core"; import { noop } from "@zwave-js/shared"; import type { ReadableWritablePair, @@ -17,7 +17,6 @@ import type { UnderlyingSource, } from "node:stream/web"; import { SerialLogger } from "../log/Logger.js"; -import { type SerialLogContext } from "../log/Logger_safe.js"; import { ZnifferParser } from "../parsers/ZnifferParser.js"; import { type ZnifferSerialFrame } from "../parsers/ZnifferSerialFrame.js"; import { type ZWaveSerialBindingFactory } from "../serialport/ZWaveSerialStream.js"; @@ -26,7 +25,7 @@ import { type ZWaveSerialBindingFactory } from "../serialport/ZWaveSerialStream. export class ZnifferSerialStreamFactory { constructor( binding: ZWaveSerialBindingFactory, - loggers: ZWaveLogContainer, + loggers: LogContainer, ) { this.binding = binding; this.logger = new SerialLogger(loggers); diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 848ff706913e..e10824335e74 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -61,6 +61,7 @@ import { EncapsulationFlags, type HostIDs, type LogConfig, + type LogContainer, type LogNodeOptions, MAX_SUPERVISION_SESSION_ID, MAX_TRANSPORT_SERVICE_SESSION_ID, @@ -91,7 +92,6 @@ import { type ValueMetadata, ZWaveError, ZWaveErrorCodes, - ZWaveLogContainer, deserializeCacheValue, generateECDHKeyPair, getCCName, @@ -208,7 +208,7 @@ import path from "pathe"; import { PACKAGE_NAME, PACKAGE_VERSION } from "../_version.js"; import { ZWaveController } from "../controller/Controller.js"; import { InclusionState, RemoveNodeReason } from "../controller/Inclusion.js"; -import { type DriverLogContext, DriverLogger } from "../log/Driver.js"; +import { DriverLogger } from "../log/Driver.js"; import type { Endpoint } from "../node/Endpoint.js"; import type { ZWaveNode } from "../node/Node.js"; import { @@ -709,23 +709,9 @@ export class Driver extends TypedEventTarget this.updateUserAgent(this._options.userAgent); } - // Initialize logging - this._logContainer = new ZWaveLogContainer(this._options.logConfig); - this._driverLog = new DriverLogger(this, this._logContainer); - this._controllerLog = new ControllerLogger(this._logContainer); - // Initialize the cache this.cacheDir = this._options.storage.cacheDir; - // Initialize config manager - this.configManager = new ConfigManager({ - logContainer: this._logContainer, - deviceConfigPriorityDir: - this._options.storage.deviceConfigPriorityDir, - deviceConfigExternalDir: - this._options.storage.deviceConfigExternalDir, - }); - const self = this; this.messageEncodingContext = { getHighestSecurityClass: (nodeId) => @@ -916,7 +902,12 @@ export class Driver extends TypedEventTarget return this._networkCache; } - public readonly configManager: ConfigManager; + // This is set during `start()` and should not be accessed before + private _configManager!: ConfigManager; + public get configManager(): ConfigManager { + return this._configManager; + } + public get configVersion(): string { return ( this.configManager?.configVersion @@ -927,14 +918,17 @@ export class Driver extends TypedEventTarget ); } - private _logContainer: ZWaveLogContainer; - private _driverLog: DriverLogger; + // This is set during `start()` and should not be accessed before + private _logContainer!: LogContainer; + // This is set during `start()` and should not be accessed before + private _driverLog!: DriverLogger; /** @internal */ public get driverLog(): DriverLogger { return this._driverLog; } - private _controllerLog: ControllerLogger; + // This is set during `start()` and should not be accessed before + private _controllerLog!: ControllerLogger; /** @internal */ public get controllerLog(): ControllerLogger { return this._controllerLog; @@ -1333,8 +1327,24 @@ export class Driver extends TypedEventTarget ?? (await import("@zwave-js/serial/bindings/node")).serial, db: this._options.host?.db ?? (await import("@zwave-js/core/bindings/db/jsonl")).db, + log: this._options.host?.log + ?? (await import("@zwave-js/core/bindings/log/node")).log, }; + // Initialize logging + this._logContainer = this.bindings.log(this._options.logConfig); + this._driverLog = new DriverLogger(this, this._logContainer); + this._controllerLog = new ControllerLogger(this._logContainer); + + // Initialize config manager + this._configManager = new ConfigManager({ + logContainer: this._logContainer, + deviceConfigPriorityDir: + this._options.storage.deviceConfigPriorityDir, + deviceConfigExternalDir: + this._options.storage.deviceConfigExternalDir, + }); + const spOpenPromise = createDeferredPromise(); // Log which version is running diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index 2f145c3c398f..d46e8a8faebe 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -1,6 +1,7 @@ import type { FileSystem as LegacyFileSystemBindings, LogConfig, + LogFactory, LongRangeChannel, RFRegion, } from "@zwave-js/core"; @@ -142,6 +143,11 @@ export interface ZWaveOptions { * Specifies which bindings are used to interact with the database used to store the cache. */ db?: DatabaseFactory; + + /** + * Specifies the logging implementation to be used + */ + log?: LogFactory; }; storage: { diff --git a/packages/zwave-js/src/lib/log/Driver.ts b/packages/zwave-js/src/lib/log/Driver.ts index e6085471ed9b..ff4140bbdb97 100644 --- a/packages/zwave-js/src/lib/log/Driver.ts +++ b/packages/zwave-js/src/lib/log/Driver.ts @@ -5,9 +5,9 @@ import { } from "@zwave-js/cc"; import { type DataDirection, + type LogContainer, type LogContext, MessagePriority, - type ZWaveLogContainer, ZWaveLoggerBase, getDirectionPrefix, messageRecordToLines, @@ -33,7 +33,7 @@ export interface DriverLogContext extends LogContext<"driver"> { export class DriverLogger extends ZWaveLoggerBase { constructor( private readonly driver: Driver, - loggers: ZWaveLogContainer, + loggers: LogContainer, ) { super(loggers, DRIVER_LABEL); } diff --git a/packages/zwave-js/src/lib/log/Zniffer.ts b/packages/zwave-js/src/lib/log/Zniffer.ts index d5ffea080e41..8d34cac861aa 100644 --- a/packages/zwave-js/src/lib/log/Zniffer.ts +++ b/packages/zwave-js/src/lib/log/Zniffer.ts @@ -5,10 +5,10 @@ import { } from "@zwave-js/cc"; import { type DataDirection, + type LogContainer, type LogContext, type MessageOrCCLogEntry, type RSSI, - type ZWaveLogContainer, ZWaveLoggerBase, getDirectionPrefix, messageRecordToLines, @@ -37,7 +37,7 @@ export interface ZnifferLogContext extends LogContext<"zniffer"> { export class ZnifferLogger extends ZWaveLoggerBase { constructor( private readonly zniffer: Zniffer, - loggers: ZWaveLogContainer, + loggers: LogContainer, ) { super(loggers, ZNIFFER_LABEL); } diff --git a/packages/zwave-js/src/lib/zniffer/Zniffer.ts b/packages/zwave-js/src/lib/zniffer/Zniffer.ts index 2c4efb26756b..9d2cefb1ca75 100644 --- a/packages/zwave-js/src/lib/zniffer/Zniffer.ts +++ b/packages/zwave-js/src/lib/zniffer/Zniffer.ts @@ -12,6 +12,7 @@ import { type FrameType, type HostIDs, type LogConfig, + type LogContainer, MPDUHeaderType, type MaybeNotKnown, NODE_ID_BROADCAST, @@ -25,7 +26,6 @@ import { type UnknownZWaveChipType, ZWaveError, ZWaveErrorCodes, - ZWaveLogContainer, ZnifferRegion, ZnifferRegionLegacy, getChipTypeAndVersion, @@ -76,7 +76,7 @@ import { createDeferredPromise, } from "alcalzone-shared/deferred-promise"; import { type ZWaveOptions } from "../driver/ZWaveOptions.js"; -import { type ZnifferLogContext, ZnifferLogger } from "../log/Zniffer.js"; +import { ZnifferLogger } from "../log/Zniffer.js"; import { type CorruptedFrame, type Frame, @@ -212,10 +212,6 @@ export class Zniffer extends TypedEventTarget { ); } - // Initialize logging - this._logContainer = new ZWaveLogContainer(options.logConfig); - this.znifferLog = new ZnifferLogger(this, this._logContainer); - this._options = options; this._active = false; @@ -314,8 +310,10 @@ export class Zniffer extends TypedEventTarget { return this._supportedFrequencies; } - private _logContainer: ZWaveLogContainer; - private znifferLog: ZnifferLogger; + // This is set during `start()` and should not be accessed before + private _logContainer!: LogContainer; + // This is set during `start()` and should not be accessed before + private znifferLog!: ZnifferLogger; /** The security managers for each node */ private securityManagers: Map { ?? (await import("@zwave-js/core/bindings/fs/node")).fs, serial: this._options.host?.serial ?? (await import("@zwave-js/serial/bindings/node")).serial, + log: this._options.host?.log + ?? (await import("@zwave-js/core/bindings/log/node")).log, }; + // Initialize logging + this._logContainer = this.bindings.log(this._options.logConfig); + this.znifferLog = new ZnifferLogger(this, this._logContainer); + // Open the serial port let binding: ZWaveSerialBindingFactory; if (typeof this.port === "string") { From fbf2f61c6e70319266997fc541d5c413cd34da95 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 31 Jan 2025 10:27:45 +0100 Subject: [PATCH 3/3] fix: tests --- packages/config/src/ConfigManager.test.ts | 9 +++++---- packages/core/src/log/Controller.test.ts | 5 +++-- packages/serial/src/serialport/ZWaveSerialStream.test.ts | 4 ++-- packages/zwave-js/src/lib/log/Driver.test.ts | 4 ++-- .../zwave-js/src/lib/test/driver/SerialLogger.test.ts | 8 +++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/config/src/ConfigManager.test.ts b/packages/config/src/ConfigManager.test.ts index 01305cca3bff..c646d5fed28e 100644 --- a/packages/config/src/ConfigManager.test.ts +++ b/packages/config/src/ConfigManager.test.ts @@ -1,5 +1,6 @@ -import { ZWaveLogContainer } from "@zwave-js/core"; +import { type LogContainer } from "@zwave-js/core"; import { fs } from "@zwave-js/core/bindings/fs/node"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { pathExists } from "@zwave-js/shared"; import fsp from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -13,7 +14,7 @@ import { syncExternalConfigDir } from "./utils.js"; interface LocalTestContext { context: { tempDir: string; - logContainer: ZWaveLogContainer; + logContainer: LogContainer; logger: ConfigLogger; }; } @@ -28,7 +29,7 @@ const test = baseTest.extend({ const tempDir = path.join(tmpdir(), "zwavejs_test"); await fsp.mkdir(tempDir, { recursive: true }); - const logContainer = new ZWaveLogContainer({ enabled: false }); + const logContainer = createZWaveLogContainer({ enabled: false }); const logger = new ConfigLogger(logContainer); // Run tests @@ -213,7 +214,7 @@ async function testDeviceConfigPriorityDir( // And load the file const cm = new ConfigManager({ deviceConfigPriorityDir: priorityDir, - logContainer: new ZWaveLogContainer({ enabled: false }), + logContainer: createZWaveLogContainer({ enabled: false }), }); await cm.loadAll(); diff --git a/packages/core/src/log/Controller.test.ts b/packages/core/src/log/Controller.test.ts index 538b2e863939..3230b98fa348 100644 --- a/packages/core/src/log/Controller.test.ts +++ b/packages/core/src/log/Controller.test.ts @@ -1,3 +1,4 @@ +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { beforeEach, test as baseTest } from "vitest"; import { CommandClasses } from "../definitions/CommandClasses.js"; import { InterviewStage } from "../definitions/InterviewStage.js"; @@ -7,7 +8,7 @@ import { assertMessage, } from "../test/SpyTransport.js"; import { ControllerLogger } from "./Controller.js"; -import { ZWaveLogContainer, createDefaultTransportFormat } from "./shared.js"; +import { createDefaultTransportFormat } from "./shared.js"; // Extend the test conte @@ -25,7 +26,7 @@ const test = baseTest.extend({ const spyTransport = new SpyTransport(); spyTransport.format = createDefaultTransportFormat(true, true); const controllerLogger = new ControllerLogger( - new ZWaveLogContainer({ + createZWaveLogContainer({ transports: [spyTransport], }), ); diff --git a/packages/serial/src/serialport/ZWaveSerialStream.test.ts b/packages/serial/src/serialport/ZWaveSerialStream.test.ts index 3f18e4c5b071..85fd2ef28411 100644 --- a/packages/serial/src/serialport/ZWaveSerialStream.test.ts +++ b/packages/serial/src/serialport/ZWaveSerialStream.test.ts @@ -1,4 +1,4 @@ -import { ZWaveLogContainer } from "@zwave-js/core"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { Bytes } from "@zwave-js/shared"; import { isUint8Array } from "node:util/types"; import { @@ -35,7 +35,7 @@ const test = baseTest.extend({ context.port = port; context.factory = new ZWaveSerialStreamFactory( port.factory(), - new ZWaveLogContainer({ enabled: false }), + createZWaveLogContainer({ enabled: false }), ); // Run tests diff --git a/packages/zwave-js/src/lib/log/Driver.test.ts b/packages/zwave-js/src/lib/log/Driver.test.ts index b77d12fe98e6..fce9c982b906 100644 --- a/packages/zwave-js/src/lib/log/Driver.test.ts +++ b/packages/zwave-js/src/lib/log/Driver.test.ts @@ -1,9 +1,9 @@ import { MessagePriority, - ZWaveLogContainer, createDefaultTransportFormat, getDirectionPrefix, } from "@zwave-js/core"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { SpyTransport, assertLogInfo, @@ -43,7 +43,7 @@ const test = baseTest.extend({ spyTransport.format = createDefaultTransportFormat(true, true); const driverLogger = new DriverLogger( driver, - new ZWaveLogContainer({ + createZWaveLogContainer({ transports: [spyTransport], }), ); diff --git a/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts b/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts index 3151423e8e6e..54e91ede7b0a 100644 --- a/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts +++ b/packages/zwave-js/src/lib/test/driver/SerialLogger.test.ts @@ -1,7 +1,5 @@ -import { - ZWaveLogContainer, - createDefaultTransportFormat, -} from "@zwave-js/core"; +import { createDefaultTransportFormat } from "@zwave-js/core"; +import { log as createZWaveLogContainer } from "@zwave-js/core/bindings/log/node"; import { SpyTransport, assertMessage } from "@zwave-js/core/test"; import { SerialLogger } from "@zwave-js/serial"; import { Bytes } from "@zwave-js/shared/safe"; @@ -24,7 +22,7 @@ const test = baseTest.extend({ const spyTransport = new SpyTransport(); spyTransport.format = createDefaultTransportFormat(true, true); const serialLogger = new SerialLogger( - new ZWaveLogContainer({ + createZWaveLogContainer({ transports: [spyTransport], }), );