1 /*=========================================================================
2 
3   Library:   CTK
4 
5   Copyright (c) Kitware Inc.
6 
7   Licensed under the Apache License, Version 2.0 (the "License");
8   you may not use this file except in compliance with the License.
9   You may obtain a copy of the License at
10 
11       http://www.apache.org/licenses/LICENSE-2.0.txt
12 
13   Unless required by applicable law or agreed to in writing, software
14   distributed under the License is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18 
19 =========================================================================*/
20 
21 // Qt includes
22 #include <QCoreApplication>
23 #include <QDateTime>
24 #include <QDebug>
25 #include <QFile>
26 #include <QMetaEnum>
27 #include <QMetaType>
28 #include <QMutexLocker>
29 #include <QPointer>
30 #include <QStandardItem>
31 #include <QThread>
32 
33 // CTK includes
34 #include "ctkErrorLogContext.h"
35 #include "ctkErrorLogModel.h"
36 #include "ctkErrorLogAbstractMessageHandler.h"
37 #include "ctkFileLogger.h"
38 
39 
40 // --------------------------------------------------------------------------
41 // ctkErrorLogModelPrivate
42 
43 // --------------------------------------------------------------------------
44 class ctkErrorLogModelPrivate
45 {
46   Q_DECLARE_PUBLIC(ctkErrorLogModel);
47 protected:
48   ctkErrorLogModel* const q_ptr;
49 public:
50   ctkErrorLogModelPrivate(ctkErrorLogModel& object);
51   ~ctkErrorLogModelPrivate();
52 
53   void init();
54 
55   void setMessageHandlerConnection(ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous);
56 
57   QStandardItemModel StandardItemModel;
58 
59   QHash<QString, ctkErrorLogAbstractMessageHandler*> RegisteredHandlers;
60 
61   ctkErrorLogLevel::LogLevels CurrentLogLevelFilter;
62 
63   bool LogEntryGrouping;
64   bool AsynchronousLogging;
65   bool AddingEntry;
66 
67   ctkErrorLogLevel ErrorLogLevel;
68 
69   ctkErrorLogTerminalOutput StdErrTerminalOutput;
70   ctkErrorLogTerminalOutput StdOutTerminalOutput;
71 
72   ctkFileLogger FileLogger;
73   QString FileLoggingPattern;
74 };
75 
76 // --------------------------------------------------------------------------
77 // ctkErrorLogModelPrivate methods
78 
79 // --------------------------------------------------------------------------
ctkErrorLogModelPrivate(ctkErrorLogModel & object)80 ctkErrorLogModelPrivate::ctkErrorLogModelPrivate(ctkErrorLogModel& object)
81   : q_ptr(&object)
82 {
83   qRegisterMetaType<ctkErrorLogContext>("ctkErrorLogContext");
84   this->StandardItemModel.setColumnCount(ctkErrorLogModel::MaxColumn);
85   this->LogEntryGrouping = false;
86   this->AsynchronousLogging = true;
87   this->AddingEntry = false;
88   this->FileLogger.setEnabled(false);
89   this->FileLoggingPattern = "[%{level}][%{origin}] %{timestamp} [%{category}] (%{file}:%{line}) - %{msg}";
90 }
91 
92 // --------------------------------------------------------------------------
~ctkErrorLogModelPrivate()93 ctkErrorLogModelPrivate::~ctkErrorLogModelPrivate()
94 {
95   foreach(const QString& handlerName, this->RegisteredHandlers.keys())
96     {
97     ctkErrorLogAbstractMessageHandler * msgHandler =
98         this->RegisteredHandlers.value(handlerName);
99     Q_ASSERT(msgHandler);
100     msgHandler->setEnabled(false);
101     delete msgHandler;
102     }
103 }
104 
105 // --------------------------------------------------------------------------
init()106 void ctkErrorLogModelPrivate::init()
107 {
108   Q_Q(ctkErrorLogModel);
109   q->setDynamicSortFilter(true);
110   //
111   // WARNING - Using a QSortFilterProxyModel slows down the insertion of rows by a factor 10
112   //
113   q->setSourceModel(&this->StandardItemModel);
114   q->setFilterKeyColumn(ctkErrorLogModel::LogLevelColumn);
115 }
116 
117 // --------------------------------------------------------------------------
setMessageHandlerConnection(ctkErrorLogAbstractMessageHandler * msgHandler,bool asynchronous)118 void ctkErrorLogModelPrivate::setMessageHandlerConnection(
119     ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous)
120 {
121   Q_Q(ctkErrorLogModel);
122 
123   msgHandler->disconnect();
124 
125   QObject::connect(msgHandler,
126         SIGNAL(messageHandled(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,ctkErrorLogContext,QString)),
127         q, SLOT(addEntry(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,ctkErrorLogContext,QString)),
128         asynchronous ? Qt::QueuedConnection : Qt::BlockingQueuedConnection);
129 }
130 
131 // --------------------------------------------------------------------------
132 // ctkErrorLogModel methods
133 
134 //------------------------------------------------------------------------------
ctkErrorLogModel(QObject * parentObject)135 ctkErrorLogModel::ctkErrorLogModel(QObject * parentObject)
136   : Superclass(parentObject)
137   , d_ptr(new ctkErrorLogModelPrivate(*this))
138 {
139   Q_D(ctkErrorLogModel);
140 
141   d->init();
142 }
143 
144 //------------------------------------------------------------------------------
~ctkErrorLogModel()145 ctkErrorLogModel::~ctkErrorLogModel()
146 {
147 }
148 
149 //------------------------------------------------------------------------------
registerMsgHandler(ctkErrorLogAbstractMessageHandler * msgHandler)150 bool ctkErrorLogModel::registerMsgHandler(ctkErrorLogAbstractMessageHandler * msgHandler)
151 {
152   Q_D(ctkErrorLogModel);
153   if (!msgHandler)
154     {
155     return false;
156     }
157   if (d->RegisteredHandlers.keys().contains(msgHandler->handlerName()))
158     {
159     return false;
160     }
161 
162   d->setMessageHandlerConnection(msgHandler, d->AsynchronousLogging);
163 
164   msgHandler->setTerminalOutput(ctkErrorLogTerminalOutput::StandardError, &d->StdErrTerminalOutput);
165   msgHandler->setTerminalOutput(ctkErrorLogTerminalOutput::StandardOutput, &d->StdOutTerminalOutput);
166 
167   d->RegisteredHandlers.insert(msgHandler->handlerName(), msgHandler);
168   return true;
169 }
170 
171 //------------------------------------------------------------------------------
msgHandlerNames() const172 QStringList ctkErrorLogModel::msgHandlerNames()const
173 {
174   Q_D(const ctkErrorLogModel);
175   return d->RegisteredHandlers.keys();
176 }
177 
178 //------------------------------------------------------------------------------
msgHandlerEnabled(const QString & handlerName) const179 bool ctkErrorLogModel::msgHandlerEnabled(const QString& handlerName) const
180 {
181   Q_D(const ctkErrorLogModel);
182   if (!d->RegisteredHandlers.keys().contains(handlerName))
183     {
184     return false;
185     }
186   return d->RegisteredHandlers.value(handlerName)->enabled();
187 }
188 
189 //------------------------------------------------------------------------------
setMsgHandlerEnabled(const QString & handlerName,bool enabled)190 void ctkErrorLogModel::setMsgHandlerEnabled(const QString& handlerName, bool enabled)
191 {
192   Q_D(ctkErrorLogModel);
193   if (!d->RegisteredHandlers.keys().contains(handlerName))
194     {
195 //    qCritical() << "Failed to enable/disable message handler " << handlerName
196 //                << "-  Handler not registered !";
197     return;
198     }
199   d->RegisteredHandlers.value(handlerName)->setEnabled(enabled);
200 }
201 
202 //------------------------------------------------------------------------------
msgHandlerEnabled() const203 QStringList ctkErrorLogModel::msgHandlerEnabled() const
204 {
205   Q_D(const ctkErrorLogModel);
206   QStringList msgHandlers;
207   foreach(const QString& handlerName, d->RegisteredHandlers.keys())
208     {
209     if (d->RegisteredHandlers.value(handlerName)->enabled())
210       {
211       msgHandlers << handlerName;
212       }
213     }
214   return msgHandlers;
215 }
216 
217 //------------------------------------------------------------------------------
setMsgHandlerEnabled(const QStringList & handlerNames)218 void ctkErrorLogModel::setMsgHandlerEnabled(const QStringList& handlerNames)
219 {
220   foreach(const QString& handlerName, handlerNames)
221     {
222     this->setMsgHandlerEnabled(handlerName, true);
223     }
224 }
225 
226 //------------------------------------------------------------------------------
enableAllMsgHandler()227 void ctkErrorLogModel::enableAllMsgHandler()
228 {
229   this->setAllMsgHandlerEnabled(true);
230 }
231 
232 //------------------------------------------------------------------------------
disableAllMsgHandler()233 void ctkErrorLogModel::disableAllMsgHandler()
234 {
235   this->setAllMsgHandlerEnabled(false);
236 }
237 
238 //------------------------------------------------------------------------------
setAllMsgHandlerEnabled(bool enabled)239 void ctkErrorLogModel::setAllMsgHandlerEnabled(bool enabled)
240 {
241   Q_D(ctkErrorLogModel);
242   foreach(const QString& msgHandlerName, d->RegisteredHandlers.keys())
243     {
244     this->setMsgHandlerEnabled(msgHandlerName, enabled);
245     }
246 }
247 
248 //------------------------------------------------------------------------------
terminalOutputs() const249 ctkErrorLogTerminalOutput::TerminalOutputs ctkErrorLogModel::terminalOutputs()const
250 {
251   Q_D(const ctkErrorLogModel);
252   ctkErrorLogTerminalOutput::TerminalOutputs currentTerminalOutputs;
253   currentTerminalOutputs |= d->StdErrTerminalOutput.enabled() ? ctkErrorLogTerminalOutput::StandardError : ctkErrorLogTerminalOutput::None;
254   currentTerminalOutputs |= d->StdOutTerminalOutput.enabled() ? ctkErrorLogTerminalOutput::StandardOutput : ctkErrorLogTerminalOutput::None;
255   return currentTerminalOutputs;
256 }
257 
258 //------------------------------------------------------------------------------
setTerminalOutputs(const ctkErrorLogTerminalOutput::TerminalOutputs & terminalOutput)259 void ctkErrorLogModel::setTerminalOutputs(
260     const ctkErrorLogTerminalOutput::TerminalOutputs& terminalOutput)
261 {
262   Q_D(ctkErrorLogModel);
263   d->StdErrTerminalOutput.setEnabled(terminalOutput & ctkErrorLogTerminalOutput::StandardOutput);
264   d->StdOutTerminalOutput.setEnabled(terminalOutput & ctkErrorLogTerminalOutput::StandardError);
265 }
266 
267 //------------------------------------------------------------------------------
addEntry(const QDateTime & currentDateTime,const QString & threadId,ctkErrorLogLevel::LogLevel logLevel,const QString & origin,const ctkErrorLogContext & context,const QString & text)268 void ctkErrorLogModel::addEntry(const QDateTime& currentDateTime, const QString& threadId,
269                                 ctkErrorLogLevel::LogLevel logLevel,
270                                 const QString& origin, const ctkErrorLogContext &context, const QString &text)
271 {
272   Q_D(ctkErrorLogModel);
273 
274   if (d->AddingEntry)
275     {
276     return;
277     }
278 
279   d->AddingEntry = true;
280 
281   QString timeFormat("dd.MM.yyyy hh:mm:ss");
282 
283   bool groupEntry = false;
284   if (d->LogEntryGrouping)
285     {
286     int lastRowIndex = d->StandardItemModel.rowCount() - 1;
287 
288     QString lastRowThreadId = this->logEntryData(lastRowIndex, Self::ThreadIdColumn).toString();
289     bool threadIdMatched = threadId == lastRowThreadId;
290 
291     QString lastRowLogLevel = this->logEntryData(lastRowIndex, Self::LogLevelColumn).toString();
292     bool logLevelMatched = d->ErrorLogLevel(logLevel) == lastRowLogLevel;
293 
294     QString lastRowOrigin = this->logEntryData(lastRowIndex, Self::OriginColumn).toString();
295     bool originMatched = origin == lastRowOrigin;
296 
297     QDateTime lastRowDateTime =
298         QDateTime::fromString(this->logEntryData(lastRowIndex, Self::TimeColumn).toString(), timeFormat);
299     int groupingIntervalInMsecs = 1000;
300     bool withinGroupingInterval = lastRowDateTime.time().msecsTo(currentDateTime.time()) <= groupingIntervalInMsecs;
301 
302     groupEntry = threadIdMatched && logLevelMatched && originMatched && withinGroupingInterval;
303     }
304 
305   if (!groupEntry)
306     {
307     QList<QStandardItem*> itemList;
308 
309     // Time item
310     QStandardItem * timeItem = new QStandardItem(currentDateTime.toString(timeFormat));
311     timeItem->setEditable(false);
312     itemList << timeItem;
313 
314     // ThreadId item
315     QStandardItem * threadIdItem = new QStandardItem(threadId);
316     threadIdItem->setEditable(false);
317     itemList << threadIdItem;
318 
319     // LogLevel item
320     QStandardItem * logLevelItem = new QStandardItem(d->ErrorLogLevel(logLevel));
321     logLevelItem->setEditable(false);
322     itemList << logLevelItem;
323 
324     // Origin item
325     QStandardItem * originItem = new QStandardItem(origin);
326     originItem->setEditable(false);
327     itemList << originItem;
328 
329     // Description item
330     QStandardItem * descriptionItem = new QStandardItem();
331     QString descriptionText(text);
332     descriptionItem->setData(descriptionText.left(160).append((descriptionText.size() > 160) ? "..." : ""), Qt::DisplayRole);
333     descriptionItem->setData(descriptionText, ctkErrorLogModel::DescriptionTextRole);
334     descriptionItem->setEditable(false);
335     itemList << descriptionItem;
336 
337     d->StandardItemModel.invisibleRootItem()->appendRow(itemList);
338     }
339   else
340     {
341     // Retrieve description associated with last row
342     QModelIndex lastRowDescriptionIndex =
343         d->StandardItemModel.index(d->StandardItemModel.rowCount() - 1, ctkErrorLogModel::DescriptionColumn);
344 
345     QStringList updatedDescription;
346     updatedDescription << lastRowDescriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
347     updatedDescription << text;
348 
349     d->StandardItemModel.setData(lastRowDescriptionIndex, updatedDescription.join("\n"),
350                                  ctkErrorLogModel::DescriptionTextRole);
351 
352     // Append '...' to displayText if needed
353     QString displayText = lastRowDescriptionIndex.data().toString();
354     if (!displayText.endsWith("..."))
355       {
356       d->StandardItemModel.setData(lastRowDescriptionIndex, displayText.append("..."), Qt::DisplayRole);
357       }
358     }
359 
360   d->AddingEntry = false;
361 
362   QString fileLogText = d->FileLoggingPattern;
363   fileLogText.replace("%{level}", d->ErrorLogLevel(logLevel).toUpper());
364   fileLogText.replace("%{timestamp}", currentDateTime.toString(timeFormat));
365   fileLogText.replace("%{origin}", origin);
366   fileLogText.replace("%{pid}", QString("%1").arg(QCoreApplication::applicationPid()));
367   fileLogText.replace("%{threadid}", threadId);
368   fileLogText.replace("%{function}", context.Function);
369   fileLogText.replace("%{line}", QString("%1").arg(context.Line));
370   fileLogText.replace("%{file}", context.File);
371   fileLogText.replace("%{category}", context.Category);
372   fileLogText.replace("%{msg}", context.Message);
373   d->FileLogger.logMessage(fileLogText.trimmed());
374 
375   emit this->entryAdded(logLevel);
376 }
377 
378 //------------------------------------------------------------------------------
clear()379 void ctkErrorLogModel::clear()
380 {
381   Q_D(ctkErrorLogModel);
382   d->StandardItemModel.invisibleRootItem()->removeRows(0, d->StandardItemModel.rowCount());
383 }
384 
385 //------------------------------------------------------------------------------
filterEntry(const ctkErrorLogLevel::LogLevels & logLevel,bool disableFilter)386 void ctkErrorLogModel::filterEntry(const ctkErrorLogLevel::LogLevels& logLevel,
387                                    bool disableFilter)
388 {
389   Q_D(ctkErrorLogModel);
390 
391   QStringList patterns;
392   if (!this->filterRegExp().pattern().isEmpty())
393     {
394     patterns << this->filterRegExp().pattern().split("|");
395     }
396   patterns.removeAll(d->ErrorLogLevel(ctkErrorLogLevel::None));
397 
398 //  foreach(QString s, patterns)
399 //    {
400 //    std::cout << "pattern:" << qPrintable(s) << std::endl;
401 //    }
402 
403   QMetaEnum logLevelEnum = d->ErrorLogLevel.metaObject()->enumerator(0);
404   Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
405 
406   // Loop over enum values and append associated name to 'patterns' if
407   // it has been specified within 'logLevel'
408   for (int i = 1; i < logLevelEnum.keyCount(); ++i)
409     {
410     int aLogLevel = logLevelEnum.value(i);
411     if (logLevel & aLogLevel)
412       {
413       QString logLevelAsString = d->ErrorLogLevel(static_cast<ctkErrorLogLevel::LogLevel>(aLogLevel));
414       if (!disableFilter)
415         {
416         patterns << logLevelAsString;
417         d->CurrentLogLevelFilter |= static_cast<ctkErrorLogLevel::LogLevels>(aLogLevel);
418         }
419       else
420         {
421         patterns.removeAll(logLevelAsString);
422         d->CurrentLogLevelFilter ^= static_cast<ctkErrorLogLevel::LogLevels>(aLogLevel);
423         }
424       }
425     }
426 
427   if (patterns.isEmpty())
428     {
429     // If there are no patterns, let's filter with the None level so that
430     // all entries are filtered out.
431     patterns << d->ErrorLogLevel(ctkErrorLogLevel::None);
432     }
433 
434   bool filterChanged = true;
435   QStringList currentPatterns = this->filterRegExp().pattern().split("|");
436   if (currentPatterns.count() == patterns.count())
437     {
438     foreach(const QString& p, patterns)
439       {
440       currentPatterns.removeAll(p);
441       }
442     filterChanged = currentPatterns.count() > 0;
443     }
444 
445   this->setFilterRegExp(patterns.join("|"));
446 
447   if (filterChanged)
448     {
449     emit this->logLevelFilterChanged();
450     }
451 }
452 
453 //------------------------------------------------------------------------------
logLevelFilter() const454 ctkErrorLogLevel::LogLevels ctkErrorLogModel::logLevelFilter()const
455 {
456   Q_D(const ctkErrorLogModel);
457   QMetaEnum logLevelEnum = d->ErrorLogLevel.metaObject()->enumerator(0);
458   Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
459 
460   ctkErrorLogLevel::LogLevels filter = ctkErrorLogLevel::Unknown;
461   foreach(const QString& filterAsString, this->filterRegExp().pattern().split("|"))
462     {
463     filter |= static_cast<ctkErrorLogLevel::LogLevels>(logLevelEnum.keyToValue(filterAsString.toLatin1()));
464     }
465   return filter;
466 }
467 
468 //------------------------------------------------------------------------------
logEntryGrouping() const469 bool ctkErrorLogModel::logEntryGrouping()const
470 {
471   Q_D(const ctkErrorLogModel);
472   return d->LogEntryGrouping;
473 }
474 
475 //------------------------------------------------------------------------------
setLogEntryGrouping(bool value)476 void ctkErrorLogModel::setLogEntryGrouping(bool value)
477 {
478   Q_D(ctkErrorLogModel);
479   d->LogEntryGrouping = value;
480 }
481 
482 //------------------------------------------------------------------------------
asynchronousLogging() const483 bool ctkErrorLogModel::asynchronousLogging()const
484 {
485   Q_D(const ctkErrorLogModel);
486   return d->AsynchronousLogging;
487 }
488 
489 //------------------------------------------------------------------------------
setAsynchronousLogging(bool value)490 void ctkErrorLogModel::setAsynchronousLogging(bool value)
491 {
492   Q_D(ctkErrorLogModel);
493   if (d->AsynchronousLogging == value)
494     {
495     return;
496     }
497 
498   foreach(const QString& handlerName, d->RegisteredHandlers.keys())
499     {
500     d->setMessageHandlerConnection(
501           d->RegisteredHandlers.value(handlerName), value);
502     }
503 
504   d->AsynchronousLogging = value;
505 }
506 
507 // --------------------------------------------------------------------------
filePath() const508 QString ctkErrorLogModel::filePath()const
509 {
510   Q_D(const ctkErrorLogModel);
511   return d->FileLogger.filePath();
512 }
513 
514 // --------------------------------------------------------------------------
setFilePath(const QString & filePath)515 void ctkErrorLogModel::setFilePath(const QString& filePath)
516 {
517   Q_D(ctkErrorLogModel);
518   return d->FileLogger.setFilePath(filePath);
519 }
520 
521 // --------------------------------------------------------------------------
numberOfFilesToKeep() const522 int ctkErrorLogModel::numberOfFilesToKeep()const
523 {
524   Q_D(const ctkErrorLogModel);
525   return d->FileLogger.numberOfFilesToKeep();
526 }
527 
528 // --------------------------------------------------------------------------
setNumberOfFilesToKeep(int value)529 void ctkErrorLogModel::setNumberOfFilesToKeep(int value)
530 {
531   Q_D(ctkErrorLogModel);
532   return d->FileLogger.setNumberOfFilesToKeep(value);
533 }
534 
535 // --------------------------------------------------------------------------
fileLoggingEnabled() const536 bool ctkErrorLogModel::fileLoggingEnabled()const
537 {
538   Q_D(const ctkErrorLogModel);
539   return d->FileLogger.enabled();
540 }
541 
542 // --------------------------------------------------------------------------
setFileLoggingEnabled(bool value)543 void ctkErrorLogModel::setFileLoggingEnabled(bool value)
544 {
545   Q_D(ctkErrorLogModel);
546   d->FileLogger.setEnabled(value);
547 }
548 
549 // --------------------------------------------------------------------------
fileLoggingPattern() const550 QString ctkErrorLogModel::fileLoggingPattern()const
551 {
552   Q_D(const ctkErrorLogModel);
553   return d->FileLoggingPattern;
554 }
555 
556 // --------------------------------------------------------------------------
setFileLoggingPattern(const QString & value)557 void ctkErrorLogModel::setFileLoggingPattern(const QString& value)
558 {
559   Q_D(ctkErrorLogModel);
560   d->FileLoggingPattern = value;
561 }
562 
563 // --------------------------------------------------------------------------
logEntryData(int row,int column,int role) const564 QVariant ctkErrorLogModel::logEntryData(int row, int column, int role) const
565 {
566   Q_D(const ctkErrorLogModel);
567   if (column < 0 || column > Self::MaxColumn
568       || row < 0 || row > this->logEntryCount())
569     {
570     return QVariant();
571     }
572   QModelIndex rowDescriptionIndex = d->StandardItemModel.index(row, column);
573   return rowDescriptionIndex.data(role);
574 }
575 
576 // --------------------------------------------------------------------------
logEntryDescription(int row) const577 QString ctkErrorLogModel::logEntryDescription(int row) const
578 {
579   return this->logEntryData(row, Self::DescriptionColumn, Self::DescriptionTextRole).toString();
580 }
581 
582 // --------------------------------------------------------------------------
logEntryCount() const583 int ctkErrorLogModel::logEntryCount()const
584 {
585   Q_D(const ctkErrorLogModel);
586   return d->StandardItemModel.rowCount();
587 }
588