Properly catch stdout and stderr and redirect it to qmessagelogger

This commit is contained in:
Michael Zanetti 2020-09-06 23:43:02 +02:00
parent 85f742a38d
commit 15eead0976
7 changed files with 150 additions and 79 deletions

View File

@ -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
};

View File

@ -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);

View 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

View File

@ -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

View File

@ -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";

View File

@ -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 \

View File

@ -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():