diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini index 083d7234e..20aa206ce 100644 --- a/cockatrice/resources/config/qtlogging.ini +++ b/cockatrice/resources/config/qtlogging.ini @@ -1,6 +1,10 @@ [Rules] # The default log level is info *.debug = false +#*.info = true +#*.warning = true +#*.critical = true +#*.fatal = true # Uncomment a rule to see debug level logs for that category, # or set = false to disable logging diff --git a/doc/doxygen/extra-pages/developer_documentation/index.md b/doc/doxygen/extra-pages/developer_documentation/index.md index b26faf836..da2d444d8 100644 --- a/doc/doxygen/extra-pages/developer_documentation/index.md +++ b/doc/doxygen/extra-pages/developer_documentation/index.md @@ -1,5 +1,7 @@ @page developer_reference Developer Reference +- @subpage logging + - @subpage primer_cards - @subpage card_database_schema_and_parsing diff --git a/doc/doxygen/extra-pages/developer_documentation/logging.md b/doc/doxygen/extra-pages/developer_documentation/logging.md new file mode 100644 index 000000000..8fd5cb015 --- /dev/null +++ b/doc/doxygen/extra-pages/developer_documentation/logging.md @@ -0,0 +1,184 @@ +@page logging Logging + +Cockatrice uses QtLogging from the QtCore module for its logging. See +the [official documentation](https://doc.qt.io/qt-6/qtlogging.html) for further details. + +# Log Message Pattern + +Any message logged through the QtLogging system automatically conforms to this message pattern: + +Generic: + +``` +[ ] [] - [:] +``` + +Example: + +``` +[2025-12-05 14:48:25.908 I] [MainWindow::startupConfigCheck] - Startup: found config with current version [window_main.cpp:951] +``` + +For more information, see [Logging Setup](#logging-setup). + +# Log Level and Categories + +\note The default log level for the application is info. + +This means that you should only use qInfo() in production-level code if you are truly sure that this message is +beneficial to end-users and other developers. As a general rule, if your functionality logs to info more than twice in +response to a user interaction, you are advised to consider moving some of these logs down to the debug level. + +\warning You are strongly advised to avoid the use of the generic logging macros (e.g. qDebug(), qInfo(), qWarn()). + +\note You should instead use the corresponding category logging macros (qCDebug(), qCInfo(), qCWarn()) and define +logging +categories for your log statements. + +Example: + +```c++ +in .h + +inline Q_LOGGING_CATEGORY(ExampleCategory, "cockatrice_example_category"); +inline Q_LOGGING_CATEGORY(ExampleSubCategory, "cockatrice_example_category.sub_category"); + +in .cpp + +qCInfo(ExampleCategory) << "Info level logs are usually sent through the main category" +qCDebug(ExampleSubCategory) << "Debug level logs are permitted their own category to allow selective silencing" +``` + +For more information on how to enable or disable logging categories, +see [Logging Configuration](#logging-configuration). + +# Logging Configuration + +For configuring our logging, we use the qtlogging.ini, located under cockatrice/resources/config/qtlogging.ini, which is +baked into the application in release version and set as the QT_LOGGING_CONF environment variable in main.cpp. + +```c++ +#ifdef Q_OS_APPLE + // /cockatrice/cockatrice.app/Contents/MacOS/cockatrice + const QByteArray configPath = "../../../qtlogging.ini"; +#elif defined(Q_OS_UNIX) + // /cockatrice/cockatrice + const QByteArray configPath = "./qtlogging.ini"; +#elif defined(Q_OS_WIN) + // /cockatrice/Debug/cockatrice.exe + const QByteArray configPath = "../qtlogging.ini"; +#else + const QByteArray configPath = ""; +#endif + + if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) { + // Set the QT_LOGGING_CONF environment variable + qputenv("QT_LOGGING_CONF", configPath); + } +``` + +For more information on how to use this file and on how Qt evaluates which logging rules/file to use, please +see the [official Qt documentation](https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories). + +Some examples: + +``` +# Turn off all logging except everything from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.* = true +``` + +``` +# Turn off all logging except info level logs from card_picture_loader and all sub categories + +[Rules] +# The default log level is info +*.debug = false +*.info = false +*.warning = false +*.critical = false +*.fatal = false + +card_picture_loader.*.info = true +``` + +``` +[Rules] +# Turn on debug level logs for card_picture_loader but keep logging for sub categories suppressed +*.debug = false + +card_picture_loader.debug = true +``` + +``` +[Rules] +# Turn on all logs for worker subcategory of card_picture_loader +*.debug = false + +card_picture_loader.worker = true +``` + +``` +[Rules] +# Turn off some noisy and irrelevant startup logging for local development +*.debug = false + +qt_translator = false +window_main.* = false +release_channel = false +spoiler_background_updater = false +theme_manager = false +sound_engine = false +tapped_out_interface = false +card_database = false +card_database.loading = false +card_database.loading.success_or_failure = true +cockatrice_xml.* = false +``` + +# Logging Setup + +This is achieved through our logging setup in @ref main.cpp, where we set the message pattern and install a custom +logger which replaces the full file path at the end with just the file name (Qt shows the full and quite lengthy path by +default). + +```c++ + qSetMessagePattern( + "\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} " + "%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{" + "endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]"); + QApplication app(argc, argv); + + QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit); + + qInstallMessageHandler(CockatriceLogger); +``` + +```c++ +static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message) +{ + QString logMessage = qFormatLogMessage(type, ctx, message); + + // Regular expression to match the full path in the square brackets and extract only the filename and line number + QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])"); + QRegularExpressionMatch match = regex.match(logMessage); + + if (match.hasMatch()) { + // Extract the filename and line number (e.g., "main.cpp:211") + QString filenameLine = match.captured(1); + + // Replace the full path in square brackets with just the filename and line number + logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine)); + } + + Logger::getInstance().log(type, ctx, logMessage); +} +``` \ No newline at end of file