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