File indexing completed on 2024-05-12 15:57:04

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "KisUsageLogger.h"
0007 
0008 #include <QScreen>
0009 #include <QGlobalStatic>
0010 #include <QDebug>
0011 #include <QDateTime>
0012 #include <QSysInfo>
0013 #include <QStandardPaths>
0014 #include <QFile>
0015 #include <QFileInfo>
0016 #include <QDir>
0017 #include <QDesktopWidget>
0018 #include <QClipboard>
0019 #include <QThread>
0020 #include <QApplication>
0021 #include <klocalizedstring.h>
0022 #include <KritaVersionWrapper.h>
0023 #include <QGuiApplication>
0024 #include <QStyle>
0025 #include <QStyleFactory>
0026 #include <QTextCodec>
0027 
0028 #ifdef Q_OS_WIN
0029 #include "KisWindowsPackageUtils.h"
0030 #include <windows.h>
0031 #endif
0032 
0033 #ifdef Q_OS_ANDROID
0034 #include <QtAndroidExtras/QtAndroid>
0035 #endif
0036 
0037 #include <clocale>
0038 
0039 Q_GLOBAL_STATIC(KisUsageLogger, s_instance)
0040 
0041 const QString KisUsageLogger::s_sectionHeader("================================================================================\n");
0042 
0043 struct KisUsageLogger::Private {
0044     bool active {false};
0045     QFile logFile;
0046     QFile sysInfoFile;
0047 };
0048 
0049 KisUsageLogger::KisUsageLogger()
0050     : d(new Private)
0051 {
0052     if (!QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()) {
0053         QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
0054     }
0055     d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
0056     d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log");
0057 
0058     QFileInfo fi(d->logFile.fileName());
0059     if (fi.size() > 100 * 1000 * 1000) { // 100 mb seems a reasonable max
0060         d->logFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
0061         d->logFile.close();
0062     }
0063     else {
0064         rotateLog();
0065     }
0066 
0067     d->logFile.open(QFile::Append | QFile::Text);
0068     d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
0069 }
0070 
0071 KisUsageLogger::~KisUsageLogger()
0072 {
0073     if (d->active) {
0074         close();
0075     }
0076 }
0077 
0078 void KisUsageLogger::initialize()
0079 {
0080     s_instance->d->active = true;
0081 
0082     QString systemInfo = basicSystemInfo();
0083     s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
0084 }
0085 
0086 QString KisUsageLogger::basicSystemInfo()
0087 {
0088     QString systemInfo;
0089 
0090     // NOTE: This is intentionally not translated!
0091 
0092     // Krita version info
0093     systemInfo.append("Krita\n");
0094     systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
0095 #ifdef Q_OS_WIN
0096     {
0097         using namespace KisWindowsPackageUtils;
0098         QString packageFamilyName;
0099         QString packageFullName;
0100         systemInfo.append("\n Installation type: ");
0101         if (tryGetCurrentPackageFamilyName(&packageFamilyName) && tryGetCurrentPackageFullName(&packageFullName)) {
0102             systemInfo.append("Store / MSIX package\n    Family Name: ")
0103                 .append(packageFamilyName)
0104                 .append("\n    Full Name: ")
0105                 .append(packageFullName);
0106         } else {
0107             systemInfo.append("installer / portable package");
0108         }
0109     }
0110 #endif
0111     systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
0112     systemInfo.append("\n\n");
0113 
0114     systemInfo.append("Qt\n");
0115     systemInfo.append("\n  Version (compiled): ").append(QT_VERSION_STR);
0116     systemInfo.append("\n  Version (loaded): ").append(qVersion());
0117     systemInfo.append("\n\n");
0118 
0119     // OS information
0120     systemInfo.append("OS Information\n");
0121     systemInfo.append("\n  Build ABI: ").append(QSysInfo::buildAbi());
0122     systemInfo.append("\n  Build CPU: ").append(QSysInfo::buildCpuArchitecture());
0123     systemInfo.append("\n  CPU: ").append(QSysInfo::currentCpuArchitecture());
0124     systemInfo.append("\n  Kernel Type: ").append(QSysInfo::kernelType());
0125     systemInfo.append("\n  Kernel Version: ").append(QSysInfo::kernelVersion());
0126     systemInfo.append("\n  Pretty Productname: ").append(QSysInfo::prettyProductName());
0127     systemInfo.append("\n  Product Type: ").append(QSysInfo::productType());
0128     systemInfo.append("\n  Product Version: ").append(QSysInfo::productVersion());
0129 
0130 #ifdef Q_OS_ANDROID
0131     QString manufacturer =
0132         QAndroidJniObject::getStaticObjectField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").toString();
0133     const QString model =
0134         QAndroidJniObject::getStaticObjectField("android/os/Build", "MODEL", "Ljava/lang/String;").toString();
0135     manufacturer[0] = manufacturer[0].toUpper();
0136     systemInfo.append("\n  Product Model: ").append(manufacturer + " " + model);
0137 #elif defined(Q_OS_LINUX)
0138     systemInfo.append("\n  Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP"));
0139 #endif
0140     systemInfo.append("\n\n");
0141 
0142     return systemInfo;
0143 }
0144 
0145 void KisUsageLogger::writeLocaleSysInfo()
0146 {
0147     if (!s_instance->d->active) {
0148         return;
0149     }
0150     QString systemInfo;
0151     systemInfo.append("Locale\n");
0152     systemInfo.append("\n  Languages: ").append(KLocalizedString::languages().join(", "));
0153     systemInfo.append("\n  C locale: ").append(std::setlocale(LC_ALL, nullptr));
0154     systemInfo.append("\n  QLocale current: ").append(QLocale().bcp47Name());
0155     systemInfo.append("\n  QLocale system: ").append(QLocale::system().bcp47Name());
0156     const QTextCodec *codecForLocale = QTextCodec::codecForLocale();
0157     systemInfo.append("\n  QTextCodec for locale: ").append(codecForLocale->name());
0158 #ifdef Q_OS_WIN
0159     {
0160         systemInfo.append("\n  Process ACP: ");
0161         CPINFOEXW cpInfo {};
0162         if (GetCPInfoExW(CP_ACP, 0, &cpInfo)) {
0163             systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
0164         } else {
0165             // Shouldn't happen, but just in case
0166             systemInfo.append(QString::number(GetACP()));
0167         }
0168         wchar_t lcData[2];
0169         int result = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, lcData, sizeof(lcData) / sizeof(lcData[0]));
0170         if (result == 2) {
0171             systemInfo.append("\n  System locale default ACP: ");
0172             int systemACP = lcData[1] << 16 | lcData[0];
0173             if (systemACP == CP_ACP) {
0174                 systemInfo.append("N/A");
0175             } else if (GetCPInfoExW(systemACP, 0, &cpInfo)) {
0176                 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
0177             } else {
0178                 // Shouldn't happen, but just in case
0179                 systemInfo.append(QString::number(systemACP));
0180             }
0181         }
0182     }
0183 #endif
0184     systemInfo.append("\n\n");
0185     s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
0186 }
0187 
0188 void KisUsageLogger::close()
0189 {
0190     log("CLOSING SESSION");
0191     s_instance->d->active = false;
0192     s_instance->d->logFile.flush();
0193     s_instance->d->logFile.close();
0194     s_instance->d->sysInfoFile.flush();
0195     s_instance->d->sysInfoFile.close();
0196 }
0197 
0198 void KisUsageLogger::log(const QString &message)
0199 {
0200     if (!s_instance->d->active) return;
0201     if (!s_instance->d->logFile.isOpen()) return;
0202 
0203     s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
0204     s_instance->d->logFile.write(": ");
0205     write(message);
0206 }
0207 
0208 void KisUsageLogger::write(const QString &message)
0209 {
0210     if (!s_instance->d->active) return;
0211     if (!s_instance->d->logFile.isOpen()) return;
0212 
0213     s_instance->d->logFile.write(message.toUtf8());
0214     s_instance->d->logFile.write("\n");
0215 
0216     s_instance->d->logFile.flush();
0217 }
0218 
0219 void KisUsageLogger::writeSysInfo(const QString &message)
0220 {
0221     if (!s_instance->d->active) return;
0222     if (!s_instance->d->sysInfoFile.isOpen()) return;
0223 
0224     s_instance->d->sysInfoFile.write(message.toUtf8());
0225     s_instance->d->sysInfoFile.write("\n");
0226 
0227     s_instance->d->sysInfoFile.flush();
0228 
0229 }
0230 
0231 
0232 void KisUsageLogger::writeHeader()
0233 {
0234     Q_ASSERT(s_instance->d->sysInfoFile.isOpen());
0235     s_instance->d->logFile.write(s_sectionHeader.toUtf8());
0236 
0237     QString sessionHeader = QString("SESSION: %1. Executing %2\n\n")
0238             .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))
0239             .arg(qApp->arguments().join(' '));
0240 
0241     s_instance->d->logFile.write(sessionHeader.toUtf8());
0242 
0243     QString KritaAndQtVersion;
0244     KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true))
0245             .append(", Qt version compiled: ").append(QT_VERSION_STR)
0246             .append(", loaded: ").append(qVersion())
0247             .append(". Process ID: ")
0248             .append(QString::number(qApp->applicationPid())).append("\n");
0249 
0250     KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n");
0251     s_instance->d->logFile.write(KritaAndQtVersion.toUtf8());
0252     s_instance->d->logFile.flush();
0253     log(QString("Style: %1. Available styles: %2")
0254         .arg(qApp->style()->objectName(),
0255              QStyleFactory::keys().join(", ")));
0256 
0257 }
0258 
0259 QString KisUsageLogger::screenInformation()
0260 {
0261     QList<QScreen*> screens = qApp->screens();
0262 
0263     QString info;
0264     info.append("Display Information");
0265     info.append("\nNumber of screens: ").append(QString::number(screens.size()));
0266 
0267     for (int i = 0; i < screens.size(); ++i ) {
0268         QScreen *screen = screens[i];
0269         info.append("\n\tScreen: ").append(QString::number(i));
0270         info.append("\n\t\tName: ").append(screen->name());
0271         info.append("\n\t\tDepth: ").append(QString::number(screen->depth()));
0272         info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio()));
0273         info.append("\n\t\tPhysical DPI").append(QString::number(screen->physicalDotsPerInch()));
0274         info.append("\n\t\tLogical DPI").append(QString::number(screen->logicalDotsPerInch()));
0275         info.append("\n\t\tPhysical Size: ").append(QString::number(screen->physicalSize().width()))
0276                 .append(", ")
0277                 .append(QString::number(screen->physicalSize().height()));
0278         info.append("\n\t\tPosition: ").append(QString::number(screen->geometry().x()))
0279                 .append(", ")
0280                 .append(QString::number(screen->geometry().y()));
0281         info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width()))
0282                 .append("x")
0283                 .append(QString::number(screen->geometry().height()));
0284         info.append("\n\t\tManufacturer: ").append(screen->manufacturer());
0285         info.append("\n\t\tModel: ").append(screen->model());
0286         info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate()));
0287 
0288     }
0289     info.append("\n");
0290     return info;
0291 }
0292 
0293 void KisUsageLogger::rotateLog()
0294 {
0295     if (d->logFile.exists()) {
0296         {
0297             // Check for CLOSING SESSION
0298             d->logFile.open(QFile::ReadOnly);
0299             QString log = QString::fromUtf8(d->logFile.readAll());
0300             if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) {
0301                 log.append("\nKRITA DID NOT CLOSE CORRECTLY\n");
0302                 QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log");
0303                 if (QFileInfo(crashLog).exists()) {
0304                     QFile f(crashLog);
0305                     f.open(QFile::ReadOnly);
0306                     QString crashes = QString::fromUtf8(f.readAll());
0307                     f.close();
0308 
0309                     QStringList crashlist = crashes.split("-------------------");
0310                     log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size()));
0311 
0312                     if (crashes.size() > 0) {
0313                         log.append(crashlist.last());
0314                     }
0315                 }
0316                 d->logFile.close();
0317                 d->logFile.open(QFile::WriteOnly);
0318                 d->logFile.write(log.toUtf8());
0319             }
0320             d->logFile.flush();
0321             d->logFile.close();
0322         }
0323 
0324         {
0325             // Rotate
0326             d->logFile.open(QFile::ReadOnly);
0327             QString log = QString::fromUtf8(d->logFile.readAll());
0328             d->logFile.close();
0329             QStringList logItems = log.split("SESSION:");
0330             QStringList keptItems;
0331             int sectionCount = logItems.size();
0332             if (sectionCount > s_maxLogs) {
0333                 for (int i = sectionCount - s_maxLogs; i < sectionCount; ++i) {
0334                     if (logItems.size() > i ) {
0335                         keptItems.append(logItems[i]);
0336                     }
0337                 }
0338 
0339                 d->logFile.open(QFile::WriteOnly);
0340                 d->logFile.write(keptItems.join("\nSESSION:").toUtf8());
0341                 d->logFile.flush();
0342                 d->logFile.close();
0343             }
0344         }
0345 
0346 
0347     }
0348 }
0349