Различные статьи по Qt и c++

Вывод qDebug в консоль: настройка и собственный обработчик сообщений

обновлено 335 просмотров

Введение

Функция qDebug() — самый простой способ посмотреть, что происходит внутри программы на Qt. Но часто вывод qDebug в консоль работает не так, как ожидается: сообщения не появляются вовсе, русский текст превращается в «кракозябры», а в релизной сборке всё молчит. В статье разберём, почему так происходит, как настроить консоль для приложения и как написать собственный обработчик сообщений, чтобы полностью управлять выводом.

Функции логирования в Qt

Кроме qDebug(), в Qt есть ещё несколько функций для разных уровней важности:

  • qDebug() — отладочные сообщения;
  • qInfo() — информационные сообщения;
  • qWarning() — предупреждения;
  • qCritical() — критические ошибки;
  • qFatal() — фатальная ошибка, после которой программа завершается.

Все они работают по одной схеме: формируют строку и передают её в обработчик сообщений. По умолчанию обработчик пишет в стандартный поток ошибок stderr, а в Windows при запуске из отладчика — в окно отладочного вывода.

Почему вывод qDebug не виден в консоли

В Windows графическое приложение по умолчанию собирается без консольного окна. Поэтому при запуске exe-файла напрямую сообщения просто некуда выводить. Чтобы консоль появилась, в файле проекта qmake нужно добавить:

CONFIG += console

Если проект собирается через CMake, консоль включена, пока для цели не указан флаг WIN32_EXECUTABLE. Для отладочной сборки его можно отключить:

set_target_properties(myapp PROPERTIES
    WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>
)

В Qt Creator также можно включить опцию «Запускать в терминале» в настройках запуска проекта. Тогда программа стартует в отдельном окне консоли.

Сообщения пропали в релизной сборке

Если в проекте определён макрос QT_NO_DEBUG_OUTPUT, все вызовы qDebug() превращаются в пустые операции. Аналогично работают QT_NO_INFO_OUTPUT и QT_NO_WARNING_OUTPUT. Проверьте, не добавлены ли они в настройках сборки для Release. Ещё одна причина — правила логирования. Переменная окружения QT_LOGGING_RULES или файл qtlogging.ini могут отключать отдельные категории сообщений, например:

QT_LOGGING_RULES="*.debug=true"

Формат сообщений

Функция qSetMessagePattern() позволяет добавить к каждому сообщению время, уровень, имя файла и номер строки. Шаблон можно задать и через переменную окружения QT_MESSAGE_PATTERN:

qSetMessagePattern("[%{time hh:mm:ss.zzz}] %{type}: %{message} (%{file}:%{line})");

Имя файла и номер строки выводятся только в отладочной сборке или если определён макрос QT_MESSAGELOGCONTEXT.

Собственный обработчик сообщений

Когда стандартного поведения мало, можно установить свой обработчик через qInstallMessageHandler(). Он получает тип сообщения, контекст (файл, строку, функцию) и сам текст. Например, так можно вывести каждое сообщение с префиксом уровня:

#include <QCoreApplication>
#include <QDebug>
#include <cstdio>
#include <cstdlib>

// Функция для обработки сообщений
void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    Q_UNUSED(context);
    const QByteArray text = msg.toUtf8();

    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s\n", text.constData());
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s\n", text.constData());
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s\n", text.constData());
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s\n", text.constData());
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s\n", text.constData());
        abort();
    }
    fflush(stderr);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // Установка обработчика сообщений
    qInstallMessageHandler(myMessageHandler);

    qDebug() << "This is a debug message.";
    qInfo() << "This is an info message.";
    qWarning() << "This is a warning message.";
    qCritical() << "This is a critical message.";

    return a.exec();
}

В консоли мы увидим следующий вывод:

Вывод qDebug, qInfo, qWarning и qCritical в консоли через собственный обработчик сообщений

Вызов fflush(stderr) гарантирует, что сообщение появится сразу, а не задержится в буфере. Внутри обработчика нельзя снова вызывать qDebug(): это приведёт к бесконечной рекурсии.

Запись лога в файл

Тот же обработчик удобно использовать для сохранения сообщений в файл. Это особенно полезно для программ, которые работают у пользователей без консоли:

#include <QFile>
#include <QTextStream>
#include <QDateTime>

void fileMessageHandler(QtMsgType type, const QMessageLogContext &, const QString &msg)
{
    static QFile file("app.log");
    if (!file.isOpen())
        file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);

    static const char *levels[] = {"DEBUG", "WARN", "CRIT", "FATAL", "INFO"};

    QTextStream out(&file);
    out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
        << " [" << levels[type] << "] " << msg << "\n";
    out.flush();
}

Массив уровней упорядочен по значениям перечисления QtMsgType: QtDebugMsg = 0, QtWarningMsg = 1, QtCriticalMsg = 2, QtFatalMsg = 3, QtInfoMsg = 4. Если приложение многопоточное, запись в файл стоит защитить мьютексом.

Русский текст в консоли Windows

Если вместо кириллицы в консоли появляются непонятные символы, значит, кодировка консоли не совпадает с UTF-8. Проще всего переключить её в начале программы:

#ifdef Q_OS_WIN
#include <windows.h>
#endif

int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
    SetConsoleOutputCP(CP_UTF8);
#endif
    QCoreApplication a(argc, argv);
    qDebug() << "Привет, мир!";
    return a.exec();
}

Заключение

Вывод qDebug в консоль зависит от нескольких вещей: есть ли у приложения консольное окно, не отключены ли сообщения макросами или правилами логирования и совпадает ли кодировка. Для полного контроля используйте qSetMessagePattern() и собственный обработчик через qInstallMessageHandler(). С его помощью сообщения можно форматировать, фильтровать и сохранять в файл.