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