Вивід налагоджувальних повідомлень у Qt

1 хв. читання

Про qDebug() відомо кожному, хто хоч раз писав "Hello, world" у Qt. Це макрос, який приховує внутрішню механіку фреймворку. Існує ще кілька подібних конструкцій:

Ці всі макроси можна використовувати у якості функції на зразок printf. При цьому не потрібно під'єднувати ніяких заголовних файлів. Однак виникає незручність, оскільки специфікатор %s вимагає типу char , тому для змінних типу QString необхідно додавати методи перетворення:

int number = 5;
qInfo("Info: %d", number);

const QString line="Line of text";
qWarning("Warning: %s", line.toLocal8Bit().data() );

Макрос qFatal() одразу після друкування повідомлення перериває роботу програми. Мабуть, саме тому його ніхто не використовує: у багтрекері з 2014 року висять незакриті баги про те, що потокова версія не працює (точніше, навіть не компілюється).

Є ще дві змінні оточення, про які варто знати:

  • якщо встановити (надати будь-яке значення, крім пустого рядка) змінну QT_FATAL_WARNINGS, то qWarning() буде поводити себе подібно до qFatal(): перериватиме виконання програми після виводу повідомлення.
  • встановлення змінної QT_FATAL_CRITICALS вмикає таку ж поведінку для qCritical()

Потоковий вивід

Якщо під'єднати хедер <QDebug>, стане доступним потоковий вивід типу

qInfo() << "Info message" ;

У нього теж є додаткові конструкції для покращення виводу:

  • nospace()/space() контролює додавання пробілів
  • noquote()/quote() контролює додавання лапок до того, що виводиться

Наприклад:

int number = 5;
QString line= "zzz" ;
qDebug() << "Number is" << number << "and line is" << line;
qDebug().nospace() << "Number is" << number << "and line is" << line;
qDebug().nospace().noquote() << "Number is" << number << "and line is" << line;

Надрукує відповідно

Number is 5 and line is "zzz"
Number is5and line is"zzz"
Number is5and line iszzz

Ще можна перевизначити оператор << для запису з вашого класу до qDebug. Це добре описано в документації (тут або тут) і у цьому прикладі. Проблема в тому, що якщо перевантажувати функцією, як в прикладах, то вона не матиме доступу до приватних членів класу. Тому, на мою думку, краще перевизначити конвертацію у QString — такий спосіб простіший і більш універсальний.

Фільтрування на етапі компіляції

За допомогою оголошень QT_NO_DEBUG_OUTPUT, QT_NO_INFO_OUTPUT, та QT_NO_WARNING_OUTPUT можна за допомогою препроцесору від'єднати відповідні повідомлення на етапі компіляції. Тобто, якщо у *.pro файлі вказати

DEFINES += "QT_NO_INFO_OUTPUT"

то зникнуть всі виклики типу

qInfo() << "Info (stream): " << someObj.getLine() ;

Навіть сам метод (у даному випадку getLine()) не буде викликаний. Хоча десь у документації було зауваження, що така поведінка не гарантується і може змінитись у майбутньому.

Налаштування формату повідомлень

За допомогою змінної оточення QT_MESSAGE_PATTERN можна змінювати формат повідомлень та навіть фільтрувати їх. При цьому перекомпіляція програми не потрібна.

Наприклад, таким патерном можна додати час до виводу:

QT_MESSAGE_PATTERN="[%{time hh:mm:ss.zzz}] :: %{message}"

Існують наступні шаблони:

  • %{message} видає саме повідомлення (те, що в програмі прийшло до qDebug())
  • %{time <format>} задає формат часу. Також є окремі
  • %{file},%{line} та %{function} задають відповідно назву файлу, рядок коду і назву методу, у якому стався виклик qDebug(). Працюватиме лише у випадку налагоджувальної конфігурації (config += debug у pro-файлі).
  • %{backtrace ...} працює лише для Linux, видає список викликів.
  • %{category} — категорія повідомлень, про це буде розказано далі
  • % {threadid} та %{qthreadptr} — ідентифікатори потоків, стануть у нагоді при налагодженні багатопотокових програм
  • % {pid} та %{appname} — ідентифікатори процесу.

Також є конструкції %{if-debug}, %{if-info}, %{if-warning}, %{if-critical} та %{if-category}, які дозволяють задавати частину формату або відфільтровувати відповідні типи повідомлень.

Наприклад, можна зробити так:

QT_MESSAGE_PATTERN="%{if-critical}%{time hh:mm:ss.zzz} %{message}%{endif}"\\
"%{if-info}%{time hh:mm:ss.zzz} %{threadid} %{message}%{endif}"\\
"%{if-warning}%{endif}"
"%{if-debug}%{endif}"

Цей патерн додає час до критичних повідомлень, час та ідентифікатор потоку до інформаційних, а також приховує результати qWarning() та qDebug().

Користувачам Windows варто згадати, що при встановленні змінної можна переходити на інший рядок символом "^", а символ "%" потрібно екранувати.

Наприклад, встановлення патерну, що додає час до всіх повідомлень і окремо назву методу до налагоджувальних, у bat-файлі виглядатиме так:

set QT_MESSAGE_PATTERN=^
%%{time hh:mm:ss.zzz}^
%%{if-debug}%%{function} :: %%{endif}^
%%{message}

Власний обробник запитів

Обробник запитів — спеціальна функція, яка викликається кожного разу, коли треба вивести налагоджувальне повідомлення. Стандартний варіант друкує все у потоці виводу, а також викликає завершення програми у випадку qFatal(). Є можливість задати свій власний варіант цієї функції, вона гарно описана у документації qInstallMessageHandler.

До речі, у Qt 5.6 з'явилась можливість перенаправляли вивід налагоджувальних повідомлень до Syslog або journald. Докладніше процес описано у цьому пості, також там є ще один приклад встановлення власного обробника запитів.

Категорії повідомлень

Клас QLoggingCategory реалізує ще один варіант управління налагоджувальними повідомленнями. Категорія — текстовий ідентифікатор, який буде додаватись до повідомлень; також є певні засоби для того, щоб приховувати/показувати повідомлення, що належать до певних категорій.

Для того, щоб оголосити категорію, треба зробити два оголошення: у заголовному файлі та у файлі з кодом.

У h-файлі так:

Q_DECLARE_LOGGING_CATEGORY(categoryName)

У cpp-файлі так:

Q_LOGGING_CATEGORY(categoryName, "specialcategoryname")

В принципі, можна обійтись і без макросів-декларацій, просто створивши окремий об'єкт QLoggingCategory у коді:

void doSomething() {
  QLoggingCategory lc("specialcategoryname");

  //now you can use qCDebug(lc, "Message" ) 
}

Так чи інакше, потім можна писати налагоджувальні повідомлення у звичному стилі:

qCCritical(categoryName) << "Critical message " << "to stream" ;

Макроси ті ж самі, лише назва трохи відрізняється і потрібен додатковий параметр — ідентифікатор категорії.

Після цього можна застосовувати правила, які виглядають приблизно так:

*.debug=true
specialcategoryname.debug=false
specialcategoryname.info=false

Цей набір дозволяє debug-повідомлення для всіх категорій, але забороняє info та debug для нашої "specialcategoryname".

Правила фільтрування можна задавати кількома шляхами:

  • записати їх до файлу, а сам файл покласти у спеціальне місце. Порядок його пошуку досить складний, але по суті це перебір усіх звичайних місць, де можуть знайтись налаштування програми
  • можна задати зсередини програми, викликавши setFilterRules
  • можна зазначити у змінній оточення QT_LOGGING_RULES (доведеться зліпити все в один рядок)
QT_LOGGING_RULES="*.debug=false;threadworker.debug=true"

QML

У QML налагоджувальні повідомлення можна писати за допомогою методів console:

    console.warn("warn completed") ;
    console.log("log completed") ;  // will be "debug" category
    console.error("error completed") ; // will be "critical" category
    console.debug("debug completed") ;
    console.exception("exception completed") ; //will be "critical" too
    console.info("info completed") ;

Все, про що було розказано вище, для таких повідомлень також працюватиме. Наприклад, %{time ... } у QT_MESSAGE_PATTERN буде так само додавати час.

Посилання

  • Презентація з Qt Developer Days про повідомлення
  • Стаття про розфарбовування виводу програми різними кольорами (для Linux, звісно)
  • Стаття про налагоджувальні повідомлення та OpenGL
  • Стаття у Qt Weekly про QLoggingCategory

Насамкінець трошки про українську мову. У нашій вікі стаття про debugger має жахливу назву "зневаджувач". Особисто мені більше подобається термін "налагодження", який теж може похвалитись відповідною статтею у вікі, тому я його тут і використовую.

Сподіваюсь, що в майбутньому codeguida не повторить сумну долю інших хабраклонів і тут можна буде просувати адекватні українські терміни.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.2K
Приєднався: 9 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація