2

I am working with a C++ QT application that also uses JavaScript. In C++ I use the qDebug function and capture all data with qInstallMessageHandler.

This captures everything that is directed to stderr. In JavaScript I am using console.info which writes data to stdout.

What I want to do is redirect stdout to stderr so that all the messages written by console.info find they're way into the same message handler.

void qDebugMsgHandler(QtMsgType type, const QMessageLogContext& context, const QString& strMsg) {
    QString strOutput;

    if ( context.file ) {
        strOutput += QString(context.file);

        if ( context.function ) {
            if ( context.line > 0 ) {
                strOutput += QString(" L%1").arg(context.line, 8, 10, QChar('0')) + QString(":");
           }
            strOutput += QString(context.function);
        }
    }
    if ( strMsg.length() > 0 ) {
        if ( strOutput.length() > 0 ) {
            strOutput += ": ";
        }
        strOutput += strMsg;
    }
    switch( type ) {
    case QtDebugMsg:
        fprintf(stderr, "   Debug:%s\n", strOutput.toLatin1().data());
        break;
    case QtInfoMsg:
        fprintf(stderr, "    Info:%s\n", strOutput.toLatin1().data());
        break;
    case QtWarningMsg:
        fprintf(stderr, " Warning:%s\n", strOutput.toLatin1().data());
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical:%s\n", strOutput.toLatin1().data());
        break;
    case QtFatalMsg:
        fprintf(stderr, "   Fatal:%s\n", strOutput.toLatin1().data());
        break;
    }
    fflush(stderr);
}
SPlatten
  • 5,334
  • 11
  • 57
  • 128
  • Could you show your message handler ? Normally, QML logs should be caught by the handler. And this does not goes through `stdout` or `stderr` yet when using the handler, it is up to you where you write that log message. – Nicolas Dusart Jul 16 '20 at 08:23
  • @NicolasDusart, posted edited with handler included. – SPlatten Jul 16 '20 at 08:26
  • When you're talking about javascript, this is from QML, right ? These use the C++ functions to print logs (https://doc.qt.io/qt-5/qtquick-debugging.html#console-api) and should be catched by the message handler. Message handler does not capture stdout or stderr, but just calls to qDebug() and other facilities. The default handler prints to stderr. You have to find out why it does not go in that handler, maybe `QLoggingCategory::isInfoEnabled()` is false for "qml" category ? – Nicolas Dusart Jul 16 '20 at 08:33
  • No, my application isn't using QML. I'm connecting my C++ signals to JavaScript slots, there is no QML involved. – SPlatten Jul 16 '20 at 08:34
  • You should rewrite your question into something like "How to catch console.info in a QtMessageHandler" as this is not a problem of redirection of stdin. – Nicolas Dusart Jul 16 '20 at 08:35
  • Just a thought....I could add an API call to my C++ application that is callable from JavaScript to log the messages, then when JavaScript calls the C++ log message it would use the Qt debug functions. – SPlatten Jul 16 '20 at 08:35
  • Ok, so you are running Javascript in a QWebEngineView ? Take a look at: https://stackoverflow.com/questions/46738098/qt-webengine-redirect-javascript-output-to-gui You could then print these message using qDebug and catch them in your handler. – Nicolas Dusart Jul 16 '20 at 08:39
  • No, I'm not using QWebEngineView, I am using QJSEngine. – SPlatten Jul 16 '20 at 08:41
  • 1
    Yes, then you have to provide a C++ object to the JS engine. To summary, QtMessageHandler does not catch prints to stdout or stderr in any way. It provides a way to handle message printed using Qt debug facilities, that's all. The solution was not in redirection. Question probably needs an edit. – Nicolas Dusart Jul 16 '20 at 08:46

2 Answers2

2

You can use ios::rdbuf.

Example:

#include <iostream>

int main() {
    std::cout << "to stdout\n";
    std::cout.rdbuf(std::cerr.rdbuf());
    std::cout << "to stderr\n";
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • I wish I could say it worked, but it didn't, will do a bit more research as to where console.info is writing to and exactly what qInstallMessageHandler displays from. – SPlatten Jul 16 '20 at 08:22
  • @SPlatten I see in your edit that you don't use `std::cout`. What if you use `std::cout` instead of `std::printf`, does it work then? You can also try `std::reopen("logfile", "w", stdout);` to write directly to a file. – Ted Lyngmo Jul 16 '20 at 08:29
  • The handler is processing everything from the C++, but nothing from the JavaScript. – SPlatten Jul 16 '20 at 08:31
  • @SPlatten Hmm, tricky. Is the C++ program running the JavaScript? Did you do `std::cout.rdbuf(std::cerr.rdbuf());` before starting the JavaScript? – Ted Lyngmo Jul 16 '20 at 08:37
  • I've had an idea, since I already provide the C++ with a set of functions it can call, I will just write a version of qDebug that the JavaScript can call. – SPlatten Jul 16 '20 at 08:40
0

For my purpose, I ended up created an invokable routine in C++ which could be called from the JavaScript, C++ prototype:

Q_INVOKABLE void log(QString strMsg, QString strFile, long ulngLine);

C++ implementation:

void clsScriptHelper::log(QString strMsg, QString strFile, long ulngLine) {
    QJsonObject json;
    json["file"] = strFile;
    json["line"] = QString("%1").arg(ulngLine);
    json["msg"] = strMsg;
    json["type"] = QString("%1").arg(QtDebugMsg);
    qDebug() << json;
}

In the JavaScript I expose my C++ layer through the object reference "api", then call the log routine:

api.log("Testing", "SomeFile.js", 99);

The message handler now looks like this:

void qDebugMsgHandler(QtMsgType type, const QMessageLogContext& context, const QString& strMsg) {
    static const QString scstrQJSONObject("QJsonObject(");

    QString strFile, strFunction, strInfo, strOutput;
    long lngLine = 0;

    switch( type ) {
    case QtDebugMsg:
        strOutput = "   Debug:";
        break;
    case QtInfoMsg:
        strOutput = "    Info:";
        break;
    case QtWarningMsg:
        strOutput = " Warning:";
        break;
    case QtCriticalMsg:
        strOutput = "Critical:";
        break;
    case QtFatalMsg:
        strOutput = "   Fatal:";
        break;
    }
    if ( strMsg.startsWith(scstrQJSONObject) ) {
//Message contains a JSON object, extract the details
        int intLength = strMsg.length() - (scstrQJSONObject.length() + 1);
        QString strJSON = strMsg.mid(scstrQJSONObject.length(), intLength);
        QJsonDocument objDoc = QJsonDocument::fromJson(strJSON.toUtf8());

        if ( objDoc.isNull() ) {
            return;
        }
        QJsonObject objJSON = objDoc.object();
        strFile = objJSON.take("file").toString();
        lngLine = static_cast<long>(objJSON.take("line").toDouble());
        strInfo = objJSON.take("msg").toString();
        type = static_cast<QtMsgType>(objJSON.take("type").toInt());
    } else {
        strFile = QString(context.file);

        if ( context.function ) {
            strFunction = QString(context.function);
        }
        if ( context.line > 0 ) {
            lngLine = context.line;
        }
        strInfo = strMsg;
    }
    if ( strFile.length() > 0 ) {
        strOutput += strFile;
    }
    if ( lngLine > 0 ) {
        strOutput += QString(" L%1").arg(lngLine, 8, 10, QChar('0')) + QString(":");
    }
    if ( strFunction.length() > 0 ) {
        strOutput += strFunction;
    }
    if ( strInfo.length() > 0 ) {
        if ( strOutput.length() > 0 ) {
            strOutput += ": ";
        }
        strOutput += strInfo;
    }
    std::cout << strOutput.toLatin1().data() << std::endl << std::flush;
}
SPlatten
  • 5,334
  • 11
  • 57
  • 128
  • Something is still not quite right, although using the debugger I can see that messages from both JavaScript via the API routine and C++ are making it into the message handler, I do not see messages written from JavaScript in the Application Output, really no idea why this is? – SPlatten Jul 16 '20 at 10:18