diff --git a/libnymea-core/integrations/python/pynymealogginghandler.h b/libnymea-core/integrations/python/pynymealogginghandler.h index 2bcce1d4..a34ca452 100644 --- a/libnymea-core/integrations/python/pynymealogginghandler.h +++ b/libnymea-core/integrations/python/pynymealogginghandler.h @@ -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 }; diff --git a/libnymea-core/integrations/python/pynymeamodule.h b/libnymea-core/integrations/python/pynymeamodule.h index e750e650..b9f01844 100644 --- a/libnymea-core/integrations/python/pynymeamodule.h +++ b/libnymea-core/integrations/python/pynymeamodule.h @@ -3,6 +3,7 @@ #include +#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); diff --git a/libnymea-core/integrations/python/pystdouthandler.h b/libnymea-core/integrations/python/pystdouthandler.h new file mode 100644 index 00000000..24d3fdba --- /dev/null +++ b/libnymea-core/integrations/python/pystdouthandler.h @@ -0,0 +1,108 @@ +#ifndef PYSTDOUTHANDLER_H +#define PYSTDOUTHANDLER_H + +#include +#include "structmember.h" + +#include +#include + +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(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 diff --git a/libnymea-core/integrations/python/pyutils.h b/libnymea-core/integrations/python/pyutils.h index 5e24a232..996183c7 100644 --- a/libnymea-core/integrations/python/pyutils.h +++ b/libnymea-core/integrations/python/pyutils.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 diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 3891a84a..47ffac42 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -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(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(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(PyObject_CallObject((PyObject*)&PyStdOutHandlerType, args)); + PySys_SetObject("stderr", (PyObject*)stdErrHandler); + Py_DECREF(args); + int loggerAdded = PyModule_AddObject(m_pluginModule, "logger", reinterpret_cast(logger)); if (loggerAdded != 0) { qCWarning(dcPythonIntegrations()) << "Failed to add the logger object"; diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index b4da93f8..f16a69a2 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -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 \ diff --git a/plugins/pymock/integrationpluginpymock.py b/plugins/pymock/integrationpluginpymock.py index e16199c2..fea164a3 100644 --- a/plugins/pymock/integrationpluginpymock.py +++ b/plugins/pymock/integrationpluginpymock.py @@ -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():