Clarify desktop logging contracts and retain early log path

This commit is contained in:
evgeny committed 2026-10-27 01:41:23 +01:00
1 parent d22bae19b4
commit b2addca5ce
5 files changed
+78 -12

No files matched your search

+2 -1
View File
@@ -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. */
+8 -2
View File
@@ -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 <QtGlobal>
// Вызывать до создания QApplication; возвращает прежний Qt handler (для восстановления в тестах).
// Установить handler на весь процесс, желательно до QApplication. Вернуть прежний handler;
// для восстановления передать возвращённый указатель в qInstallMessageHandler().
QtMessageHandler installGuiLogHandler();
+2 -1
View File
@@ -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());
+8 -2
View File
@@ -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<debug_level_t>(video_codec_log_level(level)); \
if (debug_should_output(codecLevel, category)) \
+58 -6
View File
@@ -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<Message> messages;
void capture(int level, const char*, const char* text) {
std::lock_guard<std::mutex> guard(lock); messages.push_back({level, QString::fromUtf8(text)});
void capture(int level, const char* category, const char* text) {
std::lock_guard<std::mutex> 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<int>("type"); QTest::addColumn<int>("level"); QTest::addColumn<QByteArray>("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<QtMsgType>(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"