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);
|
||||
}
|
||||
|
||||
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;
|
||||
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||
@ -73,9 +89,12 @@ static PyObject * PyNymeaLoggingHandler_warn(PyNymeaLoggingHandler* self, PyObje
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef PyNymeaLoggingHandler_methods[] = {
|
||||
{ "log", (PyCFunction)PyNymeaLoggingHandler_log, METH_VARARGS, "Add a new descriptor to the discovery" },
|
||||
{ "warn", (PyCFunction)PyNymeaLoggingHandler_warn, 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()." },
|
||||
{ "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
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "pystdouthandler.h"
|
||||
#include "pynymealogginghandler.h"
|
||||
#include "pything.h"
|
||||
#include "pythingdiscoveryinfo.h"
|
||||
@ -17,12 +18,7 @@
|
||||
|
||||
static int nymea_exec(PyObject *m) {
|
||||
|
||||
// Override stdout/stderr to use qDebug instead
|
||||
PyObject* pyLog = PyModule_Create(&pyLog_module);
|
||||
PySys_SetObject("stdout", pyLog);
|
||||
PyObject* pyWarn = PyModule_Create(&pyWarn_module);
|
||||
PySys_SetObject("stderr", pyWarn);
|
||||
|
||||
registerStdOutHandler(m);
|
||||
registerNymeaLoggingHandler(m);
|
||||
registerParamType(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();
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "pythonintegrationplugin.h"
|
||||
#include "python/pynymeamodule.h"
|
||||
#include "python/pystdouthandler.h"
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
@ -327,6 +328,18 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
PyObject *args = Py_BuildValue("(s)", category.toUtf8().data());
|
||||
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(PyObject_CallObject((PyObject*)&PyNymeaLoggingHandlerType, 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));
|
||||
if (loggerAdded != 0) {
|
||||
qCWarning(dcPythonIntegrations()) << "Failed to add the logger object";
|
||||
|
||||
@ -40,6 +40,7 @@ HEADERS += nymeacore.h \
|
||||
integrations/python/pynymealogginghandler.h \
|
||||
integrations/python/pynymeamodule.h \
|
||||
integrations/python/pyparam.h \
|
||||
integrations/python/pystdouthandler.h \
|
||||
integrations/python/pything.h \
|
||||
integrations/python/pythingactioninfo.h \
|
||||
integrations/python/pythingdescriptor.h \
|
||||
|
||||
@ -6,10 +6,13 @@ watchingAutoThings = False
|
||||
loopRunning = False
|
||||
|
||||
def init():
|
||||
logger.log("Python mock plugin init")
|
||||
global loopRunning
|
||||
loopRunning = True
|
||||
|
||||
logger.log("Python mock plugin init")
|
||||
logger.warn("Python mock warning")
|
||||
print("python stdout")
|
||||
|
||||
while loopRunning:
|
||||
time.sleep(5);
|
||||
for thing in myThings():
|
||||
|
||||
Reference in New Issue
Block a user