Skip to content
This repository was archived by the owner on Dec 20, 2023. It is now read-only.

Commit 540be9b

Browse files
author
Jay Logue
committed
Improved logging in python
-- Reworked the way developer logging happens when using the openweave wrappers for python. With this change, all WeaveLog...() calls in C++ code result in a callback to a logging function in python. The default implementation of this function directs log messages to a standard python logger object associated with the python WeaveStack object. By default, the python logger is configured to output messages to stdout, in a format identical to that used by the C++ code. However, developers are free to reconfigure the logger object, or replace it altogether, giving them complete control over the handling of C++ generated logs in python applications. -- Added support for dynamic logging behavior. Modified the standard implementation of nl::Weave::Logging::Log() so that the default logging behavior can be overridden at runtime. Applications can now use the SetLogFunct() function to specify a pointer to a function to be called for every logged message. Setting the function pointer to NULL (the default value) results in the original behavior of logging to stdout via printf().
1 parent 70a44a7 commit 540be9b

File tree

4 files changed

+315
-53
lines changed

4 files changed

+315
-53
lines changed

src/device-manager/python/WeaveDeviceManager-ScriptBinding.cpp

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <errno.h>
3030
#include <unistd.h>
3131
#include <fcntl.h>
32+
#include <sys/time.h>
3233

3334
#include <SystemLayer/SystemLayer.h>
3435
#include <SystemLayer/SystemError.h>
@@ -69,6 +70,7 @@ using DeviceDescription::IdentifyDeviceCriteria;
6970
extern "C" {
7071
typedef void * (*GetBleEventCBFunct)(void);
7172
typedef void (*ConstructBytesArrayFunct)(const uint8_t *dataBuf, uint32_t dataLen);
73+
typedef void (*LogMessageFunct)(uint64_t time, uint64_t timeUS, const char *moduleName, uint8_t category, const char *msg);
7274
}
7375

7476
enum BleEventType
@@ -257,6 +259,7 @@ extern "C" {
257259
NL_DLL_EXPORT WEAVE_ERROR nl_Weave_Stack_Shutdown();
258260
NL_DLL_EXPORT const char *nl_Weave_Stack_ErrorToString(WEAVE_ERROR err);
259261
NL_DLL_EXPORT const char *nl_Weave_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode);
262+
NL_DLL_EXPORT void nl_Weave_Stack_SetLogFunct(LogMessageFunct logFunct);
260263
#if WEAVE_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL
261264
NL_DLL_EXPORT WEAVE_ERROR nl_Weave_WdmClient_Init();
262265
NL_DLL_EXPORT WEAVE_ERROR nl_Weave_WdmClient_Shutdown();
@@ -1217,14 +1220,93 @@ WEAVE_ERROR nl_Weave_Stack_Shutdown()
12171220

12181221
const char *nl_Weave_Stack_ErrorToString(WEAVE_ERROR err)
12191222
{
1220-
return nl::ErrorStr(err);
1223+
return nl::ErrorStr(err);
12211224
}
12221225

12231226
const char *nl_Weave_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode)
12241227
{
12251228
return nl::StatusReportStr(profileId, statusCode);
12261229
}
12271230

1231+
#if WEAVE_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
1232+
1233+
// A pointer to the python logging function.
1234+
static LogMessageFunct sLogMessageFunct = NULL;
1235+
1236+
// This function is called by the Weave logging code whenever a developer message
1237+
// is logged. It serves as glue to adapt the logging arguments to what is expected
1238+
// by the python code.
1239+
// NOTE that this function MUST be thread-safe.
1240+
static void LogMessageToPython(uint8_t module, uint8_t category, const char *msg, va_list ap)
1241+
{
1242+
if (IsCategoryEnabled(category))
1243+
{
1244+
// Capture the timestamp of the log message.
1245+
struct timeval tv;
1246+
gettimeofday(&tv, NULL);
1247+
1248+
// Get the module name
1249+
char moduleName[nlWeaveLoggingModuleNameLen + 1];
1250+
::nl::Weave::Logging::GetModuleName(moduleName, module);
1251+
1252+
// Format the log message into a dynamic memory buffer, growing the
1253+
// buffer as needed to fit the message.
1254+
char * msgBuf = NULL;
1255+
size_t msgBufSize = 0;
1256+
size_t msgSize = 0;
1257+
constexpr size_t kInitialBufSize = 120;
1258+
do
1259+
{
1260+
va_list apCopy;
1261+
va_copy(apCopy, ap);
1262+
1263+
msgBufSize = max(msgSize+1, kInitialBufSize);
1264+
msgBuf = (char *)realloc(msgBuf, msgBufSize);
1265+
if (msgBuf == NULL)
1266+
{
1267+
return;
1268+
}
1269+
1270+
int res = vsnprintf(msgBuf, msgBufSize, msg, apCopy);
1271+
if (res < 0)
1272+
{
1273+
return;
1274+
}
1275+
msgSize = (size_t)res;
1276+
1277+
va_end(apCopy);
1278+
} while (msgSize >= msgBufSize);
1279+
1280+
// Call the configured python logging function.
1281+
sLogMessageFunct((int64_t)tv.tv_sec, (int64_t)tv.tv_usec, moduleName, category, msgBuf);
1282+
1283+
// Release the message buffer.
1284+
free(msgBuf);
1285+
}
1286+
}
1287+
1288+
void nl_Weave_Stack_SetLogFunct(LogMessageFunct logFunct)
1289+
{
1290+
if (logFunct != NULL)
1291+
{
1292+
sLogMessageFunct = logFunct;
1293+
::nl::Weave::Logging::SetLogFunct(LogMessageToPython);
1294+
}
1295+
else
1296+
{
1297+
sLogMessageFunct = NULL;
1298+
::nl::Weave::Logging::SetLogFunct(NULL);
1299+
}
1300+
}
1301+
1302+
#else // WEAVE_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
1303+
1304+
void nl_Weave_Stack_SetLogFunct(LogMessageFunct logFunct)
1305+
{
1306+
}
1307+
1308+
#endif // WEAVE_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
1309+
12281310
#if WEAVE_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL
12291311
static void EngineEventCallback(void * const aAppState,
12301312
SubscriptionEngine::EventID aEvent,

src/device-manager/python/openweave/WeaveStack.py

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import glob
3737
import platform
3838
import ast
39+
import logging
3940
from threading import Thread, Lock, Event
4041
from ctypes import *
4142
from .WeaveUtility import WeaveUtility
@@ -90,8 +91,57 @@ 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
97147
class WeaveStack(object):
@@ -102,8 +152,33 @@ def __init__(self):
102152
self._weaveDLLPath = None
103153
self.devMgr = None
104154
self.callbackRes = None
155+
self._activeLogFunct = None
105156

106-
self._InitLib()
157+
# Locate and load the openweave shared library.
158+
self._loadLib()
159+
160+
# By default, configure the openweave library to log to a python logger object with the
161+
# name 'openweave.WeaveStack'. If this logger has not already been initialized by
162+
# the application, automatically configure it to log to stdout.
163+
#
164+
# Applications can override this behavior in any one the following ways:
165+
# - Setting self.logger to a different python logger object.
166+
# - Replacing the StreamHandler on self.logger with a different handler object.
167+
# - Setting a different Formatter object on the existing StreamHandler object.
168+
# - Reconfiguring the existing LogMessageFormatter object, e.g. to enable logging
169+
# of timestamps.
170+
# - Configuring openwave to call an application-specific logging function by
171+
# calling self.setLogFunct().
172+
# - Calling self.setLogFunct(None), which will configure the openweave library
173+
# to log directly to stdout, bypassing python altogether.
174+
#
175+
self.logger = logging.getLogger(__name__)
176+
if len(self.logger.handlers) == 0:
177+
logHandler = logging.StreamHandler(stream=sys.stdout)
178+
logHandler.setFormatter(WeaveLogFormatter())
179+
self.logger.addHandler(logHandler)
180+
self.logger.setLevel(logging.DEBUG)
181+
self.setLogFunct(self.defaultLogFunct)
107182

108183
def HandleComplete(appState, reqState):
109184
self.callbackRes = True
@@ -117,6 +192,49 @@ def HandleError(appState, reqState, err, devStatusPtr):
117192
self.cbHandleError = _ErrorFunct(HandleError)
118193
self.blockingCB = None # set by other modules(BLE) that require service by thread while thread blocks.
119194

195+
# Initialize the openweave library
196+
res = self._weaveStackLib.nl_Weave_Stack_Init()
197+
if (res != 0):
198+
raise self._weaveStack.ErrorToException(res)
199+
200+
@property
201+
def defaultLogFunct(self):
202+
'''Returns a python callable which, when called, logs a message to the python logger object
203+
currently associated with the WeaveStack object.
204+
The returned function is suitable for passing to the setLogFunct() method.'''
205+
def logFunct(timestamp, timestampUSec, moduleName, logCat, message):
206+
moduleName = WeaveUtility.CStringToString(moduleName)
207+
message = WeaveUtility.CStringToString(message)
208+
logLevel = LogCategory.categoryToLogLevel(logCat)
209+
msgAttrs = {
210+
'weave-module' : moduleName,
211+
'timestamp' : timestamp,
212+
'timestamp-usec' : timestampUSec
213+
}
214+
self.logger.log(logLevel, message, extra=msgAttrs)
215+
return logFunct
216+
217+
def setLogFunct(self, logFunct):
218+
'''Set the function used by the openweave library to log messages.
219+
The supplied object must be a python callable that accepts the following
220+
arguments:
221+
timestamp (integer)
222+
timestampUS (integer)
223+
module name (encoded UTF-8 string)
224+
log category (integer)
225+
message (encoded UTF-8 string)
226+
Specifying None configures the openweave library to log directly to stdout.'''
227+
if logFunct is None:
228+
logFunct = 0
229+
if not isinstance(logFunct, _LogMessageFunct):
230+
logFunct = _LogMessageFunct(logFunct)
231+
with self.networkLock:
232+
# NOTE: WeaveStack must hold a reference to the CFUNCTYPE object while it is
233+
# set. Otherwise it may get garbage collected, and logging calls from the
234+
# openweave library will fail.
235+
self._activeLogFunct = logFunct
236+
self._weaveStackLib.nl_Weave_Stack_SetLogFunct(logFunct)
237+
120238
def Shutdown(self):
121239
self._weaveStack.Call(
122240
lambda: self._dmLib.nl_Weave_Stack_Shutdown()
@@ -208,7 +326,7 @@ def _AllDirsToRoot(self, dir):
208326
break
209327
dir = parent
210328

211-
def _InitLib(self):
329+
def _loadLib(self):
212330
if (self._weaveStackLib == None):
213331
self._weaveStackLib = CDLL(self.LocateWeaveDLL())
214332
self._weaveStackLib.nl_Weave_Stack_Init.argtypes = [ ]
@@ -219,7 +337,5 @@ def _InitLib(self):
219337
self._weaveStackLib.nl_Weave_Stack_StatusReportToString.restype = c_char_p
220338
self._weaveStackLib.nl_Weave_Stack_ErrorToString.argtypes = [ c_uint32 ]
221339
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)
340+
self._weaveStackLib.nl_Weave_Stack_SetLogFunct.argtypes = [ _LogMessageFunct ]
341+
self._weaveStackLib.nl_Weave_Stack_SetLogFunct.restype = c_uint32

0 commit comments

Comments
 (0)