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