import * as Sentry from "@sentry/node" ; import { DataDirection , getDirectionPrefix , LogContext , messageRecordToLines , tagify , ZWaveLogContainer , ZWaveLoggerBase , } from "@zwave-js/core" ; import { getEnumMemberName } from "@zwave-js/shared" ; import type { SortedList } from "alcalzone-shared/sorted-list" ; import type { CommandClass } from "../commandclass/CommandClass" ; import { isEncapsulatingCommandClass } from "../commandclass/EncapsulatingCommandClass" ; import { isCommandClassContainer } from "../commandclass/ICommandClassContainer" ; import type { Transaction } from "../driver/Transaction" ; import { FunctionType , MessagePriority , MessageType , } from "../message/Constants" ; import type { Message , ResponseRole } from "../message/Message" ; import { NodeStatus } from "../node/Types" ; export const DRIVER_LABEL = "DRIVER" ; const DRIVER_LOGLEVEL = "verbose" ; const SENDQUEUE_LOGLEVEL = "debug" ; export interface DriverLogContext extends LogContext < "driver" > { direction ? : DataDirection ; } export class DriverLogger extends ZWaveLoggerBase < DriverLogContext > { constructor ( loggers ) { super ( loggers , DRIVER_LABEL ) ; } private isDriverLogVisible ( ) { return this . container . isLoglevelVisible ( DRIVER_LOGLEVEL ) ; } private isSendQueueLogVisible ( ) { return this . container . isLoglevelVisible ( SENDQUEUE_LOGLEVEL ) ; } param msg public print ( message , level ? ) { const actualLevel = level || DRIVER_LOGLEVEL ; if ( ! this . container . isLoglevelVisible ( actualLevel ) ) return ; this . logger . log ( { level : actualLevel , message , direction : getDirectionPrefix ( "none" ) , context : { source : "driver" , direction : "none" } } ) ; } /**
     * Serializes a message that starts a transaction, i.e. a message that is sent and may expect a response
     */ public transaction ( transaction ) { if ( ! this . isDriverLogVisible ( ) ) return ; const { message } = transaction ; const secondaryTags = [ ] ; secondaryTags . push ( `P: ${ MessagePriority [ transaction . priority ] }` ) ; this . logMessage ( message , { secondaryTags , direction : "outbound" } ) ; } /** Logs information about a message that is received as a response to a transaction */ public transactionResponse ( message , originalTransaction , role ) { if ( ! this . isDriverLogVisible ( ) ) return ; this . logMessage ( message , { nodeId : originalTransaction ?. message ?. getNodeId ( ) , secondaryTags : [ role ] , direction : "inbound" } ) ; } public logMessage ( message , { nodeId , secondaryTags , direction = "none" , } = { } ) { if ( ! this . isDriverLogVisible ( ) ) return ; if ( nodeId == undefined ) nodeId = message . getNodeId ( ) ; if ( nodeId != undefined && ! this . container . shouldLogNode ( nodeId ) ) { return ; } const isCCContainer = isCommandClassContainer ( message ) ; const logEntry = message . toLogEntry ( ) ; let msg = [ tagify ( logEntry . tags ) ] ; if ( logEntry . message ) { msg . push ( ... messageRecordToLines ( logEntry . message ) . map ( ( line ) => ( isCCContainer ? "│ " : "  " ) + line ) ) ; } try { if ( isCommandClassContainer ( message ) ) { msg = msg . filter ( ( line ) => ! line . startsWith ( "│ payload:" ) ) ; let indent = 0 ; let cc = message . command ; while ( true ) { const isEncapCC = isEncapsulatingCommandClass ( cc ) ; const loggedCC = cc . toLogEntry ( ) ; msg . push ( " " . repeat ( indent * 2 ) + "└─" + tagify ( loggedCC . tags ) ) ; indent ++ ; if ( loggedCC . message ) { msg . push ( ... messageRecordToLines ( loggedCC . message ) . map ( ( line ) => `${ " " . repeat ( indent * 2 ) }${ isEncapCC ? "│ " : "  " }${ line }` ) ) ; } if ( isEncapsulatingCommandClass ( cc ) ) { cc = cc . encapsulated ; } else { break ; } } } this . logger . log ( { level : DRIVER_LOGLEVEL , secondaryTags : secondaryTags && secondaryTags . length > 0 ? tagify ( secondaryTags ) : undefined , message : msg , direction : getDirectionPrefix ( direction ) , context : { source : "driver" , direction } } ) ; } catch ( e ) { try { Sentry . captureException ( e ) ; } catch { } } } /** Logs whats currently in the driver's send queue */ public sendQueue ( queue ) { if ( ! this . isSendQueueLogVisible ( ) ) return ; let message = "Send queue:" ; if ( queue . length > 0 ) { for ( const trns of queue ) { const node = trns . message . getNodeUnsafe ( ) ; const prefix = trns . message . type === MessageType . Request ? "[REQ]" : "[RES]" ; const postfix = node != undefined ? ` [Node ${ node . id }, ${ getEnumMemberName ( NodeStatus , node . status ) }]` : "" ; const command = isCommandClassContainer ( trns . message ) ? ` (${ trns . message . command . constructor . name })` : "" ; message += `\n· ${ prefix } ${ FunctionType [ trns . message . functionType ] }${ command }${ postfix }` ; } } else { message += " (empty)" ; } this . logger . log ( { level : SENDQUEUE_LOGLEVEL , message , secondaryTags : `(${ queue . length } message${ queue . length === 1 ? "" : "s" })` , direction : getDirectionPrefix ( "none" ) , context : { source : "driver" , direction : "none" } } ) ; } }