Search code examples
androidqtadblogcatqdebug

What's the simplest way to see logging from a Qt app on Android in adb logcat output?


NB I am not a QtCreator user. I build android apps in build scripts with qmake, make and androiddeployqt, and deploy them to the device with adb install.

I'd like to be able to see the output of qDebug, qInfo etc, and also any qml conole.log output and any other chatter from the QML engine, in abd logcat output. But in a vanilla build of a Qt app for Android, any such messages just seem to be blackholed (or at least I have no idea where they're going).

I have had some success by the combination of:

  • Redirecting logging to stderr using qInstallMessageHandler (as shown here).

  • Redirecting stderr to Android logging using the code here. (Update: subsequently discovered this approach is rather unsatisfactory as it relies on another thread listening on a pipe, and crashing apps can lose the logging immediately preceeding the crash).

But this all seems a bit clunky and and overcomplicated. Surely there's a better and simpler way? (For example, with Qt on Windows you can simply build with CONFIG += console to get a console window with logging displayed, but that option is Windows specific).

Qt versions from 5.7 onwards of interest.

Googling this output redirection issue turns up quite a lot of mention of adb shell setprop log.redirect-stdio true, however so far as I can tell it has no effect on Qt apps' stout/stderr.


Solution

  • Actually, combining aspects of the two solutions linked in the question at least reduces it to one function:

    const char*const applicationName="myapp";
    
    #ifdef ANDROIDQUIRKS  // Set in my myapp.pro file for android builds
    #include <android/log.h>
    
    void myMessageHandler(
      QtMsgType type,
      const QMessageLogContext& context,
      const QString& msg
    ) {
      QString report=msg;
      if (context.file && !QString(context.file).isEmpty()) {
        report+=" in file ";
        report+=QString(context.file);
        report+=" line ";
        report+=QString::number(context.line);
      }
      if (context.function && !QString(context.function).isEmpty()) {
        report+=+" function ";
        report+=QString(context.function);
      }
      const char*const local=report.toLocal8Bit().constData();
      switch (type) {
      case QtDebugMsg:
        __android_log_write(ANDROID_LOG_DEBUG,applicationName,local);
        break;
      case QtInfoMsg:
        __android_log_write(ANDROID_LOG_INFO,applicationName,local);
        break;
      case QtWarningMsg:
        __android_log_write(ANDROID_LOG_WARN,applicationName,local);
        break;
      case QtCriticalMsg:
        __android_log_write(ANDROID_LOG_ERROR,applicationName,local);
        break;
      case QtFatalMsg:
      default:
        __android_log_write(ANDROID_LOG_FATAL,applicationName,local);
        abort();    
      }
    }
    #endif
    
    ...
    
    int main(int argc,char* argv[]) {
    
      QGuiApplication app(argc,argv);  
    #ifdef ANDROIDQUIRKS
      qInstallMessageHandler(myMessageHandler);
    #endif
      app.setApplicationName(applicationName);
      ...
    

    This gets things like qInfo() << messages from C++ and console.log messages from QML. What it doesn't get is stdout/stderr output, but I can live with that for new code which can use Qt logging throughout.

    In logcat, the QML code Component.onCompleted: console.log("QML completed") results in

    08-02 12:27:53.378  1199  1220 D myapp : QML completed in file qrc:///main.qml line 7 function onCompleted
    

    and in C++ the code qInfo() << "QQuickViewer setup completed"; produces

    08-02 12:27:53.562  1199  1220 I myapp : QQuickViewer setup completed
    

    Installing the message handler even before QGuiApplication is instantiated seems to work equally well, but it doesn't generate any additional output (I note Qt on iOS spews all sorts of stuff on startup to the xcode console e.g a performance warning linking to https://wiki.qt.io/V4 and wondered if Android did the same; but if it's emitting anything it must be before my handler is installed).