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

Commit 1bb84c0

Browse files
author
Jay Logue
authored
Merge pull request #556 from openweave/feature/improved-python-logging
Improved logging in python
2 parents 80a2ffe + b021b99 commit 1bb84c0

File tree

5 files changed

+345
-55
lines changed

5 files changed

+345
-55
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: 144 additions & 7 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,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
97147
class 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

Comments
 (0)