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 d->logFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
71 d->logFile.close();
72 }
73 else {
74 rotateLog();
75 }
76
77 d->logFile.open(QFile::Append | QFile::Text);
78 d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
79}
80
82{
83 if (d->active) {
84 close();
85 }
86}
87
89{
90 s_instance->d->active = true;
91
92 QString systemInfo = basicSystemInfo();
93 s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
94}
95
97{
98 QString systemInfo;
99
100 // NOTE: This is intentionally not translated!
101
102 // Krita version info
103 systemInfo.append("Krita\n");
104 systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
105#ifdef Q_OS_WIN
106 {
107 using namespace KisWindowsPackageUtils;
108 QString packageFamilyName;
109 QString packageFullName;
110 systemInfo.append("\n Installation type: ");
111 if (tryGetCurrentPackageFamilyName(&packageFamilyName) && tryGetCurrentPackageFullName(&packageFullName)) {
112 systemInfo.append("Store / MSIX package\n Family Name: ")
113 .append(packageFamilyName)
114 .append("\n Full Name: ")
115 .append(packageFullName);
116 } else {
117 systemInfo.append("installer / portable package");
118 }
119 }
120#endif
121#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
122 // Attribute does nothing on Qt6
123 systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
124#endif
125#ifdef Q_OS_MACOS
126 KisMacosEntitlements entitlements;
127 systemInfo.append("\n Sandbox: ").append((entitlements.sandbox()) ? "true" : "false");
128#endif
129 systemInfo.append("\n\n");
130
131 systemInfo.append("Qt\n");
132 systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR);
133 systemInfo.append("\n Version (loaded): ").append(qVersion());
134 systemInfo.append("\n\n");
135
136 // OS information
137 systemInfo.append("OS Information\n");
138 systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi());
139 systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
140 systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
141 systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType());
142 systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
143 systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
144 systemInfo.append("\n Product Type: ").append(QSysInfo::productType());
145 systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion());
146
147#ifdef Q_OS_ANDROID
148 QString manufacturer =
149 QAndroidJniObject::getStaticObjectField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").toString();
150 const QString model =
151 QAndroidJniObject::getStaticObjectField("android/os/Build", "MODEL", "Ljava/lang/String;").toString();
152 manufacturer[0] = manufacturer[0].toUpper();
153 systemInfo.append("\n Product Model: ").append(manufacturer + " " + model);
154#elif defined(Q_OS_LINUX)
155 systemInfo.append("\n Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP"));
156
157 systemInfo.append("\n Appimage build: ").append(qEnvironmentVariableIsSet("APPIMAGE") ? "Yes" : "No");
158#elif defined(Q_OS_WIN)
159 systemInfo.append("\n Result of IsWindows10OrGreater(): ").append(IsWindows10OrGreater() ? "Yes" : "No");
160#endif
161 systemInfo.append("\n\n");
162
163 return systemInfo;
164}
165
167{
168 if (!s_instance->d->active) {
169 return;
170 }
171 QString systemInfo;
172 systemInfo.append("Locale\n");
173 systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", "));
174 systemInfo.append("\n C locale: ").append(std::setlocale(LC_ALL, nullptr));
175 systemInfo.append("\n QLocale current: ").append(QLocale().bcp47Name());
176 systemInfo.append("\n QLocale system: ").append(QLocale::system().bcp47Name());
177 const QTextCodec *codecForLocale = QTextCodec::codecForLocale();
178 systemInfo.append("\n QTextCodec for locale: ").append(codecForLocale->name());
179#ifdef Q_OS_WIN
180 {
181 systemInfo.append("\n Process ACP: ");
182 CPINFOEXW cpInfo {};
183 if (GetCPInfoExW(CP_ACP, 0, &cpInfo)) {
184 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
185 } else {
186 // Shouldn't happen, but just in case
187 systemInfo.append(QString::number(GetACP()));
188 }
189 wchar_t lcData[2];
190 int result = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, lcData, sizeof(lcData) / sizeof(lcData[0]));
191 if (result == 2) {
192 systemInfo.append("\n System locale default ACP: ");
193 int systemACP = lcData[1] << 16 | lcData[0];
194 if (systemACP == CP_ACP) {
195 systemInfo.append("N/A");
196 } else if (GetCPInfoExW(systemACP, 0, &cpInfo)) {
197 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
198 } else {
199 // Shouldn't happen, but just in case
200 systemInfo.append(QString::number(systemACP));
201 }
202 }
203 }
204#endif
205 systemInfo.append("\n\n");
206 s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
207}
208
210{
211 log("CLOSING SESSION");
212 s_instance->d->active = false;
213 s_instance->d->logFile.flush();
214 s_instance->d->logFile.close();
215 s_instance->d->sysInfoFile.flush();
216 s_instance->d->sysInfoFile.close();
217}
218
219void KisUsageLogger::log(const QString &message)
220{
221 QMutexLocker locker(&s_instance->d->mutex);
222
223 if (!s_instance->d->active) return;
224 if (!s_instance->d->logFile.isOpen()) return;
225
226 s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
227 s_instance->d->logFile.write(": ");
228 s_instance->d->logFile.write(message.toUtf8());
229 s_instance->d->logFile.write("\n");
230 s_instance->d->logFile.flush();
231}
232
233void KisUsageLogger::writeSysInfo(const QString &message)
234{
235 QMutexLocker locker(&s_instance->d->mutex);
236
237 if (!s_instance->d->active) return;
238 if (!s_instance->d->sysInfoFile.isOpen()) return;
239
240 s_instance->d->sysInfoFile.write(message.toUtf8());
241 s_instance->d->sysInfoFile.write("\n");
242
243 s_instance->d->sysInfoFile.flush();
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}
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)