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