diff --git a/src/video/video.h b/src/video/video.h index 9a18c317..6670f367 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -21,7 +21,8 @@ struct UASYNC; void video_ffmpeg_init(void); /* Проба доступности codec: begin/end парные, вложенные, только для текущего потока. - * Ожидаемые отказы FFmpeg выводятся на debug с подсчётом повторов; рабочие потоки не затрагиваются. */ + * Ожидаемые отказы FFmpeg и сводка повторов выводятся на trace категории video; + * рабочие потоки сохраняют error/warn. begin/end должны выполняться в одном потоке. */ void video_codec_probe_begin(void); void video_codec_probe_end(void); /* Возвращает debug вместо error/warn/info внутри пробы; level — DEBUG_LEVEL_* из debug_config.h. */ diff --git a/tools/chatgui/src/gui_log.h b/tools/chatgui/src/gui_log.h index a793617b..2d84ddc4 100644 --- a/tools/chatgui/src/gui_log.h +++ b/tools/chatgui/src/gui_log.h @@ -1,6 +1,12 @@ -// Диагностика Qt через общий лог ядра: уровни/категории и debug_file применяются как к остальным сообщениям. +/* Диагностика Qt через общий logger ядра, с его фильтрами уровней/категорий и текущим файлом вывода. + * Qt debug/info/warning → debug/info/warn; critical/fatal → error. Категории с префиксом + * qt.multimedia направляются в video, остальные — в general. Контекст file/line/function + * сохраняется; QString синхронно преобразуется в UTF-8. Старый handler не вызывается. + * Qt может вызывать обработчик из разных потоков; он использует общий debug_config. + * Настройка logger-а и время жизни заимствованного пути его файла остаются у вызывающего. */ #pragma once #include -// Вызывать до создания QApplication; возвращает прежний Qt handler (для восстановления в тестах). +// Установить handler на весь процесс, желательно до QApplication. Вернуть прежний handler; +// для восстановления передать возвращённый указатель в qInstallMessageHandler(). QtMessageHandler installGuiLogHandler(); diff --git a/tools/chatgui/src/main.cpp b/tools/chatgui/src/main.cpp index 35813ee9..adf26dc4 100644 --- a/tools/chatgui/src/main.cpp +++ b/tools/chatgui/src/main.cpp @@ -346,7 +346,8 @@ int main(int argc, char *argv[]) { * конструкторе MainWindow ДО старта utun-потока, поэтому без этого его диагностика * (ma_engine_init и т.п.) в лог-файл не попадёт. */ debug_enable_function_name(0); - debug_enable_file_output(debugFile.toUtf8().constData(), 0); + const QByteArray debugFilePath = debugFile.toUtf8(); // Logger заимствует путь до закрытия/замены файла. + debug_enable_file_output(debugFilePath.constData(), 0); QString lvl = cfg.debugLevel().trimmed(); if (lvl.isEmpty()) lvl = QStringLiteral("info"); debug_apply_global_level(lvl.toUtf8().constData()); diff --git a/tools/chatgui/src/video_codec_log.h b/tools/chatgui/src/video_codec_log.h index e1b04f7c..d8bb93a0 100644 --- a/tools/chatgui/src/video_codec_log.h +++ b/tools/chatgui/src/video_codec_log.h @@ -1,5 +1,9 @@ -// Диагностика codec: scoped-проба понижает ожидаемые отказы до debug только в своём потоке. -// Создать VideoCodecProbe на время проверки; CODEC_LOG сохраняет обычный уровень вне неё. +/* Диагностика codec: VideoCodecProbe задаёт вложенную scoped-пробу в текущем потоке. + * Создать/уничтожить в одном потоке; копирование и перенос запрещены. Конструктор возвращает + * общий FFmpeg callback после возможной замены Qt Multimedia; деструктор завершает одну вложенность. + * CODEC_LOG внутри пробы переводит error/warn/info в debug; debug/trace не меняются. + * Подробности FFmpeg-пробы и сводка повторов идут на trace категории video, вне пробы — error/warn. + * Глобальные уровни не меняются, ошибки рабочего codec в соседнем потоке сохраняют свой уровень. */ #pragma once #include "../../../src/video/video.h" #include "debug_config.h" @@ -12,6 +16,8 @@ public: VideoCodecProbe& operator=(const VideoCodecProbe&) = delete; }; +// level/category — DEBUG_LEVEL_*/DEBUG_CATEGORY_*, далее printf-формат и аргументы; +// аргументы форматирования вычисляются только после фильтрации по итоговому уровню. #define CODEC_LOG(level, category, ...) do { \ const auto codecLevel = static_cast(video_codec_log_level(level)); \ if (debug_should_output(codecLevel, category)) \ diff --git a/tools/chatgui/tests/test_video_diagnostics.cpp b/tools/chatgui/tests/test_video_diagnostics.cpp index 87220ae7..2715c9d1 100644 --- a/tools/chatgui/tests/test_video_diagnostics.cpp +++ b/tools/chatgui/tests/test_video_diagnostics.cpp @@ -13,11 +13,11 @@ extern "C" { } namespace { -struct Message { int level; QString text; }; +struct Message { int level; QString text, category; }; std::mutex lock; std::vector messages; -void capture(int level, const char*, const char* text) { - std::lock_guard guard(lock); messages.push_back({level, QString::fromUtf8(text)}); +void capture(int level, const char* category, const char* text) { + std::lock_guard guard(lock); messages.push_back({level, QString::fromUtf8(text), QString::fromUtf8(category)}); } } @@ -30,6 +30,7 @@ private slots: } void cleanup() { debug_set_log_hook(nullptr); } void probeLevelsRepeatsAndConcurrentErrors() { + debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_TRACE); { VideoCodecProbe probe; av_log(nullptr, AV_LOG_ERROR, "GPU unavailable\n"); @@ -44,10 +45,10 @@ private slots: } av_log(nullptr, AV_LOG_ERROR, "runtime failure after probe\n"); QCOMPARE(messages.size(), size_t(5)); - QCOMPARE(messages[0].level, int(DEBUG_LEVEL_DEBUG)); QVERIFY(messages[0].text.contains("GPU unavailable")); + QCOMPARE(messages[0].level, int(DEBUG_LEVEL_TRACE)); QVERIFY(messages[0].text.contains("GPU unavailable")); QCOMPARE(messages[1].level, int(DEBUG_LEVEL_DEBUG)); QVERIFY(messages[1].text.contains("expected probe failure")); QCOMPARE(messages[2].level, int(DEBUG_LEVEL_ERROR)); QVERIFY(messages[2].text.contains("live decoder failure")); - QCOMPARE(messages[3].level, int(DEBUG_LEVEL_DEBUG)); QVERIFY(messages[3].text.contains("repeated=2")); + QCOMPARE(messages[3].level, int(DEBUG_LEVEL_TRACE)); QVERIFY(messages[3].text.contains("repeated=2")); QCOMPARE(messages[4].level, int(DEBUG_LEVEL_ERROR)); QVERIFY(messages[4].text.contains("runtime failure after probe")); } void infoLevelSkipsProbeDetails() { @@ -62,6 +63,17 @@ private slots: CODEC_LOG(DEBUG_LEVEL_WARN, DEBUG_CATEGORY_CALL, "actual playback warning"); QCOMPARE(messages.size(), size_t(1)); QCOMPARE(messages[0].level, int(DEBUG_LEVEL_WARN)); } + void debugShowsCodecDecisionsAndTraceIsRequiredForFfmpegDetails() { + { + VideoCodecProbe probe; + av_log(nullptr, AV_LOG_ERROR, "detailed FFmpeg probe error\n"); + av_log(nullptr, AV_LOG_ERROR, "detailed FFmpeg probe error\n"); + CODEC_LOG(DEBUG_LEVEL_WARN, DEBUG_CATEGORY_CALL, "codec decision during probe"); + } + QCOMPARE(messages.size(), size_t(1)); QCOMPARE(messages[0].level, int(DEBUG_LEVEL_DEBUG)); + QVERIFY(messages[0].text.contains("codec decision during probe")); + QCOMPARE(video_codec_log_level(DEBUG_LEVEL_ERROR), int(DEBUG_LEVEL_ERROR)); + } void restoresFfmpegCallback() { av_log_set_callback(av_log_default_callback); video_ffmpeg_init(); @@ -70,7 +82,9 @@ private slots: } void qtAndFfmpegUseCommonFile() { QTemporaryDir directory; QVERIFY(directory.isValid()); - const auto path = directory.filePath("chatgui.log"); debug_enable_file_output(path.toUtf8().constData(), 0); + const auto path = directory.filePath("chatgui.log"); + const QByteArray filePath = path.toUtf8(); debug_enable_file_output(filePath.constData(), 0); + QCOMPARE(QByteArray(g_debug_config.output_file), filePath); QCOMPARE(debug_reopen_log(), 0); const auto previous = installGuiLogHandler(); const QLoggingCategory category("qt.multimedia.test"); qWarning("portal diagnostic"); @@ -82,6 +96,44 @@ private slots: QVERIFY(text.contains("[WARN] [video]")); QVERIFY(text.contains("Qt[qt.multimedia.test]: codec diagnostic")); QVERIFY(text.contains("[ERROR] [video]")); QVERIFY(text.contains("ffmpeg: FFmpeg diagnostic")); } + + void qtSeverityAndCategory_data() { + QTest::addColumn("type"); QTest::addColumn("level"); QTest::addColumn("category"); + QTest::newRow("debug") << int(QtDebugMsg) << int(DEBUG_LEVEL_DEBUG) << QByteArray("default"); + QTest::newRow("info") << int(QtInfoMsg) << int(DEBUG_LEVEL_INFO) << QByteArray("default"); + QTest::newRow("warning") << int(QtWarningMsg) << int(DEBUG_LEVEL_WARN) << QByteArray("default"); + QTest::newRow("critical") << int(QtCriticalMsg) << int(DEBUG_LEVEL_ERROR) << QByteArray("qt.network"); + QTest::newRow("fatal-handler") << int(QtFatalMsg) << int(DEBUG_LEVEL_ERROR) << QByteArray("default"); + QTest::newRow("multimedia") << int(QtWarningMsg) << int(DEBUG_LEVEL_WARN) << QByteArray("qt.multimedia"); + QTest::newRow("multimedia-child") << int(QtWarningMsg) << int(DEBUG_LEVEL_WARN) << QByteArray("qt.multimedia.test"); + } + + void qtSeverityAndCategory() { + QFETCH(int, type); QFETCH(int, level); QFETCH(QByteArray, category); + const auto previous = installGuiLogHandler(); + const auto handler = qInstallMessageHandler(previous); // Получить callback, восстановив QtTest handler. + QVERIFY(handler); + const QMessageLogContext context("gui-source.cpp", 42, "guiFunction", category.constData()); + // Вызываем сам callback: QtFatalMsg здесь не запускает внешнее завершение qFatal. + handler(static_cast(type), context, QString::fromUtf8("Проверка UTF-8")); + QCOMPARE(messages.size(), size_t(1)); QCOMPARE(messages[0].level, level); + QCOMPARE(messages[0].category, category.startsWith("qt.multimedia") ? QStringLiteral("video") : QStringLiteral("general")); + QVERIFY(messages[0].text.contains("gui-source.cpp:42")); QVERIFY(messages[0].text.contains("guiFunction")); + QVERIFY(messages[0].text.contains(QString::fromUtf8("Проверка UTF-8"))); + } + + void qtHonorsCategoryOverrides() { + const auto previous = installGuiLogHandler(); + const auto handler = qInstallMessageHandler(previous); QVERIFY(handler); + debug_set_level(DEBUG_LEVEL_ERROR); + debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_DEBUG); + debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DISABLED); + handler(QtWarningMsg, QMessageLogContext(), QStringLiteral("filtered general warning")); + QVERIFY(messages.empty()); + handler(QtDebugMsg, QMessageLogContext(nullptr, 0, nullptr, "qt.multimedia"), QStringLiteral("video override")); + QCOMPARE(messages.size(), size_t(1)); QCOMPARE(messages[0].category, QStringLiteral("video")); + QCOMPARE(messages[0].level, int(DEBUG_LEVEL_DEBUG)); + } }; QTEST_GUILESS_MAIN(DiagnosticsTest) #include "test_video_diagnostics.moc"