1 /*
2 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "log.h"
8 #include <cstdlib>
9 #include <iostream>
10
11 #include <QDateTime>
12 #include <QDebug>
13 #include <QFile>
14 #include <QList>
15 #include <QMutex>
16 #include <QTextStream>
17
18 #include <KIO/CopyJob>
19
20 #include "autorotatelogjob.h"
21 #include "compressfilejob.h"
22 #include "error.h"
23 #include <interfaces/logmonitorinterface.h>
24 #include <util/fileops.h>
25
26 namespace bt
27 {
28 const Uint32 MAX_LOG_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
29
30 static void QtMessageOutput(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg);
31
32 class Log::Private
33 {
34 public:
35 Log *parent;
36 QTextStream *out;
37 QFile *fptr;
38 bool to_cout;
39 unsigned int filter;
40 QList<LogMonitorInterface *> monitors;
41 QString tmp;
42 QMutex mutex;
43 AutoRotateLogJob *rotate_job;
44
45 public:
Private(Log * parent)46 Private(Log *parent)
47 : parent(parent)
48 , out(nullptr)
49 , fptr(nullptr)
50 , to_cout(false)
51 , filter(0)
52 , rotate_job(nullptr)
53 {
54 }
55
~Private()56 ~Private()
57 {
58 cleanup();
59 }
60
cleanup()61 void cleanup()
62 {
63 delete out;
64 out = nullptr;
65
66 delete fptr;
67 fptr = nullptr;
68 }
69
setFilter(unsigned int f)70 void setFilter(unsigned int f)
71 {
72 filter = f;
73 }
74
rotateLogs(const QString & file)75 void rotateLogs(const QString &file)
76 {
77 if (bt::Exists(file + QStringLiteral("-10.gz")))
78 bt::Delete(file + QStringLiteral("-10.gz"), true);
79
80 // move all log files one up
81 for (Uint32 i = 10; i > 1; i--) {
82 QString prev = QStringLiteral("%1-%2.gz").arg(file).arg(i - 1);
83 QString curr = QStringLiteral("%1-%2.gz").arg(file).arg(i);
84 if (bt::Exists(prev))
85 QFile::rename(prev, curr);
86 }
87
88 // move current log to 1 and zip it
89 QFile::rename(file, file + QStringLiteral("-1"));
90 CompressFileJob *gzip = new CompressFileJob(file + QStringLiteral("-1"));
91 gzip->start();
92 }
93
setOutputFile(const QString & file,bool rotate,bool handle_qt_messages)94 void setOutputFile(const QString &file, bool rotate, bool handle_qt_messages)
95 {
96 QMutexLocker lock(&mutex);
97
98 if (handle_qt_messages)
99 qInstallMessageHandler(QtMessageOutput);
100
101 cleanup();
102
103 if (bt::Exists(file) && rotate)
104 rotateLogs(file);
105
106 fptr = new QFile(file);
107 if (!fptr->open(QIODevice::WriteOnly)) {
108 QString err = fptr->errorString();
109 std::cout << "Failed to open log file " << file.toLocal8Bit().constData() << ": " << err.toLocal8Bit().constData() << std::endl;
110 cleanup();
111 return;
112 }
113
114 out = new QTextStream(fptr);
115 }
116
write(const QString & line)117 void write(const QString &line)
118 {
119 tmp += line;
120 }
121
finishLine()122 void finishLine()
123 {
124 QString final = QDateTime::currentDateTime().toString() + QStringLiteral(": ") + tmp;
125
126 // only add stuff when we are not rotating the logs
127 // this could result in the loss of some messages
128 if (!rotate_job && fptr != nullptr) {
129 if (out)
130 *out << final << Qt::endl;
131
132 fptr->flush();
133 if (to_cout)
134 std::cout << final.toLocal8Bit().constData() << std::endl;
135 ;
136 }
137
138 if (monitors.count() > 0) {
139 QList<LogMonitorInterface *>::iterator i = monitors.begin();
140 while (i != monitors.end()) {
141 LogMonitorInterface *lmi = *i;
142 lmi->message(final, filter);
143 ++i;
144 }
145 }
146 tmp.clear();
147 }
148
endline()149 void endline()
150 {
151 finishLine();
152 if (fptr && fptr->size() > MAX_LOG_FILE_SIZE && !rotate_job) {
153 tmp = QStringLiteral("Log larger then 10 MB, rotating");
154 finishLine();
155 QString file = fptr->fileName();
156 fptr->close(); // close the log file
157 out->setDevice(nullptr);
158 rotateLogs(file);
159 logRotateDone();
160 }
161 }
162
logRotateDone()163 void logRotateDone()
164 {
165 fptr->open(QIODevice::WriteOnly);
166 out->setDevice(fptr);
167 rotate_job = nullptr;
168 }
169 };
170
Log()171 Log::Log()
172 {
173 priv = new Private(this);
174 }
175
~Log()176 Log::~Log()
177 {
178 qInstallMessageHandler(nullptr);
179 delete priv;
180 }
181
setOutputFile(const QString & file,bool rotate,bool handle_qt_messages)182 void Log::setOutputFile(const QString &file, bool rotate, bool handle_qt_messages)
183 {
184 priv->setOutputFile(file, rotate, handle_qt_messages);
185 }
186
addMonitor(LogMonitorInterface * m)187 void Log::addMonitor(LogMonitorInterface *m)
188 {
189 priv->monitors.append(m);
190 }
191
removeMonitor(LogMonitorInterface * m)192 void Log::removeMonitor(LogMonitorInterface *m)
193 {
194 int index = priv->monitors.indexOf(m);
195 if (index != -1)
196 priv->monitors.takeAt(index);
197 }
198
setOutputToConsole(bool on)199 void Log::setOutputToConsole(bool on)
200 {
201 priv->to_cout = on;
202 }
203
logRotateDone()204 void Log::logRotateDone()
205 {
206 priv->logRotateDone();
207 }
208
endl(Log & lg)209 Log &endl(Log &lg)
210 {
211 lg.priv->endline();
212 lg.priv->mutex.unlock(); // unlock after end of line
213 return lg;
214 }
215
operator <<(const QUrl & url)216 Log &Log::operator<<(const QUrl &url)
217 {
218 return operator<<(url.toString());
219 }
220
operator <<(const QString & s)221 Log &Log::operator<<(const QString &s)
222 {
223 priv->write(s);
224 return *this;
225 }
226
operator <<(const char * s)227 Log &Log::operator<<(const char *s)
228 {
229 priv->write(QString::fromUtf8(s));
230 return *this;
231 }
232
operator <<(Uint64 v)233 Log &Log::operator<<(Uint64 v)
234 {
235 return operator<<(QString::number(v));
236 }
237
operator <<(Int64 v)238 Log &Log::operator<<(Int64 v)
239 {
240 return operator<<(QString::number(v));
241 }
242
setFilter(unsigned int filter)243 void Log::setFilter(unsigned int filter)
244 {
245 priv->setFilter(filter);
246 }
247
lock()248 void Log::lock()
249 {
250 priv->mutex.lock();
251 }
252
Q_GLOBAL_STATIC(Log,global_log)253 Q_GLOBAL_STATIC(Log, global_log)
254
255 Log &Out(unsigned int arg)
256 {
257 global_log->setFilter(arg);
258 global_log->lock();
259 return *global_log;
260 }
261
InitLog(const QString & file,bool rotate,bool handle_qt_messages,bool to_stdout)262 void InitLog(const QString &file, bool rotate, bool handle_qt_messages, bool to_stdout)
263 {
264 global_log->setOutputFile(file, rotate, handle_qt_messages);
265 global_log->setOutputToConsole(to_stdout);
266 }
267
AddLogMonitor(LogMonitorInterface * m)268 void AddLogMonitor(LogMonitorInterface *m)
269 {
270 global_log->addMonitor(m);
271 }
272
RemoveLogMonitor(LogMonitorInterface * m)273 void RemoveLogMonitor(LogMonitorInterface *m)
274 {
275 global_log->removeMonitor(m);
276 }
277
QtMessageOutput(QtMsgType type,const QMessageLogContext &,const QString & msg)278 static void QtMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg)
279 {
280 switch (type) {
281 case QtDebugMsg:
282 Out(SYS_GEN | LOG_DEBUG) << "Qt Debug: " << msg << endl;
283 break;
284 case QtWarningMsg:
285 case QtInfoMsg:
286 Out(SYS_GEN | LOG_NOTICE) << "Qt Warning: " << msg << endl;
287 fprintf(stderr, "Warning: %s\n", msg.toUtf8().constData());
288 break;
289 case QtCriticalMsg:
290 Out(SYS_GEN | LOG_IMPORTANT) << "Qt Critical: " << msg << endl;
291 fprintf(stderr, "Critical: %s\n", msg.toUtf8().constData());
292 break;
293 case QtFatalMsg:
294 Out(SYS_GEN | LOG_IMPORTANT) << "Qt Fatal: " << msg << endl;
295 fprintf(stderr, "Fatal: %s\n", msg.toUtf8().constData());
296 abort();
297 break;
298 }
299 }
300 }
301