1 // Copyright (c) 2013, Razvan Petru
2 // All rights reserved.
3 
4 // Redistribution and use in source and binary forms, with or without modification,
5 // are permitted provided that the following conditions are met:
6 
7 // * Redistributions of source code must retain the above copyright notice, this
8 //   list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above copyright notice, this
10 //   list of conditions and the following disclaimer in the documentation and/or other
11 //   materials provided with the distribution.
12 // * The name of the contributors may not be used to endorse or promote products
13 //   derived from this software without specific prior written permission.
14 
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
24 // OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #include "QsLog.h"
27 #include "QsLogDest.h"
28 #ifdef QS_LOG_SEPARATE_THREAD
29 #include <QThread>
30 #include <QWaitCondition>
31 #include <queue>
32 #endif
33 #include <QMutex>
34 #include <QDateTime>
35 #include <QLatin1String>
36 #include <QtGlobal>
37 #include <cstdlib>
38 #include <stdexcept>
39 #include <algorithm>
40 #include <vector>
41 
42 namespace QsLogging
43 {
44 using DestinationList = std::vector<DestinationPtrU>;
45 
46 #ifdef QS_LOG_SEPARATE_THREAD
47 //! Messages can be enqueued from other threads and will be logged one by one.
48 //! Note: std::queue was used instead of QQueue because it accepts types missing operator=.
49 class LoggerThread : public QThread
50 {
51 public:
enqueue(const LogMessage & message)52     void enqueue(const LogMessage& message)
53     {
54         QMutexLocker locker(&mutex);
55         messageQueue.push(message);
56         waitCondition.wakeOne();
57     }
58 
requestStop()59     void requestStop()
60     {
61         QMutexLocker locker(&mutex);
62         requestInterruption();
63         waitCondition.wakeOne();
64     }
65 
66 protected:
run()67     virtual void run()
68     {
69         while (true) {
70             QMutexLocker locker(&mutex);
71             if (messageQueue.empty() && !isInterruptionRequested()) {
72                 waitCondition.wait(&mutex);
73             }
74             if (isInterruptionRequested()) {
75                 break;
76             }
77 
78             const LogMessage msg = messageQueue.front();
79             messageQueue.pop();
80             locker.unlock();
81             Logger::instance().write(msg);
82         }
83     }
84 
85 private:
86     QMutex mutex;
87     QWaitCondition waitCondition;
88     std::queue<LogMessage> messageQueue;
89 };
90 #endif
91 
92 
93 class LoggerImpl
94 {
95 public:
96     LoggerImpl();
97     ~LoggerImpl();
98 
99 #ifdef QS_LOG_SEPARATE_THREAD
100     bool shutDownLoggerThread();
101 
102     LoggerThread loggerThread;
103 #endif
104     QMutex logMutex;
105     Level level;
106     DestinationList destList;
107 };
108 
LoggerImpl()109 LoggerImpl::LoggerImpl()
110     : level(InfoLevel)
111 {
112     destList.reserve(2); // assume at least file + console
113 #ifdef QS_LOG_SEPARATE_THREAD
114     loggerThread.start(QThread::LowPriority);
115 #endif
116 }
117 
~LoggerImpl()118 LoggerImpl::~LoggerImpl()
119 {
120 #ifdef QS_LOG_SEPARATE_THREAD
121 #if defined(Q_OS_WIN) && defined(QSLOG_IS_SHARED_LIBRARY)
122     // Waiting on the thread here is too late and can lead to deadlocks. More details:
123     // * Another reason not to do anything scary in your DllMain:
124     //   https://blogs.msdn.microsoft.com/oldnewthing/20040128-00/?p=40853
125     // * Dynamic Link libraries best practices:
126     //   https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971%28v=vs.85%29.aspx#general_best_practices
127     Q_ASSERT(loggerThread.isFinished());
128     if (!loggerThread.isFinished()) {
129         qCritical("You must shut down the QsLog thread, otherwise deadlocks can occur!");
130     }
131 #endif
132     shutDownLoggerThread();
133 #endif
134 }
135 
136 #ifdef QS_LOG_SEPARATE_THREAD
shutDownLoggerThread()137 bool LoggerImpl::shutDownLoggerThread()
138 {
139     if (loggerThread.isFinished()) {
140         return true;
141     }
142 
143     loggerThread.requestStop();
144     return loggerThread.wait();
145 }
146 #endif
147 
148 
instance()149 Logger& Logger::instance()
150 {
151     static Logger instance;
152     return instance;
153 }
154 
155 // tries to extract the level from a string log message. If available, conversionSucceeded will
156 // contain the conversion result.
levelFromLogMessage(const QString & logMessage,bool * conversionSucceeded)157 Level Logger::levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded)
158 {
159     using namespace QsLogging;
160 
161     if (conversionSucceeded)
162         *conversionSucceeded = true;
163 
164     if (logMessage.startsWith(QLatin1String(LevelName(TraceLevel))))
165         return TraceLevel;
166     if (logMessage.startsWith(QLatin1String(LevelName(DebugLevel))))
167         return DebugLevel;
168     if (logMessage.startsWith(QLatin1String(LevelName(InfoLevel))))
169         return InfoLevel;
170     if (logMessage.startsWith(QLatin1String(LevelName(WarnLevel))))
171         return WarnLevel;
172     if (logMessage.startsWith(QLatin1String(LevelName(ErrorLevel))))
173         return ErrorLevel;
174     if (logMessage.startsWith(QLatin1String(LevelName(FatalLevel))))
175         return FatalLevel;
176 
177     if (conversionSucceeded)
178         *conversionSucceeded = false;
179     return OffLevel;
180 }
181 
182 Logger::~Logger() noexcept = default;
183 
184 #if defined(Q_OS_WIN)
shutDownLoggerThread()185 bool Logger::shutDownLoggerThread()
186 {
187 #ifdef QS_LOG_SEPARATE_THREAD
188     return d->shutDownLoggerThread();
189 #else
190     return true;
191 #endif
192 }
193 #endif
194 
addDestination(DestinationPtrU && destination)195 void Logger::addDestination(DestinationPtrU&& destination)
196 {
197     Q_ASSERT(destination.get());
198     QMutexLocker lock(&d->logMutex);
199     d->destList.emplace_back(std::move(destination));
200 }
201 
removeDestination(const QString & type)202 DestinationPtrU Logger::removeDestination(const QString &type)
203 {
204     QMutexLocker lock(&d->logMutex);
205 
206     const auto it = std::find_if(d->destList.begin(), d->destList.end(), [&type](const DestinationPtrU& dest){
207         return dest->type() == type;
208     });
209 
210     if (it != d->destList.end()) {
211         auto removed = std::move(*it);
212         d->destList.erase(it);
213         return removed;
214     }
215 
216     return DestinationPtrU();
217 }
218 
hasDestinationOfType(const char * type) const219 bool Logger::hasDestinationOfType(const char* type) const
220 {
221     QMutexLocker lock(&d->logMutex);
222     const QLatin1String latin1Type(type);
223     for (const auto& dest : d->destList) {
224         if (dest->type() == latin1Type) {
225             return true;
226         }
227     }
228 
229     return false;
230 }
231 
setLoggingLevel(Level newLevel)232 void Logger::setLoggingLevel(Level newLevel)
233 {
234     d->level = newLevel;
235 }
236 
loggingLevel() const237 Level Logger::loggingLevel() const
238 {
239     return d->level;
240 }
241 
~Helper()242 Logger::Helper::~Helper() noexcept
243 {
244     const LogMessage msg(buffer, QDateTime::currentDateTimeUtc(), level);
245     Logger::instance().enqueueWrite(msg);
246 }
247 
248 
Logger()249 Logger::Logger()
250     : d(new LoggerImpl)
251 {
252     qRegisterMetaType<LogMessage>("QsLogging::LogMessage");
253 }
254 
255 //! directs the message to the task queue or writes it directly
enqueueWrite(const LogMessage & message)256 void Logger::enqueueWrite(const LogMessage& message)
257 {
258 #ifdef QS_LOG_SEPARATE_THREAD
259     d->loggerThread.enqueue(message);
260 #else
261     write(message);
262 #endif
263 }
264 
265 //! Sends the message to all the destinations. The level for this message is passed in case
266 //! it's useful for processing in the destination.
write(const LogMessage & message)267 void Logger::write(const LogMessage& message)
268 {
269     QMutexLocker lock(&d->logMutex);
270     for (auto& dest : d->destList) {
271         dest->write(message);
272     }
273 }
274 
275 } // end namespace
276