防止 qDebug() 写入标准输出

Prevent qDebug() from writing to std output

我正在使用 qDebug()qInfo() 等结合 qInstallMessageHandler 来写我的 logfiles。执行我的应用程序时,我的批处理屏幕上也有输出。

我只找到 QT_NO_DEBUG_OUTPUT,但我想在 运行 时切换此配置。有没有办法阻止 Qt 写入标准输出?

sadly no, you only get access to the messages, but cannot prevent from beeing written to std output.

至少在 Qt 5 中这是错误的。消息打印代码looks as follows:你可以清楚地看到只使用了消息处理程序:

if (grabMessageHandler()) {
    // prefer new message handler over the old one
    if (msgHandler.load() == qDefaultMsgHandler
            || messageHandler.load() != qDefaultMessageHandler) {
        (*messageHandler.load())(msgType, context, message);
    } else {
        (*msgHandler.load())(msgType, message.toLocal8Bit().constData());
    }
    ungrabMessageHandler();
} else {
    fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
}

如您所见,仅当从 messageHandler 本身内部检测到递归时,fprintf(stderr, ...) 才是回退。

因此,要防止任何调试输出,您只需实施和设置自己的 messageHandler

要完全关闭 Qt 中的所有调试输出,以下工作:

#include <QtCore>
int main() {
  qDebug() << "I'm not quiet at all yet";
  qInstallMessageHandler(+[](QtMsgType, const QMessageLogContext &, const QString &){});
  qDebug() << "I'm very, very quiet";
}

无论如何,应用程序范围的文件记录器的合理实现可能如下所示 - 它不会不必要地重新创建 QTextStream;它使用 QString::toUtf8() 代替,并明确写入行尾。

#include <QtCore>

class Logger {
   static struct Data {
      Logger *instance;
      QtMessageHandler chainedHandler;
   } d;
   bool m_isOpen;
   QFile m_logFile;
   QtMessageHandler m_oldHandler = {};
   static void handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
      if (d.instance)
         d.instance->log(msg);
      if (d.chainedHandler)
         d.chainedHandler(type, context, msg);
   }
public:
   enum ChainMode { DontChain, Chain };
   Logger() {
      Q_ASSERT(!instance());
      m_logFile.setFileName("myLog.txt");
      m_isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
      d.instance = this;
   }
   ~Logger() { uninstallHandler(); }
   bool isOpen() const { return m_isOpen; }
   void installHandler(ChainMode mode) {
      Q_ASSERT(!m_oldHandler);
      m_oldHandler = qInstallMessageHandler(handler);
      if (mode == Chain)
         d.chainedHandler = m_oldHandler;
   }
   void uninstallHandler() {
      if (m_oldHandler) {
         m_oldHandler = nullptr;
         d.chainedHandler = nullptr;
         qInstallMessageHandler(m_oldHandler);
      }
   }
   /// This method is *not* thread-safe. Use with a thread-safe wrapper such as `qDebug`.
   void log(const QString & msg) {
      if (isOpen()) {
         m_logFile.write(msg.toUtf8());
         m_logFile.write("\n", 1);
      }
   }
   /// Closes the log file early - this is mostly used for testing.
   void endLog() {
      uninstallHandler();
      m_logFile.close();
   }
   static Logger *instance() { return d.instance; }
};

Logger::Data Logger::d;

template <typename T> QByteArray streamOutputFor(const T &data) {
   QBuffer buf;
   buf.open(QIODevice::ReadWrite | QIODevice::Text);
   QTextStream s(&buf);
   s << data << endl;
   buf.close();
   return buf.data();
}

QByteArray readEnd(const QString &fileName, int count) {
   QFile file(fileName);
   if (file.open(QIODevice::ReadOnly | QIODevice::Text) && file.size() >= count) {
      file.seek(file.size() - count);
      return file.readAll();
   }
   return {};
}

void test() {
   auto const entry = QDateTime::currentDateTime().toString().toUtf8();
   Q_ASSERT(Logger::instance()->isOpen());
   qDebug() << entry.data();
   Logger::instance()->endLog();

   auto reference = streamOutputFor(entry.data());
   auto readback = readEnd("myLog.txt", reference.size());
   Q_ASSERT(!reference.isEmpty());
   Q_ASSERT(readback == reference);
}

int main() {
   Logger logger;
   logger.installHandler(Logger::DontChain);
   qDebug() << "Something or else";
   test();
}