ctkErrorLogModel.cpp 19 KB

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