3636import glob
3737import platform
3838import ast
39+ import logging
3940from threading import Thread , Lock , Event
4041from ctypes import *
4142from .WeaveUtility import WeaveUtility
@@ -90,20 +91,115 @@ def __init__(self, profileId, statusCode, systemErrorCode, msg=None):
9091 def __str__ (self ):
9192 return "Device Error: " + self .message
9293
94+ class LogCategory (object ):
95+ '''Debug logging categories used by openweave.'''
96+
97+ # NOTE: These values must correspond to those used in the openweave C++ code.
98+ Disabled = 0
99+ Error = 1
100+ Progress = 2
101+ Detail = 3
102+ Retain = 4
103+
104+ @staticmethod
105+ def categoryToLogLevel (cat ):
106+ if cat == LogCategory .Error :
107+ return logging .ERROR
108+ elif cat == LogCategory .Progress :
109+ return logging .INFO
110+ elif cat == LogCategory .Detail :
111+ return logging .DEBUG
112+ elif cat == LogCategory .Retain :
113+ return logging .CRITICAL
114+ else :
115+ return logging .NOTSET
116+
117+ class WeaveLogFormatter (logging .Formatter ):
118+ '''A custom logging.Formatter for logging openweave library messages.'''
119+ def __init__ (self , datefmt = None , logModule = True , logLevel = False , logTimestamp = False , logMSecs = True ):
120+ fmt = '%(message)s'
121+ if logModule :
122+ fmt = 'WEAVE:%(weave-module)s: ' + fmt
123+ if logLevel :
124+ fmt = '%(levelname)s:' + fmt
125+ if datefmt is not None or logTimestamp :
126+ fmt = '%(asctime)s ' + fmt
127+ super (WeaveLogFormatter , self ).__init__ (fmt = fmt , datefmt = datefmt )
128+ self .logMSecs = logMSecs
129+
130+ def formatTime (self , record , datefmt = None ):
131+ timestamp = record .__dict__ .get ('timestamp' , time .time ())
132+ timestamp = time .localtime (timestamp )
133+ if datefmt is None :
134+ datefmt = '%Y-%m-%d %H:%M:%S%z'
135+ timestampStr = time .strftime ('%Y-%m-%d %H:%M:%S%z' )
136+ if self .logMSecs :
137+ timestampUS = record .__dict__ .get ('timestamp-usec' , 0 )
138+ timestampStr = '%s.%03ld' % (timestampStr , timestampUS / 1000 )
139+ return timestampStr
140+
141+
93142_CompleteFunct = CFUNCTYPE (None , c_void_p , c_void_p )
94143_ErrorFunct = CFUNCTYPE (None , c_void_p , c_void_p , c_ulong , POINTER (DeviceStatusStruct ))
144+ _LogMessageFunct = CFUNCTYPE (None , c_int64 , c_int64 , c_char_p , c_uint8 , c_char_p )
95145
96146@_singleton
97147class WeaveStack (object ):
98- def __init__ (self ):
148+ def __init__ (self , installDefaultLogHandler = True ):
99149 self .networkLock = Lock ()
100150 self .completeEvent = Event ()
101151 self ._weaveStackLib = None
102152 self ._weaveDLLPath = None
103153 self .devMgr = None
104154 self .callbackRes = None
155+ self ._activeLogFunct = None
156+
157+ # Locate and load the openweave shared library.
158+ self ._loadLib ()
159+
160+ # Arrange to log output from the openweave library to a python logger object with the
161+ # name 'openweave.WeaveStack'. If desired, applications can override this behavior by
162+ # setting self.logger to a different python logger object, or by calling setLogFunct()
163+ # with their own logging function.
164+ self .logger = logging .getLogger (__name__ )
165+ self .setLogFunct (self .defaultLogFunct )
166+
167+ # Determine if there are already handlers installed for the logger. Python 3.5+
168+ # has a method for this; on older versions the check has to be done manually.
169+ if hasattr (self .logger , 'hasHandlers' ):
170+ hasHandlers = self .logger .hasHandlers ()
171+ else :
172+ hasHandlers = False
173+ logger = self .logger
174+ while logger is not None :
175+ if len (logger .handlers ) > 0 :
176+ hasHandlers = True
177+ break
178+ if not logger .propagate :
179+ break
180+ logger = logger .parent
105181
106- self ._InitLib ()
182+ # If a logging handler has not already been initialized for 'openweave.WeaveStack',
183+ # or any one of its parent loggers, automatically configure a handler to log to
184+ # stdout. This maintains compatibility with a number of applications which expect
185+ # openweave log output to go to stdout by default.
186+ #
187+ # This behavior can be overridden in a variety of ways:
188+ # - Initialize a different log handler before WeaveStack is initialized.
189+ # - Pass installDefaultLogHandler=False when initializing WeaveStack.
190+ # - Replace the StreamHandler on self.logger with a different handler object.
191+ # - Set a different Formatter object on the existing StreamHandler object.
192+ # - Reconfigure the existing WeaveLogFormatter object.
193+ # - Configure openweave to call an application-specific logging function by
194+ # calling self.setLogFunct().
195+ # - Call self.setLogFunct(None), which will configure the openweave library
196+ # to log directly to stdout, bypassing python altogether.
197+ #
198+ if installDefaultLogHandler and not hasHandlers :
199+ logHandler = logging .StreamHandler (stream = sys .stdout )
200+ logHandler .setFormatter (WeaveLogFormatter ())
201+ self .logger .addHandler (logHandler )
202+ self .logger .setLevel (logging .DEBUG )
107203
108204 def HandleComplete (appState , reqState ):
109205 self .callbackRes = True
@@ -117,6 +213,49 @@ def HandleError(appState, reqState, err, devStatusPtr):
117213 self .cbHandleError = _ErrorFunct (HandleError )
118214 self .blockingCB = None # set by other modules(BLE) that require service by thread while thread blocks.
119215
216+ # Initialize the openweave library
217+ res = self ._weaveStackLib .nl_Weave_Stack_Init ()
218+ if (res != 0 ):
219+ raise self ._weaveStack .ErrorToException (res )
220+
221+ @property
222+ def defaultLogFunct (self ):
223+ '''Returns a python callable which, when called, logs a message to the python logger object
224+ currently associated with the WeaveStack object.
225+ The returned function is suitable for passing to the setLogFunct() method.'''
226+ def logFunct (timestamp , timestampUSec , moduleName , logCat , message ):
227+ moduleName = WeaveUtility .CStringToString (moduleName )
228+ message = WeaveUtility .CStringToString (message )
229+ logLevel = LogCategory .categoryToLogLevel (logCat )
230+ msgAttrs = {
231+ 'weave-module' : moduleName ,
232+ 'timestamp' : timestamp ,
233+ 'timestamp-usec' : timestampUSec
234+ }
235+ self .logger .log (logLevel , message , extra = msgAttrs )
236+ return logFunct
237+
238+ def setLogFunct (self , logFunct ):
239+ '''Set the function used by the openweave library to log messages.
240+ The supplied object must be a python callable that accepts the following
241+ arguments:
242+ timestamp (integer)
243+ timestampUS (integer)
244+ module name (encoded UTF-8 string)
245+ log category (integer)
246+ message (encoded UTF-8 string)
247+ Specifying None configures the openweave library to log directly to stdout.'''
248+ if logFunct is None :
249+ logFunct = 0
250+ if not isinstance (logFunct , _LogMessageFunct ):
251+ logFunct = _LogMessageFunct (logFunct )
252+ with self .networkLock :
253+ # NOTE: WeaveStack must hold a reference to the CFUNCTYPE object while it is
254+ # set. Otherwise it may get garbage collected, and logging calls from the
255+ # openweave library will fail.
256+ self ._activeLogFunct = logFunct
257+ self ._weaveStackLib .nl_Weave_Stack_SetLogFunct (logFunct )
258+
120259 def Shutdown (self ):
121260 self ._weaveStack .Call (
122261 lambda : self ._dmLib .nl_Weave_Stack_Shutdown ()
@@ -208,7 +347,7 @@ def _AllDirsToRoot(self, dir):
208347 break
209348 dir = parent
210349
211- def _InitLib (self ):
350+ def _loadLib (self ):
212351 if (self ._weaveStackLib == None ):
213352 self ._weaveStackLib = CDLL (self .LocateWeaveDLL ())
214353 self ._weaveStackLib .nl_Weave_Stack_Init .argtypes = [ ]
@@ -219,7 +358,5 @@ def _InitLib(self):
219358 self ._weaveStackLib .nl_Weave_Stack_StatusReportToString .restype = c_char_p
220359 self ._weaveStackLib .nl_Weave_Stack_ErrorToString .argtypes = [ c_uint32 ]
221360 self ._weaveStackLib .nl_Weave_Stack_ErrorToString .restype = c_char_p
222-
223- res = self ._weaveStackLib .nl_Weave_Stack_Init ()
224- if (res != 0 ):
225- raise self ._weaveStack .ErrorToException (res )
361+ self ._weaveStackLib .nl_Weave_Stack_SetLogFunct .argtypes = [ _LogMessageFunct ]
362+ self ._weaveStackLib .nl_Weave_Stack_SetLogFunct .restype = c_uint32
0 commit comments