import { CommandClasses , DataDirection , getDirectionPrefix , getNodeTag , LogContext , NodeLogContext , tagify , ValueAddedArgs , ValueID , ValueLogContext , ValueNotificationArgs , ValueRemovedArgs , ValueUpdatedArgs , ZWaveLogContainer , ZWaveLoggerBase , } from "@zwave-js/core" ; import { isObject } from "alcalzone-shared/typeguards" ; import type { ZWaveNode } from "../node/Node" ; import { InterviewStage } from "../node/Types" ; export const CONTROLLER_LABEL = "CNTRLR" ; const CONTROLLER_LOGLEVEL = "info" ; const VALUE_LOGLEVEL = "debug" ; interface LogNodeOptions { message : string ; level ? : "debug" | "verbose" | "warn" | "error" ; direction ? : DataDirection ; endpoint ? : number ; } export type ControllerLogContext = LogContext < "controller" > ; export type ControllerNodeLogContext = ControllerLogContext & NodeLogContext & { endpoint ? : number ; direction : string ; } ; export type ControllerValueLogContext = ControllerLogContext & ValueLogContext & { direction ? : string ; change ? : "added" | "updated" | "removed" | "notification" ; internal ? : boolean ; } ; export type LogValueArgs < T > = T & { nodeId : number ; internal ? : boolean ; } ; export class ControllerLogger extends ZWaveLoggerBase < ControllerLogContext > { constructor ( loggers ) { super ( loggers , CONTROLLER_LABEL ) ; } private isValueLogVisible ( ) { return this . container . isLoglevelVisible ( VALUE_LOGLEVEL ) ; } private isControllerLogVisible ( ) { return this . container . isLoglevelVisible ( CONTROLLER_LOGLEVEL ) ; } param msg public print ( message , level ? ) { const actualLevel = level || CONTROLLER_LOGLEVEL ; if ( ! this . container . isLoglevelVisible ( actualLevel ) ) return ; this . logger . log ( { level : actualLevel , message , direction : getDirectionPrefix ( "none" ) , context : { source : "controller" , type : "controller" } } ) ; } param message param level public logNode ( nodeId , message , level ? ) ; param node param options public logNode ( nodeId , options ) ; public logNode ( nodeId , messageOrOptions , logLevel ? ) { if ( typeof messageOrOptions === "string" ) { return this . logNode ( nodeId , { message : messageOrOptions , level : logLevel } ) ; } const { level , message , direction , endpoint } = messageOrOptions ; const actualLevel = level || CONTROLLER_LOGLEVEL ; if ( ! this . container . isLoglevelVisible ( actualLevel ) ) return ; if ( ! this . container . shouldLogNode ( nodeId ) ) return ; const context = { nodeId , source : "controller" , type : "node" , direction : direction || "none" } ; if ( endpoint ) context . endpoint = endpoint ; this . logger . log ( { level : actualLevel , primaryTags : tagify ( [ getNodeTag ( nodeId ) ] ) , message , secondaryTags : endpoint ? tagify ( [ `Endpoint ${ endpoint }` ] ) : undefined , direction : getDirectionPrefix ( direction || "none" ) , context } ) ; } valueEventPrefixes = Object . freeze ( { added : "+" , updated : "~" , removed : "-" , notification : "!" } ) ; private formatValue ( value ) { if ( isObject ( value ) ) return JSON . stringify ( value ) ; if ( typeof value !== "string" ) return String ( value ) ; return `"${ value }"` ; } /** Prints a log message for an added, updated or removed value */ public value ( change , args ) ; public value ( change , args ) ; public value ( change , args ) ; public value ( change , args ) ; public value ( change , args ) { if ( ! this . isValueLogVisible ( ) ) return ; if ( ! this . container . shouldLogNode ( args . nodeId ) ) return ; const context = { nodeId : args . nodeId , change , commandClass : args . commandClass , internal : args . internal , property : args . property , source : "controller" , type : "value" } ; const primaryTags = [ getNodeTag ( args . nodeId ) , this . valueEventPrefixes [ change ] , CommandClasses [ args . commandClass ] , ] ; const secondaryTags = [ ] ; if ( args . endpoint != undefined ) { context . endpoint = args . endpoint ; secondaryTags . push ( `Endpoint ${ args . endpoint }` ) ; } if ( args . internal === true ) { secondaryTags . push ( "internal" ) ; } let message = args . property . toString ( ) ; if ( args . propertyKey != undefined ) { context . propertyKey = args . propertyKey ; message += `[${ args . propertyKey }]` ; } switch ( change ) { case "added" : message += `: ${ this . formatValue ( ( args as unknown as ValueAddedArgs ) . newValue ) }` ; break ; case "updated" : { const _args = args as unknown as ValueUpdatedArgs ; message += `: ${ this . formatValue ( _args . prevValue ) } => ${ this . formatValue ( _args . newValue ) }` ; break ; } case "removed" : message += ` (was ${ this . formatValue ( ( args as unknown as ValueRemovedArgs ) . prevValue ) })` ; break ; case "notification" : message += `: ${ this . formatValue ( ( args as unknown as ValueNotificationArgs ) . value ) }` ; break ; } this . logger . log ( { level : VALUE_LOGLEVEL , primaryTags : tagify ( primaryTags ) , secondaryTags : tagify ( secondaryTags ) , message , direction : getDirectionPrefix ( "none" ) , context } ) ; } /** Prints a log message for updated metadata of a value id */ public metadataUpdated ( args ) { if ( ! this . isValueLogVisible ( ) ) return ; if ( ! this . container . shouldLogNode ( args . nodeId ) ) return ; const context = { nodeId : args . nodeId , commandClass : args . commandClass , internal : args . internal , property : args . property , source : "controller" , type : "value" } ; const primaryTags = [ getNodeTag ( args . nodeId ) , CommandClasses [ args . commandClass ] , ] ; const secondaryTags = [ ] ; if ( args . endpoint != undefined ) { context . endpoint = args . endpoint ; secondaryTags . push ( `Endpoint ${ args . endpoint }` ) ; } if ( args . internal === true ) { secondaryTags . push ( "internal" ) ; } let message = args . property . toString ( ) ; if ( args . propertyKey != undefined ) { context . propertyKey = args . propertyKey ; message += `[${ args . propertyKey }]` ; } message += ": metadata updated" ; this . logger . log ( { level : VALUE_LOGLEVEL , primaryTags : tagify ( primaryTags ) , secondaryTags : tagify ( secondaryTags ) , message , direction : getDirectionPrefix ( "none" ) , context } ) ; } /** Logs the interview progress of a node */ public interviewStage ( node ) { if ( ! this . isControllerLogVisible ( ) ) return ; if ( ! this . container . shouldLogNode ( node . id ) ) return ; this . logger . log < ControllerNodeLogContext > ( { level : CONTROLLER_LOGLEVEL , primaryTags : tagify ( [ getNodeTag ( node . id ) ] ) , message : node . interviewStage === InterviewStage . Complete ? "Interview completed" : `Interview stage completed: ${ InterviewStage [ node . interviewStage ] }` , direction : getDirectionPrefix ( "none" ) , context : { nodeId : node . id , source : "controller" , type : "node" , direction : "none" } } ) ; } /** Logs the interview progress of a node */ public interviewStart ( node ) { if ( ! this . isControllerLogVisible ( ) ) return ; if ( ! this . container . shouldLogNode ( node . id ) ) return ; const message = `Beginning interview - last completed stage: ${ InterviewStage [ node . interviewStage ] }` ; this . logger . log < ControllerNodeLogContext > ( { level : CONTROLLER_LOGLEVEL , primaryTags : tagify ( [ getNodeTag ( node . id ) ] ) , message , direction : getDirectionPrefix ( "none" ) , context : { nodeId : node . id , source : "controller" , type : "node" , direction : "none" } } ) ; } }