Properly catch stdout and stderr and redirect it to qmessagelogger
This commit is contained in:
parent
85f742a38d
commit
15eead0976
@ -41,7 +41,23 @@ static void PyNymeaLoggingHandler_dealloc(PyNymeaLoggingHandler * self)
|
|||||||
Py_TYPE(self)->tp_free(self);
|
Py_TYPE(self)->tp_free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject * PyNymeaLoggingHandler_log(PyNymeaLoggingHandler* self, PyObject* args)
|
static PyObject * PyNymeaLoggingHandler_info(PyNymeaLoggingHandler* self, PyObject* args)
|
||||||
|
{
|
||||||
|
QStringList strings;
|
||||||
|
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||||
|
PyObject *obj = PyTuple_GET_ITEM(args, i);
|
||||||
|
PyObject* repr = PyObject_Repr(obj);
|
||||||
|
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||||
|
const char *bytes = PyBytes_AS_STRING(str);
|
||||||
|
Py_XDECREF(repr);
|
||||||
|
Py_XDECREF(str);
|
||||||
|
strings.append(bytes);
|
||||||
|
}
|
||||||
|
qCInfo(QLoggingCategory(self->category)).noquote() << strings.join(' ');
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject * PyNymeaLoggingHandler_debug(PyNymeaLoggingHandler* self, PyObject* args)
|
||||||
{
|
{
|
||||||
QStringList strings;
|
QStringList strings;
|
||||||
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||||
@ -73,9 +89,12 @@ static PyObject * PyNymeaLoggingHandler_warn(PyNymeaLoggingHandler* self, PyObje
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef PyNymeaLoggingHandler_methods[] = {
|
static PyMethodDef PyNymeaLoggingHandler_methods[] = {
|
||||||
{ "log", (PyCFunction)PyNymeaLoggingHandler_log, METH_VARARGS, "Add a new descriptor to the discovery" },
|
{ "log", (PyCFunction)PyNymeaLoggingHandler_info, METH_VARARGS, "Log an info message to the nymea log. Same as info()." },
|
||||||
{ "warn", (PyCFunction)PyNymeaLoggingHandler_warn, METH_VARARGS, "Add a new descriptor to the discovery" },
|
{ "info", (PyCFunction)PyNymeaLoggingHandler_info, METH_VARARGS, "Log an info message to the nymea log." },
|
||||||
|
{ "debug", (PyCFunction)PyNymeaLoggingHandler_debug, METH_VARARGS, "Log a debug message to the nymea log." },
|
||||||
|
{ "warn", (PyCFunction)PyNymeaLoggingHandler_warn, METH_VARARGS, "Log a warning message to the nymea log." },
|
||||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
|
||||||
|
#include "pystdouthandler.h"
|
||||||
#include "pynymealogginghandler.h"
|
#include "pynymealogginghandler.h"
|
||||||
#include "pything.h"
|
#include "pything.h"
|
||||||
#include "pythingdiscoveryinfo.h"
|
#include "pythingdiscoveryinfo.h"
|
||||||
@ -17,12 +18,7 @@
|
|||||||
|
|
||||||
static int nymea_exec(PyObject *m) {
|
static int nymea_exec(PyObject *m) {
|
||||||
|
|
||||||
// Override stdout/stderr to use qDebug instead
|
registerStdOutHandler(m);
|
||||||
PyObject* pyLog = PyModule_Create(&pyLog_module);
|
|
||||||
PySys_SetObject("stdout", pyLog);
|
|
||||||
PyObject* pyWarn = PyModule_Create(&pyWarn_module);
|
|
||||||
PySys_SetObject("stderr", pyWarn);
|
|
||||||
|
|
||||||
registerNymeaLoggingHandler(m);
|
registerNymeaLoggingHandler(m);
|
||||||
registerParamType(m);
|
registerParamType(m);
|
||||||
registerThingType(m);
|
registerThingType(m);
|
||||||
|
|||||||
108
libnymea-core/integrations/python/pystdouthandler.h
Normal file
108
libnymea-core/integrations/python/pystdouthandler.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#ifndef PYSTDOUTHANDLER_H
|
||||||
|
#define PYSTDOUTHANDLER_H
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
#include "structmember.h"
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(dcPythonIntegrations)
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||||
|
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||||
|
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
char *category;
|
||||||
|
QtMsgType msgType;
|
||||||
|
} PyStdOutHandler;
|
||||||
|
|
||||||
|
static int PyStdOutHandler_init(PyStdOutHandler *self, PyObject *args, PyObject */*kwds*/)
|
||||||
|
{
|
||||||
|
qCDebug(dcPythonIntegrations()) << "+++ PyStdOutHandler";
|
||||||
|
char *category = nullptr;
|
||||||
|
QtMsgType msgType;
|
||||||
|
if (!PyArg_ParseTuple(args, "si", &category, &msgType)) {
|
||||||
|
qCWarning(dcPythonIntegrations()) << "PyStdOutHandler: Error parsing parameters";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->category = (char*)malloc(qstrlen(category));
|
||||||
|
self->msgType = msgType;
|
||||||
|
qstrcpy(self->category, category);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PyStdOutHandler_dealloc(PyStdOutHandler * self)
|
||||||
|
{
|
||||||
|
qCDebug(dcPythonIntegrations()) << "--- PyStdOutHandler";
|
||||||
|
free(self->category);
|
||||||
|
Py_TYPE(self)->tp_free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject* PyStdOutHandler_write(PyStdOutHandler* self, PyObject* args)
|
||||||
|
{
|
||||||
|
const char *what;
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &what))
|
||||||
|
return nullptr;
|
||||||
|
if (!QByteArray(what).trimmed().isEmpty()) {
|
||||||
|
switch (self->msgType) {
|
||||||
|
case QtMsgType::QtInfoMsg:
|
||||||
|
qCInfo(QLoggingCategory(self->category)) << what;
|
||||||
|
break;
|
||||||
|
case QtMsgType::QtDebugMsg:
|
||||||
|
qCDebug(QLoggingCategory(self->category)) << what;
|
||||||
|
break;
|
||||||
|
case QtMsgType::QtWarningMsg:
|
||||||
|
qCWarning(QLoggingCategory(self->category)) << what;
|
||||||
|
break;
|
||||||
|
case QtMsgType::QtCriticalMsg:
|
||||||
|
qCCritical(QLoggingCategory(self->category)) << what;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qCDebug(QLoggingCategory(self->category)) << what;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject* PyStdOutHandler_flush(PyObject* /*self*/, PyObject* /*args*/)
|
||||||
|
{
|
||||||
|
// Not really needed... QDebug flushes already on its own
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyStdOutHandler_methods[] = {
|
||||||
|
{ "write", (PyCFunction)PyStdOutHandler_write, METH_VARARGS, "Writes to stdout through qDebug()"},
|
||||||
|
{ "flush", (PyCFunction)PyStdOutHandler_flush, METH_VARARGS, "no-op"},
|
||||||
|
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject PyStdOutHandlerType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"nymea.StdOutHandler", /* tp_name */
|
||||||
|
sizeof(PyStdOutHandler), /* tp_basicsize */
|
||||||
|
0, /* tp_itemsize */
|
||||||
|
(destructor)PyStdOutHandler_dealloc,/* tp_dealloc */
|
||||||
|
};
|
||||||
|
|
||||||
|
static void registerStdOutHandler(PyObject *module)
|
||||||
|
{
|
||||||
|
|
||||||
|
PyStdOutHandlerType.tp_new = PyType_GenericNew;
|
||||||
|
PyStdOutHandlerType.tp_init = reinterpret_cast<initproc>(PyStdOutHandler_init);
|
||||||
|
PyStdOutHandlerType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||||
|
PyStdOutHandlerType.tp_methods = PyStdOutHandler_methods;
|
||||||
|
PyStdOutHandlerType.tp_doc = "Logging handler for nymea.";
|
||||||
|
|
||||||
|
if (PyType_Ready(&PyStdOutHandlerType) == 0) {
|
||||||
|
PyModule_AddObject(module, "NymeaLoggingHandler", (PyObject *)&PyStdOutHandlerType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PYSTDOUTHANDLER_H
|
||||||
@ -71,74 +71,5 @@ QVariant PyObjectToQVariant(PyObject *pyObject)
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to stdout
|
|
||||||
PyObject* pyLog_write(PyObject* /*self*/, PyObject* args)
|
|
||||||
{
|
|
||||||
const char *what;
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &what))
|
|
||||||
return nullptr;
|
|
||||||
if (!QByteArray(what).trimmed().isEmpty()) {
|
|
||||||
qCDebug(dcPythonIntegrations()) << what;
|
|
||||||
}
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
PyObject* pyLog_flush(PyObject* /*self*/, PyObject* /*args*/)
|
|
||||||
{
|
|
||||||
// Not really needed... qDebug() flushes already on its own
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef pyLog_methods[] =
|
|
||||||
{
|
|
||||||
{"write", pyLog_write, METH_VARARGS, "Writes to stdout through qDebug()"},
|
|
||||||
{"flush", pyLog_flush, METH_VARARGS, "noop"},
|
|
||||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef pyLog_module =
|
|
||||||
{
|
|
||||||
PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
|
|
||||||
"pyLog", // const char* m_name;
|
|
||||||
"pyLog stdout override",// const char* m_doc;
|
|
||||||
-1, // Py_ssize_t m_size;
|
|
||||||
pyLog_methods, // PyMethodDef *m_methods
|
|
||||||
// inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free;
|
|
||||||
nullptr, nullptr, nullptr, nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write to stderr
|
|
||||||
PyObject* pyWarn_write(PyObject* /*self*/, PyObject* args)
|
|
||||||
{
|
|
||||||
const char *what;
|
|
||||||
if (!PyArg_ParseTuple(args, "s", &what))
|
|
||||||
return nullptr;
|
|
||||||
if (!QByteArray(what).trimmed().isEmpty()) {
|
|
||||||
qCWarning(dcPythonIntegrations()) << what;
|
|
||||||
}
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
PyObject* pyWarn_flush(PyObject* /*self*/, PyObject* /*args*/)
|
|
||||||
{
|
|
||||||
// Not really needed... qDebug() flushes already on its own
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef pyWarn_methods[] =
|
|
||||||
{
|
|
||||||
{"write", pyWarn_write, METH_VARARGS, "Writes to stderr through qWarnging()"},
|
|
||||||
{"flush", pyWarn_flush, METH_VARARGS, "noop"},
|
|
||||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef pyWarn_module =
|
|
||||||
{
|
|
||||||
PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
|
|
||||||
"pyWarn", // const char* m_name;
|
|
||||||
"pyWarn stdout override",// const char* m_doc;
|
|
||||||
-1, // Py_ssize_t m_size;
|
|
||||||
pyWarn_methods, // PyMethodDef *m_methods
|
|
||||||
// inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free;
|
|
||||||
nullptr, nullptr, nullptr, nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PYUTILS_H
|
#endif // PYUTILS_H
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "pythonintegrationplugin.h"
|
#include "pythonintegrationplugin.h"
|
||||||
#include "python/pynymeamodule.h"
|
#include "python/pynymeamodule.h"
|
||||||
|
#include "python/pystdouthandler.h"
|
||||||
|
|
||||||
#include "loggingcategories.h"
|
#include "loggingcategories.h"
|
||||||
|
|
||||||
@ -327,6 +328,18 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
|||||||
PyObject *args = Py_BuildValue("(s)", category.toUtf8().data());
|
PyObject *args = Py_BuildValue("(s)", category.toUtf8().data());
|
||||||
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(PyObject_CallObject((PyObject*)&PyNymeaLoggingHandlerType, args));
|
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(PyObject_CallObject((PyObject*)&PyNymeaLoggingHandlerType, args));
|
||||||
Py_DECREF(args);
|
Py_DECREF(args);
|
||||||
|
|
||||||
|
// Override stdout and stderr
|
||||||
|
|
||||||
|
args = Py_BuildValue("(si)", category.toUtf8().data(), QtMsgType::QtInfoMsg);
|
||||||
|
PyStdOutHandler*stdOutHandler = reinterpret_cast<PyStdOutHandler*>(PyObject_CallObject((PyObject*)&PyStdOutHandlerType, args));
|
||||||
|
Py_DECREF(args);
|
||||||
|
PySys_SetObject("stdout", (PyObject*)stdOutHandler);
|
||||||
|
args = Py_BuildValue("(si)", category.toUtf8().data(), QtMsgType::QtWarningMsg);
|
||||||
|
PyStdOutHandler*stdErrHandler = reinterpret_cast<PyStdOutHandler*>(PyObject_CallObject((PyObject*)&PyStdOutHandlerType, args));
|
||||||
|
PySys_SetObject("stderr", (PyObject*)stdErrHandler);
|
||||||
|
Py_DECREF(args);
|
||||||
|
|
||||||
int loggerAdded = PyModule_AddObject(m_pluginModule, "logger", reinterpret_cast<PyObject*>(logger));
|
int loggerAdded = PyModule_AddObject(m_pluginModule, "logger", reinterpret_cast<PyObject*>(logger));
|
||||||
if (loggerAdded != 0) {
|
if (loggerAdded != 0) {
|
||||||
qCWarning(dcPythonIntegrations()) << "Failed to add the logger object";
|
qCWarning(dcPythonIntegrations()) << "Failed to add the logger object";
|
||||||
|
|||||||
@ -40,6 +40,7 @@ HEADERS += nymeacore.h \
|
|||||||
integrations/python/pynymealogginghandler.h \
|
integrations/python/pynymealogginghandler.h \
|
||||||
integrations/python/pynymeamodule.h \
|
integrations/python/pynymeamodule.h \
|
||||||
integrations/python/pyparam.h \
|
integrations/python/pyparam.h \
|
||||||
|
integrations/python/pystdouthandler.h \
|
||||||
integrations/python/pything.h \
|
integrations/python/pything.h \
|
||||||
integrations/python/pythingactioninfo.h \
|
integrations/python/pythingactioninfo.h \
|
||||||
integrations/python/pythingdescriptor.h \
|
integrations/python/pythingdescriptor.h \
|
||||||
|
|||||||
@ -6,10 +6,13 @@ watchingAutoThings = False
|
|||||||
loopRunning = False
|
loopRunning = False
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
logger.log("Python mock plugin init")
|
|
||||||
global loopRunning
|
global loopRunning
|
||||||
loopRunning = True
|
loopRunning = True
|
||||||
|
|
||||||
|
logger.log("Python mock plugin init")
|
||||||
|
logger.warn("Python mock warning")
|
||||||
|
print("python stdout")
|
||||||
|
|
||||||
while loopRunning:
|
while loopRunning:
|
||||||
time.sleep(5);
|
time.sleep(5);
|
||||||
for thing in myThings():
|
for thing in myThings():
|
||||||
|
|||||||
Reference in New Issue
Block a user