1 /*
2 * Copyright (c) 2019 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18 #include "KisUsageLogger.h"
19
20 #include <QScreen>
21 #include <QGlobalStatic>
22 #include <QDebug>
23 #include <QDateTime>
24 #include <QSysInfo>
25 #include <QStandardPaths>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QDesktopWidget>
29 #include <QClipboard>
30 #include <QThread>
31 #include <QApplication>
32 #include <klocalizedstring.h>
33 #include <KritaVersionWrapper.h>
34 #include <QGuiApplication>
35 #include <QStyle>
36 #include <QStyleFactory>
37
38 Q_GLOBAL_STATIC(KisUsageLogger, s_instance)
39
40 const QString KisUsageLogger::s_sectionHeader("================================================================================\n");
41
42 struct KisUsageLogger::Private {
43 bool active {false};
44 QFile logFile;
45 QFile sysInfoFile;
46 };
47
KisUsageLogger()48 KisUsageLogger::KisUsageLogger()
49 : d(new Private)
50 {
51 d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
52 d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log");
53
54 rotateLog();
55
56 d->logFile.open(QFile::Append | QFile::Text);
57 d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
58 }
59
~KisUsageLogger()60 KisUsageLogger::~KisUsageLogger()
61 {
62 if (d->active) {
63 close();
64 }
65 }
66
initialize()67 void KisUsageLogger::initialize()
68 {
69 s_instance->d->active = true;
70
71 QString systemInfo = basicSystemInfo();
72 s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
73 }
74
basicSystemInfo()75 QString KisUsageLogger::basicSystemInfo()
76 {
77 QString systemInfo;
78
79 // NOTE: This is intentionally not translated!
80
81 // Krita version info
82 systemInfo.append("Krita\n");
83 systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
84 systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", "));
85 systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
86 systemInfo.append("\n\n");
87
88 systemInfo.append("Qt\n");
89 systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR);
90 systemInfo.append("\n Version (loaded): ").append(qVersion());
91 systemInfo.append("\n\n");
92
93 // OS information
94 systemInfo.append("OS Information\n");
95 systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi());
96 systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
97 systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
98 systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType());
99 systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
100 systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
101 systemInfo.append("\n Product Type: ").append(QSysInfo::productType());
102 systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion());
103 #ifdef Q_OS_LINUX
104 systemInfo.append("\n Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP"));
105 #endif
106 systemInfo.append("\n\n");
107
108 return systemInfo;
109 }
110
close()111 void KisUsageLogger::close()
112 {
113 log("CLOSING SESSION");
114 s_instance->d->active = false;
115 s_instance->d->logFile.flush();
116 s_instance->d->logFile.close();
117 s_instance->d->sysInfoFile.flush();
118 s_instance->d->sysInfoFile.close();
119 }
120
log(const QString & message)121 void KisUsageLogger::log(const QString &message)
122 {
123 if (!s_instance->d->active) return;
124 if (!s_instance->d->logFile.isOpen()) return;
125
126 s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
127 s_instance->d->logFile.write(": ");
128 write(message);
129 }
130
write(const QString & message)131 void KisUsageLogger::write(const QString &message)
132 {
133 if (!s_instance->d->active) return;
134 if (!s_instance->d->logFile.isOpen()) return;
135
136 s_instance->d->logFile.write(message.toUtf8());
137 s_instance->d->logFile.write("\n");
138
139 s_instance->d->logFile.flush();
140 }
141
writeSysInfo(const QString & message)142 void KisUsageLogger::writeSysInfo(const QString &message)
143 {
144 if (!s_instance->d->active) return;
145 if (!s_instance->d->sysInfoFile.isOpen()) return;
146
147 s_instance->d->sysInfoFile.write(message.toUtf8());
148 s_instance->d->sysInfoFile.write("\n");
149
150 s_instance->d->sysInfoFile.flush();
151
152 }
153
154
writeHeader()155 void KisUsageLogger::writeHeader()
156 {
157 Q_ASSERT(s_instance->d->sysInfoFile.isOpen());
158 s_instance->d->logFile.write(s_sectionHeader.toUtf8());
159
160 QString sessionHeader = QString("SESSION: %1. Executing %2\n\n")
161 .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))
162 .arg(qApp->arguments().join(' '));
163
164 s_instance->d->logFile.write(sessionHeader.toUtf8());
165
166 QString KritaAndQtVersion;
167 KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true))
168 .append(", Qt version compiled: ").append(QT_VERSION_STR)
169 .append(", loaded: ").append(qVersion())
170 .append(". Process ID: ")
171 .append(QString::number(qApp->applicationPid())).append("\n");
172
173 KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n");
174 s_instance->d->logFile.write(KritaAndQtVersion.toUtf8());
175 s_instance->d->logFile.flush();
176 log(QString("Style: %1. Available styles: %2")
177 .arg(qApp->style()->objectName(),
178 QStyleFactory::keys().join(", ")));
179
180 }
181
screenInformation()182 QString KisUsageLogger::screenInformation()
183 {
184 QList<QScreen*> screens = qApp->screens();
185
186 QString info;
187 info.append("Display Information");
188 info.append("\nNumber of screens: ").append(QString::number(screens.size()));
189
190 for (int i = 0; i < screens.size(); ++i ) {
191 QScreen *screen = screens[i];
192 info.append("\n\tScreen: ").append(QString::number(i));
193 info.append("\n\t\tName: ").append(screen->name());
194 info.append("\n\t\tDepth: ").append(QString::number(screen->depth()));
195 info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio()));
196 info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width()))
197 .append("x")
198 .append(QString::number(screen->geometry().height()));
199 info.append("\n\t\tManufacturer: ").append(screen->manufacturer());
200 info.append("\n\t\tModel: ").append(screen->model());
201 info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate()));
202 }
203 info.append("\n");
204 return info;
205 }
206
rotateLog()207 void KisUsageLogger::rotateLog()
208 {
209 if (d->logFile.exists()) {
210 {
211 // Check for CLOSING SESSION
212 d->logFile.open(QFile::ReadOnly);
213 QString log = QString::fromUtf8(d->logFile.readAll());
214 if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) {
215 log.append("\nKRITA DID NOT CLOSE CORRECTLY\n");
216 QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log");
217 if (QFileInfo(crashLog).exists()) {
218 QFile f(crashLog);
219 f.open(QFile::ReadOnly);
220 QString crashes = QString::fromUtf8(f.readAll());
221 f.close();
222
223 QStringList crashlist = crashes.split("-------------------");
224 log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size()));
225
226 if (crashes.size() > 0) {
227 log.append(crashlist.last());
228 }
229 }
230 d->logFile.close();
231 d->logFile.open(QFile::WriteOnly);
232 d->logFile.write(log.toUtf8());
233 }
234 d->logFile.flush();
235 d->logFile.close();
236 }
237
238 {
239 // Rotate
240 d->logFile.open(QFile::ReadOnly);
241 QString log = QString::fromUtf8(d->logFile.readAll());
242 int sectionCount = log.count(s_sectionHeader);
243 int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length());
244 while(sectionCount >= s_maxLogs) {
245 log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex));
246 nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length());
247 sectionCount = log.count(s_sectionHeader);
248 }
249 d->logFile.close();
250 d->logFile.open(QFile::WriteOnly);
251 d->logFile.write(log.toUtf8());
252 d->logFile.flush();
253 d->logFile.close();
254 }
255
256
257 }
258 }
259
260