Krita Source Code Documentation
Loading...
Searching...
No Matches
KisUsageLogger.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "KisUsageLogger.h"
7
8#include <QScreen>
9#include <QGlobalStatic>
10#include <QDebug>
11#include <QDateTime>
12#include <QSysInfo>
13#include <QStandardPaths>
14#include <QFile>
15#include <QFileInfo>
16#include <QDir>
17#include <QScreen>
18#include <QClipboard>
19#include <QThread>
20#include <QApplication>
21#include <klocalizedstring.h>
22#include <KritaVersionWrapper.h>
23#include <QGuiApplication>
24#include <QStyle>
25#include <QStyleFactory>
26#include <QTextCodec>
27
28#ifdef Q_OS_WIN
30#include <windows.h>
31#include <versionhelpers.h>
32#endif
33
34#ifdef Q_OS_ANDROID
35#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
36#include <QtAndroidExtras/QtAndroid>
37#endif
38#endif
39
40#ifdef Q_OS_MACOS
42#endif
43
44#include <clocale>
45
47
48const QString KisUsageLogger::s_sectionHeader("================================================================================\n");
49
51 bool active {false};
52 QFile logFile;
54};
55
57 : d(new Private)
58{
59 if (!QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()) {
60 QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
61 }
62 d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
63 d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log");
64
65 QFileInfo fi(d->logFile.fileName());
66 if (fi.size() > 100 * 1000 * 1000) { // 100 mb seems a reasonable max
67 d->logFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
68 d->logFile.close();
69 }
70 else {
71 rotateLog();
72 }
73
74 d->logFile.open(QFile::Append | QFile::Text);
75 d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
76}
77
79{
80 if (d->active) {
81 close();
82 }
83}
84
86{
87 s_instance->d->active = true;
88
89 QString systemInfo = basicSystemInfo();
90 s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
91}
92
94{
95 QString systemInfo;
96
97 // NOTE: This is intentionally not translated!
98
99 // Krita version info
100 systemInfo.append("Krita\n");
101 systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
102#ifdef Q_OS_WIN
103 {
104 using namespace KisWindowsPackageUtils;
105 QString packageFamilyName;
106 QString packageFullName;
107 systemInfo.append("\n Installation type: ");
108 if (tryGetCurrentPackageFamilyName(&packageFamilyName) && tryGetCurrentPackageFullName(&packageFullName)) {
109 systemInfo.append("Store / MSIX package\n Family Name: ")
110 .append(packageFamilyName)
111 .append("\n Full Name: ")
112 .append(packageFullName);
113 } else {
114 systemInfo.append("installer / portable package");
115 }
116 }
117#endif
118 systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
119#ifdef Q_OS_MACOS
120 KisMacosEntitlements entitlements;
121 systemInfo.append("\n Sandbox: ").append((entitlements.sandbox()) ? "true" : "false");
122#endif
123 systemInfo.append("\n\n");
124
125 systemInfo.append("Qt\n");
126 systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR);
127 systemInfo.append("\n Version (loaded): ").append(qVersion());
128 systemInfo.append("\n\n");
129
130 // OS information
131 systemInfo.append("OS Information\n");
132 systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi());
133 systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
134 systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
135 systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType());
136 systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
137 systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
138 systemInfo.append("\n Product Type: ").append(QSysInfo::productType());
139 systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion());
140
141#ifdef Q_OS_ANDROID
142 QString manufacturer =
143 QAndroidJniObject::getStaticObjectField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").toString();
144 const QString model =
145 QAndroidJniObject::getStaticObjectField("android/os/Build", "MODEL", "Ljava/lang/String;").toString();
146 manufacturer[0] = manufacturer[0].toUpper();
147 systemInfo.append("\n Product Model: ").append(manufacturer + " " + model);
148#elif defined(Q_OS_LINUX)
149 systemInfo.append("\n Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP"));
150
151 systemInfo.append("\n Appimage build: ").append(qEnvironmentVariableIsSet("APPIMAGE") ? "Yes" : "No");
152#elif defined(Q_OS_WIN)
153 systemInfo.append("\n Result of IsWindows10OrGreater(): ").append(IsWindows10OrGreater() ? "Yes" : "No");
154#endif
155 systemInfo.append("\n\n");
156
157 return systemInfo;
158}
159
161{
162 if (!s_instance->d->active) {
163 return;
164 }
165 QString systemInfo;
166 systemInfo.append("Locale\n");
167 systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", "));
168 systemInfo.append("\n C locale: ").append(std::setlocale(LC_ALL, nullptr));
169 systemInfo.append("\n QLocale current: ").append(QLocale().bcp47Name());
170 systemInfo.append("\n QLocale system: ").append(QLocale::system().bcp47Name());
171 const QTextCodec *codecForLocale = QTextCodec::codecForLocale();
172 systemInfo.append("\n QTextCodec for locale: ").append(codecForLocale->name());
173#ifdef Q_OS_WIN
174 {
175 systemInfo.append("\n Process ACP: ");
176 CPINFOEXW cpInfo {};
177 if (GetCPInfoExW(CP_ACP, 0, &cpInfo)) {
178 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
179 } else {
180 // Shouldn't happen, but just in case
181 systemInfo.append(QString::number(GetACP()));
182 }
183 wchar_t lcData[2];
184 int result = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, lcData, sizeof(lcData) / sizeof(lcData[0]));
185 if (result == 2) {
186 systemInfo.append("\n System locale default ACP: ");
187 int systemACP = lcData[1] << 16 | lcData[0];
188 if (systemACP == CP_ACP) {
189 systemInfo.append("N/A");
190 } else if (GetCPInfoExW(systemACP, 0, &cpInfo)) {
191 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
192 } else {
193 // Shouldn't happen, but just in case
194 systemInfo.append(QString::number(systemACP));
195 }
196 }
197 }
198#endif
199 systemInfo.append("\n\n");
200 s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
201}
202
204{
205 log("CLOSING SESSION");
206 s_instance->d->active = false;
207 s_instance->d->logFile.flush();
208 s_instance->d->logFile.close();
209 s_instance->d->sysInfoFile.flush();
210 s_instance->d->sysInfoFile.close();
211}
212
213void KisUsageLogger::log(const QString &message)
214{
215 if (!s_instance->d->active) return;
216 if (!s_instance->d->logFile.isOpen()) return;
217
218 s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
219 s_instance->d->logFile.write(": ");
220 write(message);
221}
222
223void KisUsageLogger::write(const QString &message)
224{
225 if (!s_instance->d->active) return;
226 if (!s_instance->d->logFile.isOpen()) return;
227
228 s_instance->d->logFile.write(message.toUtf8());
229 s_instance->d->logFile.write("\n");
230
231 s_instance->d->logFile.flush();
232}
233
234void KisUsageLogger::writeSysInfo(const QString &message)
235{
236 if (!s_instance->d->active) return;
237 if (!s_instance->d->sysInfoFile.isOpen()) return;
238
239 s_instance->d->sysInfoFile.write(message.toUtf8());
240 s_instance->d->sysInfoFile.write("\n");
241
242 s_instance->d->sysInfoFile.flush();
243
244}
245
246
248{
249 Q_ASSERT(s_instance->d->sysInfoFile.isOpen());
250 s_instance->d->logFile.write(s_sectionHeader.toUtf8());
251
252 QString sessionHeader = QString("SESSION: %1. Executing %2\n\n")
253 .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))
254 .arg(qApp->arguments().join(' '));
255
256 s_instance->d->logFile.write(sessionHeader.toUtf8());
257
258 QString KritaAndQtVersion;
259 KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true))
260 .append(", Qt version compiled: ").append(QT_VERSION_STR)
261 .append(", loaded: ").append(qVersion())
262 .append(". Process ID: ")
263 .append(QString::number(qApp->applicationPid())).append("\n");
264
265 KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n");
266 s_instance->d->logFile.write(KritaAndQtVersion.toUtf8());
267 s_instance->d->logFile.flush();
268 log(QString("Style: %1. Available styles: %2")
269 .arg(qApp->style()->objectName(),
270 QStyleFactory::keys().join(", ")));
271
272}
273
275{
276 QList<QScreen*> screens = qApp->screens();
277
278 QString info;
279 info.append("Display Information");
280 info.append("\nNumber of screens: ").append(QString::number(screens.size()));
281
282 for (int i = 0; i < screens.size(); ++i ) {
283 QScreen *screen = screens[i];
284 info.append("\n\tScreen: ").append(QString::number(i));
285 info.append("\n\t\tName: ").append(screen->name());
286 info.append("\n\t\tDepth: ").append(QString::number(screen->depth()));
287 info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio()));
288 info.append("\n\t\tPhysical DPI").append(QString::number(screen->physicalDotsPerInch()));
289 info.append("\n\t\tLogical DPI").append(QString::number(screen->logicalDotsPerInch()));
290 info.append("\n\t\tPhysical Size: ").append(QString::number(screen->physicalSize().width()))
291 .append(", ")
292 .append(QString::number(screen->physicalSize().height()));
293 info.append("\n\t\tPosition: ").append(QString::number(screen->geometry().x()))
294 .append(", ")
295 .append(QString::number(screen->geometry().y()));
296 info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width()))
297 .append("x")
298 .append(QString::number(screen->geometry().height()));
299 info.append("\n\t\tManufacturer: ").append(screen->manufacturer());
300 info.append("\n\t\tModel: ").append(screen->model());
301 info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate()));
302 info.append("\n\t\tSerial Number: ").append(screen->serialNumber());
303
304 }
305 info.append("\n");
306 return info;
307}
308
310{
311 if (d->logFile.exists()) {
312 {
313 // Check for CLOSING SESSION
314 d->logFile.open(QFile::ReadOnly);
315 QString log = QString::fromUtf8(d->logFile.readAll());
316 if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) {
317 log.append("\nKRITA DID NOT CLOSE CORRECTLY\n");
318 QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log");
319 if (QFileInfo(crashLog).exists()) {
320 QFile f(crashLog);
321 f.open(QFile::ReadOnly);
322 QString crashes = QString::fromUtf8(f.readAll());
323 f.close();
324
325 QStringList crashlist = crashes.split("-------------------");
326 log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size()));
327
328 if (crashes.size() > 0) {
329 log.append(crashlist.last());
330 }
331 }
332 d->logFile.close();
333 d->logFile.open(QFile::WriteOnly);
334 d->logFile.write(log.toUtf8());
335 }
336 d->logFile.flush();
337 d->logFile.close();
338 }
339
340 {
341 // Rotate
342 d->logFile.open(QFile::ReadOnly);
343 QString log = QString::fromUtf8(d->logFile.readAll());
344 d->logFile.close();
345 QStringList logItems = log.split("SESSION:");
346 QStringList keptItems;
347 int sectionCount = logItems.size();
348 if (sectionCount > s_maxLogs) {
349 for (int i = sectionCount - s_maxLogs; i < sectionCount; ++i) {
350 if (logItems.size() > i ) {
351 keptItems.append(logItems[i]);
352 }
353 }
354
355 d->logFile.open(QFile::WriteOnly);
356 d->logFile.write(keptItems.join("\nSESSION:").toUtf8());
357 d->logFile.flush();
358 d->logFile.close();
359 }
360 }
361
362
363 }
364}
365
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
The KisUsageLogger class logs messages to a logfile.
const QScopedPointer< Private > d
static const int s_maxLogs
static void initialize()
static void log(const QString &message)
Logs with date/time.
static void writeHeader()
static QString screenInformation()
Returns information about all available screens.
static void writeLocaleSysInfo()
static void writeSysInfo(const QString &message)
Writes to the system information file and Krita log.
static void write(const QString &message)
Writes without date/time.
static void close()
static QString basicSystemInfo()
static const QString s_sectionHeader
KRITAVERSION_EXPORT QString versionString(bool checkGit=false)