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