1
0
mirror of https://gitlab.com/kelteseth/ScreenPlay.git synced 2024-10-06 09:17:07 +02:00

Add unified logging class

this now logs to file and to ui
This commit is contained in:
Elias Steurer 2023-10-29 18:10:13 +01:00
parent 3a14c2b8e4
commit 23873e1f1f
11 changed files with 342 additions and 109 deletions

View File

@ -87,7 +87,6 @@ public slots:
void requestDataProtection();
bool fileExists(const QString& filePath) const;
static void logToGui(QtMsgType type, const QMessageLogContext& context, const QString& msg);
static bool writeJsonObjectToFile(const QString& absoluteFilePath, const QJsonObject& object, bool truncate = true);
static bool writeSettings(const QJsonObject& obj, const QString& absolutePath);
static bool writeFile(const QString& text, const QString& absolutePath);
@ -127,6 +126,4 @@ private:
std::unique_ptr<QArchive::DiskExtractor> m_extractor;
};
// Used for redirect content from static logToGui to setDebugMessages
static Util* utilPointer { nullptr };
}

View File

@ -5,6 +5,7 @@
#include <QDebug>
#include <QGuiApplication>
#include <QStyleFactory>
#include "ScreenPlayUtil/logginghandler.h"
#if defined(Q_OS_WIN)
#include <sentry.h>
@ -26,16 +27,19 @@ int main(int argc, char* argv[])
#endif
QGuiApplication qtGuiApp(argc, argv);
std::unique_ptr<const ScreenPlayUtil::LoggingHandler> logging;
ScreenPlay::App app;
if (app.m_isAnotherScreenPlayInstanceRunning) {
return 0;
} else {
logging = std::make_unique<const ScreenPlayUtil::LoggingHandler>("ScreenPlay");
app.init();
const int status = qtGuiApp.exec();
#if defined(Q_OS_WIN)
sentry_shutdown();
#endif
logging.reset();
return status;
}
}

View File

@ -92,13 +92,13 @@ ScreenPlayWallpaper::ScreenPlayWallpaper(const QVector<int>& screenNumber,
if (m_type != InstalledType::InstalledType::GodotWallpaper) {
m_appArgumentsList.append(" --disable-features=HardwareMediaKeyHandling");
}
if (m_type == InstalledType::InstalledType::GodotWallpaper) {
exportGodotProject(m_absolutePath);
}
}
bool ScreenPlayWallpaper::start()
{
if (m_type == InstalledType::InstalledType::GodotWallpaper) {
exportGodotProject(m_absolutePath);
}
m_process.setArguments(m_appArgumentsList);
if (m_type == InstalledType::InstalledType::GodotWallpaper) {
m_process.setProgram(m_globalVariables->godotWallpaperExecutablePath().toString());

View File

@ -26,11 +26,6 @@ namespace ScreenPlay {
Util::Util()
: QObject(nullptr)
{
// Fix log access vilation on quit
utilPointer = this;
auto* guiAppInst = dynamic_cast<QGuiApplication*>(QGuiApplication::instance());
QObject::connect(guiAppInst, &QGuiApplication::aboutToQuit, this, []() { utilPointer = nullptr; });
m_extractor = std::make_unique<QArchive::DiskExtractor>();
m_compressor = std::make_unique<QArchive::DiskCompressor>();
@ -39,15 +34,6 @@ Util::Util()
QObject::connect(m_compressor.get(), &QArchive::DiskCompressor::progress, this, &Util::compressionProgressChanged);
QObject::connect(m_compressor.get(), &QArchive::DiskCompressor::finished, this, &Util::compressionFinished);
// In release mode redirect messages to logging otherwhise we break the nice clickable output :(
#ifdef QT_NO_DEBUG
qInstallMessageHandler(Util::logToGui);
qInfo() << "Starting ScreenPlay LOG!";
#endif
// This gives us nice clickable output in QtCreator
qSetMessagePattern("%{if-category}%{category}: %{endif}%{message}\n Loc: [%{file}:%{line}]");
}
/*!
@ -269,81 +255,6 @@ bool Util::fileExists(const QString& filePath) const
return file.isFile();
}
static const char*
logLevelForMessageType(QtMsgType msgType)
{
switch (msgType) {
case QtDebugMsg:
return "debug";
case QtWarningMsg:
return "warning";
case QtCriticalMsg:
return "error";
case QtFatalMsg:
return "fatal";
case QtInfoMsg:
Q_FALLTHROUGH();
default:
return "info";
}
}
/*!
\brief Basic logging to the GUI. No logging is done to a log file for now. This string can be copied
in the settings tab in the UI.
*/
void Util::logToGui(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
qDebug() << msg;
QByteArray localMsg = msg.toLocal8Bit();
QByteArray file = "File: " + QByteArray(context.file) + ", ";
QByteArray line = "in line " + QByteArray::number(context.line) + ", ";
QByteArray function = "function " + QByteArray(context.function) + ", Message: ";
QString log = "&emsp; <font color=\"#03A9F4\"> " + QDateTime::currentDateTime().toString() + "</font> &emsp; " + localMsg + "<br>";
switch (type) {
case QtDebugMsg:
log.prepend("<b><font color=\"##78909C\"> Debug</font>:</b>");
break;
case QtInfoMsg:
log.prepend("<b><font color=\"#8BC34A\"> Info</font>:</b>");
break;
case QtWarningMsg:
log.prepend("<b><font color=\"#FFC107\"> Warning</font>:</b>");
break;
case QtCriticalMsg:
log.prepend("<b><font color=\"#FF5722\"> Critical</font>:</b>");
break;
case QtFatalMsg:
log.prepend("<b><font color=\"#F44336\"> Fatal</font>:</b>");
break;
}
log.append("\n");
if (utilPointer != nullptr)
utilPointer->appendDebugMessages(log);
#if defined(Q_OS_WIN)
sentry_value_t crumb
= sentry_value_new_breadcrumb("default", qUtf8Printable(msg));
sentry_value_set_by_key(
crumb, "category", sentry_value_new_string(context.category));
sentry_value_set_by_key(
crumb, "level", sentry_value_new_string(logLevelForMessageType(type)));
sentry_value_t location = sentry_value_new_object();
sentry_value_set_by_key(
location, "file", sentry_value_new_string(context.file));
sentry_value_set_by_key(
location, "line", sentry_value_new_int32(context.line));
sentry_value_set_by_key(crumb, "data", location);
sentry_add_breadcrumb(crumb);
#endif
}
/*!
\brief Takes ownership of \a obj and \a name. Tries to save into a text file

View File

@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
find_package(fmt CONFIG REQUIRED)
find_package(
Qt6
COMPONENTS Core Quick
@ -41,6 +42,7 @@ set(SOURCES
# cmake-format: sort
inc/public/ScreenPlayUtil/httpfileserver.cpp
src/contenttypes.cpp
src/logginghandler.cpp
src/projectfile.cpp
src/util.cpp)
@ -57,6 +59,7 @@ set(HEADER
inc/public/ScreenPlayUtil/PropertyHelpers.h
inc/public/ScreenPlayUtil/PtrPropertyHelpers.h
inc/public/ScreenPlayUtil/SingletonHelper.h
inc/public/ScreenPlayUtil/logginghandler.h
inc/public/ScreenPlayUtil/util.h)
if(APPLE)
@ -95,8 +98,7 @@ target_include_directories(
${PROJECT_NAME}
PUBLIC inc/public/
PRIVATE src/)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Quick)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Quick PUBLIC fmt::fmt-header-only ScreenPlaySysInfo)
if(WIN32)
# Used for query windows monitor data

View File

@ -0,0 +1,36 @@
#pragma once
#include <QFile>
#include <QLoggingCategory>
#include <QObject>
#include <mutex>
namespace ScreenPlayUtil {
class LoggingHandler : public QObject {
Q_OBJECT
public:
explicit LoggingHandler(const QString& logFileName);
~LoggingHandler() override;
private:
static void start();
static void stop();
static std::mutex& logFileMutex();
static QFile& logFile();
static size_t& logFileCounter();
static QString logLine(QtMsgType type, const QMessageLogContext& context, const QString& message);
static QString toString(QtMsgType type);
static QString extractFileName(const QMessageLogContext& context);
static QString extractFunction(const QMessageLogContext& context);
static void writeToConsole(const QString& line, QtMsgType type);
static void writeToFile(const QString& line);
static void checkLogRotation();
static void loggingMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message);
private:
// When increasing the allowed file size, the code that selects the correct log file must be adjusted.
// Otherwise already completed log files of the day will be expanded.
static constexpr qint64 m_maxFileSize = 2000000000; // 2GB
static QString m_logFileName;
};
}

View File

@ -0,0 +1,270 @@
#include "ScreenPlayUtil/logginghandler.h"
#include "sysinfo.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QMutex>
#include <QStandardPaths>
#include <QSysInfo>
#include <QUrl>
#include <fmt/color.h>
/*!
\class ScreenPlayUtil::LoggingHandler
\inmodule core
\brief The LoggingHandler class writes logs to the console and a log file.
*/
namespace ScreenPlayUtil {
QString LoggingHandler::m_logFileName;
LoggingHandler::LoggingHandler(const QString& logFileName)
: QObject()
{
#ifdef Q_OS_WINDOWS
// Enable UTF-8 support
SetConsoleOutputCP(CP_UTF8);
// QtCreator has issues with fmt prints
// https://bugreports.qt.io/browse/QTCREATORBUG-3994
setbuf(stdout, NULL);
#endif
qInstallMessageHandler(LoggingHandler::loggingMessageHandler);
const auto lock = std::lock_guard(logFileMutex());
m_logFileName = logFileName;
start();
}
LoggingHandler::~LoggingHandler()
{
const auto lock = std::lock_guard(logFileMutex());
stop();
}
void LoggingHandler::start()
{
Q_ASSERT(!m_logFileName.isEmpty());
// Use hardcoded path for now because QStandardpaths gives us: 'C:/ProgramData/K3000'
QDir directory;
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
directory = QDir(cacheDir + "/ScreenPlay/Logs");
if (!directory.exists()) {
if (!directory.mkpath(directory.path())) {
qCritical() << "Unable to create logging path at:" << directory.path();
return;
}
}
QString filePath;
for (++logFileCounter(); logFileCounter() < 1000; ++logFileCounter()) {
const auto dateTime = QDateTime::currentDateTime().toString("yy-MM-dd");
const auto fileName = QString { "%1-%2-%3.log" }.arg(dateTime).arg(m_logFileName).arg(QString::number(logFileCounter()));
filePath = directory.path() + "/" + fileName;
auto file = QFile { filePath };
if (!file.exists())
break;
if (file.size() <= m_maxFileSize)
break;
}
logFile().setFileName(filePath);
const auto isOpen = logFile().open(QIODevice::Append | QIODevice::Text);
if (!isOpen) {
qCritical() << "Unable to open log file" << logFile().fileName();
return;
}
SysInfo sysInfo;
QTextStream stream(&logFile());
stream << "Start ScreenPlay logging " << QDateTime::currentDateTime().toString("dd.MM.yyyy-hh:mm:ss.zzz") << "\n";
stream << "version: 1.0\n";
stream << "buildAbi: " << QSysInfo::buildAbi() << "\n";
stream << "buildCpuArchitecture: " << QSysInfo::buildCpuArchitecture() << "\n";
stream << "currentCpuArchitecture: " << QSysInfo::currentCpuArchitecture() << "\n";
stream << "kernelType: " << QSysInfo::kernelType() << "\n";
stream << "kernelVersion: " << QSysInfo::kernelVersion() << "\n";
stream << "machineHostName: " << QSysInfo::machineHostName() << "\n";
stream << "machineUniqueId: " << QSysInfo::machineUniqueId() << "\n";
stream << "prettyProductName: " << QSysInfo::prettyProductName() << "\n";
stream << "productType: " << QSysInfo::productType() << "\n";
stream << "productVersion: " << QSysInfo::productVersion() << "\n";
stream << "GPU Name: " << sysInfo.gpu()->name() << "\n";
stream << "GPU Vendor: " << sysInfo.gpu()->vendor() << "\n";
stream << "GPU ramSize: " << QString::number(sysInfo.gpu()->ramSize()) << "\n";
stream << "GPU cacheSize: " << QString::number(sysInfo.gpu()->cacheSize()) << "\n";
stream << "GPU maxFrequency: " << QString::number(sysInfo.gpu()->maxFrequency()) << "\n";
stream << QString("Uptime: %1 days %2 hours %3 minutes %4 seconds").arg(sysInfo.uptime()->days()).arg(sysInfo.uptime()->hours()).arg(sysInfo.uptime()->minutes()).arg(sysInfo.uptime()->seconds()) << "\n";
const auto now = QLocale().toString(QDateTime::currentDateTime(), QLocale::FormatType::ShortFormat);
const auto message = QString { "[%1] Start logging\n" }.arg(now);
logFile().write(message.toStdString().c_str());
}
void LoggingHandler::stop()
{
const auto now = QLocale().toString(QDateTime::currentDateTime(), QLocale::FormatType::ShortFormat);
const auto message = QString { "[%1] Stop logging\n\n" }.arg(now);
logFile().write(message.toStdString().c_str());
logFile().close();
}
std::mutex& LoggingHandler::logFileMutex()
{
static std::mutex mutex;
return mutex;
}
size_t& LoggingHandler::logFileCounter()
{
static size_t counter = 0;
return counter;
}
QFile& LoggingHandler::logFile()
{
static QFile file;
return file;
}
/*!
\brief Returns a formatted logging line.
Format: "[<DATETIME>]|<CATEGORY>.<TYPE>|<FILENAME>:<LINENUMBER>|<FUNCTION>|<MESSAGE>\n"
If possible, this format should not be changed, otherwise the subsequent parsing of the log file will be very difficult.
*/
QString LoggingHandler::logLine(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
const auto now = QLocale().toString(QDateTime::currentDateTime(), QLocale::FormatType::ShortFormat);
const auto filename = extractFileName(context);
const auto function = extractFunction(context);
auto category = QString(context.category);
if (category == "default")
category = "";
return QString("[%1] %2.%3 | %4:%5 | %6 | %7\n").arg(now).arg(category).arg(toString(type)).arg(filename).arg(context.line).arg(function).arg(message);
}
QString LoggingHandler::toString(QtMsgType type)
{
switch (type) {
case QtDebugMsg:
return "Debug";
case QtWarningMsg:
return "Warning";
case QtCriticalMsg:
return "Critical";
case QtFatalMsg:
return "Fatal";
case QtInfoMsg:
return "Info";
default:
return "?";
}
}
QString LoggingHandler::extractFileName(const QMessageLogContext& context)
{
auto file = QString(context.file);
return file.mid(file.lastIndexOf("\\")).remove("\\");
}
/*!
\brief Removes all the unreadable type stuff from the function information in \a context.
*/
QString LoggingHandler::extractFunction(const QMessageLogContext& context)
{
auto func = QString(context.function);
auto from = -1;
if (from < 0) {
static const QString search { "__thiscall" };
from = func.indexOf(search);
if (from >= 0) {
from += search.length();
}
}
if (from < 0) {
static const QString search { "__cdecl" };
from = func.indexOf(search);
if (from >= 0) {
from += search.length();
}
}
if (from < 0) {
from = 0;
}
auto to = func.indexOf("(", from);
if (to < 0)
return func;
auto length = to - from;
return func.mid(from, length).trimmed();
}
/*!
* \brief LoggingHandler::writeToConsole
* std::flush is used to fix QtCreator not printing output.
*/
void LoggingHandler::writeToConsole(const QString& line, QtMsgType type)
{
constexpr auto darkMode = true;
auto color = fmt::color::black;
switch (type) {
case QtDebugMsg:
color = fmt::color::green;
break;
case QtWarningMsg:
color = fmt::color::orange;
break;
case QtCriticalMsg:
color = fmt::color::magenta;
break;
case QtFatalMsg:
color = fmt::color::red;
break;
default:
color = darkMode ? fmt::color::gray : fmt::color::black;
break;
}
fmt::print("{}", fmt::styled(line.toStdString(), fg(color)));
}
void LoggingHandler::writeToFile(const QString& line)
{
std::lock_guard lock(logFileMutex());
if (logFile().isOpen()) {
checkLogRotation();
logFile().write(line.toStdString().c_str());
}
}
/*!
\brief Opens a new log file if the current log file becomes too large.
*/
void LoggingHandler::checkLogRotation()
{
if (logFile().size() <= m_maxFileSize)
return;
logFile().write("Maximum file size reached. A new log file is used\n");
qInfo() << "Rotate Log file";
stop();
start();
}
void LoggingHandler::loggingMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
const QString line = LoggingHandler::logLine(type, context, message);
LoggingHandler::writeToConsole(line, type);
LoggingHandler::writeToFile(line);
}
}

View File

@ -10,6 +10,7 @@
#include "ScreenPlayUtil/exitcodes.h"
#include "ScreenPlayUtil/util.h"
#include "ScreenPlayUtil/logginghandler.h"
#if defined(Q_OS_WIN)
#include "src/winwindow.h"
@ -36,6 +37,7 @@ int main(int argc, char* argv[])
#endif
QGuiApplication app(argc, argv);
std::unique_ptr<const ScreenPlayUtil::LoggingHandler> logging;
std::unique_ptr<BaseWindow> window;
const auto platformName = QGuiApplication::platformName();
@ -54,6 +56,7 @@ int main(int argc, char* argv[])
// If we start with only one argument (app path)
// It means we want to test a single wallpaper
const QStringList argumentList = app.arguments();
QString appID;
// For testing purposes when starting the ScreenPlayWallpaper directly.
if (argumentList.length() == 1) {
@ -66,7 +69,7 @@ int main(int argc, char* argv[])
"/wallpaper_video_astronaut_vp9", // 4
"/wallpaper_video_nebula_h264" // 5
};
const int index = 1;
const int index = 5;
QString projectPath = exampleContentPath + contentFolder.at(index);
window->setActiveScreensList({ 0 });
@ -111,7 +114,7 @@ int main(int argc, char* argv[])
return static_cast<int>(ScreenPlay::WallpaperExitCode::Invalid_Volume);
}
QString appID = argumentList.at(3);
appID = argumentList.at(3);
if (!appID.startsWith("appID=")) {
qCritical("Invalid appID");
return static_cast<int>(ScreenPlay::WallpaperExitCode::Invalid_AppID);
@ -137,5 +140,8 @@ int main(int argc, char* argv[])
return static_cast<int>(startStatus);
}
emit window->qmlStart();
return app.exec();
logging = std::make_unique<const ScreenPlayUtil::LoggingHandler>("ScreenPlayWallpaper_" + appID);
const int status = app.exec();
logging.reset();
return status;
}

View File

@ -9,6 +9,7 @@
#include <QtWebEngineQuick>
#include "src/widgetwindow.h"
#include "ScreenPlayUtil/logginghandler.h"
#if defined(Q_OS_WIN)
Q_IMPORT_QML_PLUGIN(ScreenPlaySysInfoPlugin)
@ -32,6 +33,7 @@ int main(int argc, char* argv[])
#endif
QGuiApplication app(argc, argv);
std::unique_ptr<const ScreenPlayUtil::LoggingHandler> logging;
const QStringList argumentList = app.arguments();
@ -83,16 +85,19 @@ int main(int argc, char* argv[])
qWarning() << "Could not parse PositionY value to int: " << argumentList.at(5);
positionY = 0;
}
const QString appID = argumentList.at(2);
WidgetWindow spwmw(
argumentList.at(1), // Project path,
argumentList.at(2), // AppID
appID, // AppID
argumentList.at(3), // Type
QPoint { positionX, positionY });
#if defined(Q_OS_OSX)
MacUtils::showDockIcon(false);
#endif
logging = std::make_unique<const ScreenPlayUtil::LoggingHandler>("ScreenPlayWidget_"+ appID);
return app.exec();
const int status = app.exec();
logging.reset();
return status;
}

View File

@ -26,6 +26,13 @@ QT_TOOLS_PATH = QT_PATH.joinpath("Tools/")
QT_IFW_VERSION = "4.6"
# 02.06.2023 https://gitlab.com/kelteseth/screenplay-vcpkg :
VCPKG_VERSION = "f06975f46d8c7a1dad916e1e997584f77ae0c34a"
VCPKG_BASE_PACKAGES = [
"curl",
"cpp-httplib",
"libarchive",
"fmt",
"catch2"
]
PYTHON_EXECUTABLE = "python" if sys.platform == "win32" else "python3"
FFMPEG_VERSION = "5.0.1"

View File

@ -113,12 +113,7 @@ def main():
root_path = Path(util.cd_repo_root_path())
project_source_parent_path = root_path.joinpath("../").resolve()
vcpkg_path = project_source_parent_path.joinpath("vcpkg").resolve()
vcpkg_packages_list = [
"curl",
"cpp-httplib",
"libarchive",
"catch2"
]
vcpkg_packages_list = defines.VCPKG_BASE_PACKAGES
if not args.skip_aqt:
setup_qt()