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