Про 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 не повторить сумну долю інших хабраклонів і тут можна буде просувати адекватні українські терміни.
Ще немає коментарів