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