LogViewer.cpp
Go to the documentation of this file.
1/*
2* This file is part of ArmarX.
3*
4* ArmarX is free software; you can redistribute it and/or modify
5* it under the terms of the GNU General Public License version 2 as
6* published by the Free Software Foundation.
7*
8* ArmarX is distributed in the hope that it will be useful, but
9* WITHOUT ANY WARRANTY; without even the implied warranty of
10* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11* GNU General Public License for more details.
12*
13* You should have received a copy of the GNU General Public License
14* along with this program. If not, see <http://www.gnu.org/licenses/>.
15*
16* @package ArmarX::
17* @author Mirko Waechter ( mirko.waechter at kit dot edu)
18* @date 2012
19* @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20* GNU General Public License
21*/
22
23#include "LogViewer.h"
24
25#include <ArmarXCore/interface/core/ManagedIceObjectDefinitions.h>
26
27#include <ArmarXGui/gui-plugins/LoggingPlugin/ui_FilterDialog.h>
28
29#include "FilterDialog.h"
30#include "LogTable.h"
31#include "LogTableModel.h"
32
33// C++ includes
34#include <algorithm>
35#include <iterator>
36#include <sstream>
37
38// Qt includes
39#include <boost/algorithm/string/regex.hpp>
40
41#include <QColorDialog>
42#include <QComboBox>
43#include <QInputDialog>
44#include <QListWidget>
45#include <QMainWindow>
46#include <QMenu>
47#include <QPixmap>
48#include <QScrollBar>
49#include <QShortcut>
50#include <QStatusBar>
51#include <QTimer>
52#include <QToolBar>
53
54
55#define USERROLE_LOGTABLEID Qt::UserRole + 1
56#define USERROLE_LOGTABLEPTR Qt::UserRole + 2
57#define USERROLE_BASENAME Qt::UserRole + 3
58#define REGEX_COLORS "\033\\[(\\d|\\w?)[;]?(\\d+)m"
59#define ALL_MESSAGES_FILTER "All Messages"
60
61namespace armarx
62{
64 logTable(NULL), loggingPaused(false), verbosityLevel(1), customToolbar(0)
65
66 {
67 qRegisterMetaType<Qt::Orientation>("Qt::Orientation");
68 qRegisterMetaType<std::string>("std::string");
69 ui.setupUi(getWidget());
70
71 for (int i = 0; i < eLogLevelCount; i++)
72 {
73 ui.cbVerbosityLevel->addItem(LogSender::levelToString((MessageTypeT)i).c_str());
74 }
75
76 ui.cbVerbosityLevel->setCurrentIndex(1);
77 // Keep the atomic mirror of the verbosity level in sync so the Ice-thread
78 // writeLog can read it without touching the QComboBox.
79 verbosityLevel = ui.cbVerbosityLevel->currentIndex();
80 connect(ui.cbVerbosityLevel,
81 QOverload<int>::of(&QComboBox::currentIndexChanged),
82 this,
83 [this](int index) { verbosityLevel = index; });
85 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
86 // Sorting stays off here: with it enabled, every setText() on an item -- i.e.
87 // every new-message badge update -- re-sorts the entire tree. The filter set only
88 // changes when a filter is added, so sortFilterList() is called explicitly there.
89 ui.lvFilters->setSortingEnabled(false);
90 sortFilterList();
91 pendingEntriesTimer = new QTimer(getWidget());
92 pendingEntriesTimer->setInterval(50);
93
94 connect(pendingEntriesTimer, SIGNAL(timeout()), this, SLOT(insertPendingEntries()));
95 connect(this, SIGNAL(componentConnected()), pendingEntriesTimer, SLOT(start()));
96
97 // The new-message badges do not need the ingest rate; refreshing them relayouts
98 // and repaints the filter tree.
99 filterBadgeTimer = new QTimer(getWidget());
100 filterBadgeTimer->setInterval(250);
101 connect(filterBadgeTimer, SIGNAL(timeout()), this, SLOT(updateFilterList()));
102 connect(this, SIGNAL(componentConnected()), filterBadgeTimer, SLOT(start()));
103
104
105 QList<int> sizes;
106 sizes.push_back(80);
107 sizes.push_back(400);
108 ui.splitter->setSizes(sizes);
109 addFilter(
110 "Warning+",
111 "Warning+",
112 "",
113 "",
114 "",
115 eWARN,
116 "",
117 "",
118 ""); // add additional filters after splitter->setSizes-> otherwise they have a width of NULL
119
120
121 qRegisterMetaType<LogMessage>("LogMessage");
122
123 // SIGNALS AND SLOTS CONNECTIONS
124 connect(
125 ui.edtLiveFilter, SIGNAL(textChanged(QString)), this, SLOT(performLiveFilter(QString)));
126 connect(
127 ui.edtLiveSearch, SIGNAL(textChanged(QString)), this, SLOT(performLiveSearch(QString)));
128 connect(ui.btnAddFilter, SIGNAL(clicked()), this, SLOT(addFilter()));
129 connect(ui.btnRemoveFilter, SIGNAL(clicked()), this, SLOT(removeSelectedFilter()));
130 connect(ui.btnPause, SIGNAL(toggled(bool)), this, SLOT(pauseLogging(bool)));
131 connect(ui.btnClearLog, SIGNAL(clicked()), this, SLOT(clearSelectedLog()));
132 connect(ui.btnClearAllLogs, SIGNAL(clicked()), this, SLOT(clearAllLogs()));
133 connect(ui.lvFilters,
134 SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
135 this,
136 SLOT(filterSelectionChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
137 connect(ui.lvFilters,
138 SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
139 this,
140 SLOT(editFilter(QTreeWidgetItem*, int)));
141 connect(
142 ui.cbSearchType, SIGNAL(currentIndexChanged(int)), this, SLOT(searchTypeChanged(int)));
143 connect(this, SIGNAL(updateFilterListSignal()), this, SLOT(updateFilterList()));
144 connect(ui.btnNextItem, SIGNAL(clicked()), this, SLOT(selectNextSearchResult()));
145 connect(ui.btnPreviousItem, SIGNAL(clicked()), this, SLOT(selectPreviousSearchResult()));
146
147 // Labels panel: mirror the pin registry, navigate on click, edit via context menu.
148 connect(&markerRegistry, &LogMarkerRegistry::markersChanged, this, &LogViewer::refreshLabelsPanel);
149 connect(ui.lvLabels, &QListWidget::itemClicked, this, &LogViewer::labelClicked);
150 connect(ui.lvLabels,
151 &QWidget::customContextMenuRequested,
152 this,
154 QShortcut* deleteLabelShortcut = new QShortcut(QKeySequence::Delete, ui.lvLabels);
155 deleteLabelShortcut->setContext(Qt::WidgetShortcut);
156 connect(deleteLabelShortcut, &QShortcut::activated, this, &LogViewer::removeSelectedLabel);
158
159 ui.edtLiveFilter->hide();
160 ui.edtLiveSearch->setFocus();
161 }
162
164 {
165 // ARMARX_VERBOSE << "~LogViewer";
166 }
167
168 void
170 {
171 ui.cbVerbosityLevel->setCurrentIndex(settings->value("verbosityLevel", 2).toInt());
172 ui.cbAutoComponentFilters->setChecked(settings->value("autoFilterAdding", true).toBool());
173 }
174
175 void
177 {
178
179 settings->setValue("verbosityLevel", ui.cbVerbosityLevel->currentIndex());
180
181 settings->setValue("autoFilterAdding", ui.cbAutoComponentFilters->isChecked());
182
183
184 // // saving filters
185
186 // QString filterName = ui.lvFilters->items()->data(USERROLE_LOGTABLEID).toString();
187 // for(unsigned int i= 0; i< logTable->getModel()->getFilters().size(); i++)
188 // {
189 // std::string columnName = logTable->getModel()->getFilters()[i].first;
190 // std::string filter = logTable->getModel()->getFilters()[i].second;
191 // if(columnName == ARMARX_LOG_COMPONENTSTR)
192 // filterDialog.ui->editComponent->setText(filter.c_str());
193 // else if(columnName == ARMARX_LOG_TAGSTR)
194 // filterDialog.ui->edtTag->setText(filter.c_str());
195 // else if(columnName == ARMARX_LOG_VERBOSITYSTR){
196
197 // filterDialog.ui->cbVerbosity->setCurrentIndex(QString(filter.c_str()).toInt());
198 // }
199 // else if(columnName == ARMARX_LOG_MESSAGESTR)
200 // filterDialog.ui->edtMessage->setText(filter.c_str());
201 // else if(columnName == ARMARX_LOG_FILESTR)
202 // filterDialog.ui->edtFile->setText(filter.c_str());
203 // else if(columnName == ARMARX_LOG_FUNCTIONSTR)
204 // filterDialog.ui->edtFunction->setText(filter.c_str());
205 // }
206 }
207
208 void
213
214 void
219
220 bool
222 {
223 // QInputDialog dialog;
224 // if(dialog.exec() == QDialog::Rejected)
225 // return false;
226
228 }
229
230 QString
231 LogViewer::loggingGroupNameToFilterName(const QString& loggingGroupName) const
232 {
233 return loggingGroupName.isEmpty() ? "" : ("#" + loggingGroupName);
234 }
235
236 void
237 LogViewer::sortFilterList()
238 {
239 ui.lvFilters->sortItems(0, Qt::AscendingOrder);
240 }
241
242 QString
243 LogViewer::autoFilterId(const QString& loggingGroupName, const QString& componentName) const
244 {
245 const QString filler =
246 (loggingGroupName.length() > 0 && componentName.length() > 0) ? "_" : "";
247 return loggingGroupNameToFilterName(loggingGroupName) + filler + componentName;
248 }
249
250 LogTable*
251 LogViewer::ensureAutoFilter(const QString& filterId,
252 const QString& loggingGroupName,
253 const QString& componentName,
254 bool mayCreate)
255 {
256 if (filterId.isEmpty())
257 {
258 return nullptr;
259 }
260
261 const auto it = autoFilterMap.find(filterId);
262 if (it != autoFilterMap.end())
263 {
264 return it.value();
265 }
266
267 if (!mayCreate || !ui.cbAutoComponentFilters->isChecked() || filterMap.count(filterId))
268 {
269 // Auto filters are switched off, or the id is already taken by a user-created
270 // filter -- which is a general filter and therefore already sees this message.
271 return nullptr;
272 }
273
274 const QString filterName = componentName.length() > 0
275 ? componentName
276 : loggingGroupNameToFilterName(loggingGroupName);
277 ARMARX_CHECK_EXPRESSION(!filterName.isEmpty());
278
279 LogTable* table = addFilter(
280 filterId, filterName, loggingGroupName, componentName, "", eDEBUG, "", "", "");
281 if (table)
282 {
283 // addFilter registers every new table as a general filter; this one can be
284 // routed to, so move it into the dispatch map.
285 generalFilters.erase(std::remove(generalFilters.begin(), generalFilters.end(), table),
286 generalFilters.end());
287 autoFilterMap.insert(filterId, table);
288 }
289 return table;
290 }
291
292 void
293 LogViewer::ingestBatch(LogTable* table, const std::vector<LogSeq>& seqs)
294 {
295 if (!table || seqs.empty())
296 {
297 return;
298 }
299
300 const QString liveFilter = table->getCurrentLiveFilter();
301 MessageType batchMaxLevel = eUNDEFINED;
302 const int rowsAdded = table->getModel()->addEntries(seqs, &batchMaxLevel);
303
304 if (!table->getModel()->isMaterialised())
305 {
306 // A hidden table builds no rows, so its rowsInserted never fires; feed the
307 // new-message badge from here instead.
308 table->notifyNewMessages(rowsAdded, batchMaxLevel);
309 return;
310 }
311
312 if (rowsAdded > 0 && !liveFilter.isEmpty())
313 {
314 // The new rows are always the last rowsAdded ones. The row count is re-read
315 // here because addEntries() may have trimmed the buffer, which shifts every
316 // index down.
317 const int count = table->getModel()->rowCount();
318 for (int r = std::max(0, count - rowsAdded); r < count; r++)
319 {
320 table->liveFilterRow(liveFilter, r);
321 }
322 }
323 }
324
325 void
329
330 void
331 LogViewer::write(const std::string& who,
332 Ice::Long time,
333 const std::string& tag,
334 MessageType severity,
335 const std::string& message,
336 const std::string& file,
337 Ice::Int line,
338 const std::string& function,
339 const Ice::Current&)
340 {
341 LogMessage msg;
342 msg.who = who;
343 msg.time = time;
344 msg.tag = tag;
345 msg.type = severity;
346 msg.what = message;
347 msg.file = file;
348 msg.line = line;
349 msg.function = function;
350
351 writeLog(msg);
352 }
353
354 void
355 LogViewer::writeLog(const LogMessage& msg, const Ice::Current&)
356 {
357 if (loggingPaused)
358 {
359 return;
360 }
361
362 // Runs on an Ice dispatch thread: use the mirrored verbosity level instead of
363 // reading the (GUI-thread-only) QComboBox. insertPendingEntries re-checks the
364 // level on the GUI thread anyway.
365 if (verbosityLevel.load() <= msg.type)
366 {
367 std::unique_lock lock(pendingEntriesMutex);
368 if (pendingEntries.size() >= maxPendingEntries)
369 {
370 // The GUI thread cannot keep up. Drop rather than let the backlog grow
371 // without bound, which would only make the next tick slower still. The
372 // count is surfaced in the status bar by updateFilterList().
373 ++droppedEntryCount;
374 return;
375 }
376 pendingEntries.push_back(msg);
377 }
378 }
379
380 void
381 LogViewer::performLiveFilter(QString filterStr, int startRow)
382 {
383 // if((IceUtil::Time::now() - lastLiveSearchEditChangeTime).toSeconds() < 1)
384 // return;
385 if (filterStr.length() == 0)
386 {
387 logTable->resetLiveFilter();
388 }
389 else if (filterStr.length() < 3)
390 {
391 return;
392 }
393
394 // LogSearch search(logTable);
395 // search.search(searchStr);
396 logTable->liveFilter(filterStr, startRow);
397 lastLiveSearchEditChangeTime = IceUtil::Time::now();
398 }
399
400 void
402 {
403 if (searchStr.length() == 0)
404 {
405 logTable->resetLiveSearch();
406 ui.btnNextItem->setEnabled(false);
407 ui.btnPreviousItem->setEnabled(false);
408 }
409 // else if(searchStr.length() < 3)
410 // return;
411 else
412 {
413 if (!logTable->liveSearch(searchStr))
414 {
415 getMainWindow()->statusBar()->showMessage(
416 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
417 "' in the log!",
418 5000);
419 ui.btnNextItem->setEnabled(false);
420 ui.btnPreviousItem->setEnabled(false);
421 }
422 else
423 {
424 ui.btnNextItem->setEnabled(true);
425 ui.btnPreviousItem->setEnabled(true);
426 }
427 }
428 }
429
430 void
432 {
433 if (logTable)
434 {
435 logTable->getModel()->clearData();
436 // See clearAllLogs(): the row removal that would otherwise reset the badge is
437 // not emitted when the model holds no rows.
438 logTable->resetNewMessageCount();
439 // The cleared line may have been a pin's last native home.
440 expireOrphanedMarkers();
441 }
442 }
443
444 void
446 {
447 // Drop the shared history first, so that clearData() below records a watermark past
448 // everything and no filter can bring old lines back when it is next materialised.
449 store.clear();
450
451 std::map<QString, LogTable*>::iterator it = filterMap.begin();
452
453 for (; it != filterMap.end(); it++)
454 {
455 it->second->getModel()->clearData();
456 // Reset the new-message badge explicitly rather than relying on clearData()'s
457 // row removal to do it: a filter that is not currently materialised holds no
458 // rows, so no removal is emitted and its count and colour would survive the
459 // clear.
460 it->second->resetNewMessageCount();
461 }
462
463 // Pins are log-scoped: clearing all logs drops them all.
464 markerRegistry.clear();
465
467 }
468
469 void
470 LogViewer::expireOrphanedMarkers()
471 {
472 // A pin lives exactly as long as the store keeps the line it refers to. With a
473 // shared store that is a single range check per marker, instead of searching every
474 // filter's buffer for a matching message.
475 markerRegistry.expireBefore(store.firstSeq());
476 }
477
478 void
480 {
481 loggingPaused = pause;
482 }
483
484 void
485 LogViewer::setupLogTable(LogTable* logTable)
486 {
487 // Every filter is a view onto the one shared buffer; it keeps sequence numbers into
488 // it rather than its own copies of the messages.
489 logTable->getModel()->setStore(&store);
490
491 // Share the pin registry so this filter's model injects/colors pinned lines and
492 // stays in sync as pins are added or removed anywhere.
493 logTable->getModel()->setMarkerRegistry(&markerRegistry);
494
495 // Route the table's context-menu actions to the shared registry.
496 connect(logTable,
498 this,
499 [this](LogSeq seq) { markerRegistry.add(seq); });
500 connect(logTable,
502 this,
503 [this](LogSeq seq)
504 {
505 if (const LogMarkerId id = markerRegistry.markerIdForSeq(seq))
506 {
507 markerRegistry.remove(id);
508 }
509 });
510 }
511
512 LogTable*
514 {
515 LogTable* newLogTable = new LogTable();
516 setupLogTable(newLogTable);
517 // newLogTable->setColumns(standardColumns);
518 QTreeWidgetItem* item = new QTreeWidgetItem(ui.lvFilters);
519 QString standardFilterStr = ALL_MESSAGES_FILTER;
520 item->setText(0, standardFilterStr);
521 item->setData(0, USERROLE_LOGTABLEID, standardFilterStr);
522 item->setData(0, USERROLE_BASENAME, standardFilterStr);
523 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
524 // ui.lvFilters->addItem(item);
525 filterMap[standardFilterStr] = newLogTable;
526 // "All Messages" has no filters at all, so it can never be routed on.
527 generalFilters.push_back(newLogTable);
528 ui.splitter->addWidget(newLogTable);
529
530 return newLogTable;
531 }
532
533 LogTable*
534 LogViewer::addFilter(QString filterId,
535 QString filterName,
536 QString loggingGroup,
537 QString componentFilter,
538 QString tagFilter,
539 MessageType minimumVerbosity,
540 QString messageFilter,
541 QString fileFilter,
542 QString functionFilter)
543 {
544
545 if (filterMap.find(filterId) != filterMap.end())
546 {
547 QString msg = "A filter with the id " + filterId + " exists already";
548 showMessageBox(msg);
549 return NULL;
550 }
551
552 LogTable* newLogTable = new LogTable();
553 setupLogTable(newLogTable);
554 newLogTable->hide();
555
556 // This helper builds the auto-generated per-group / per-component filters, which
557 // must isolate exactly one group/component -- so match the group and component
558 // values exactly rather than as substrings (otherwise "bar" would also collect
559 // "foo_bar").
560 if (loggingGroup.length())
561 {
562 newLogTable->getModel()->addFilter(
563 ARMARX_LOG_LOGGINGGROUPSTR, loggingGroup.toStdString(), MatchMode::Exact);
564 }
565
566 // newLogTable->setColumns(standardColumns);
567 if (componentFilter.length())
568 {
569 newLogTable->getModel()->addFilter(
570 ARMARX_LOG_COMPONENTSTR, componentFilter.toStdString(), MatchMode::Exact);
571 }
572
573 if (tagFilter.length())
574 {
575 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR, tagFilter.toStdString());
576 }
577
578 if (messageFilter.length())
579 {
580 newLogTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR, messageFilter.toStdString());
581 }
582
583 if (fileFilter.length())
584 {
585 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR, fileFilter.toStdString());
586 }
587
588 if (functionFilter.length())
589 {
591 functionFilter.toStdString());
592 }
593
595 QString::number(minimumVerbosity).toStdString());
596
597 QTreeWidgetItem* item = new QTreeWidgetItem();
598 item->setText(0, filterName);
599 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
600 item->setData(0, USERROLE_LOGTABLEID, filterId);
601 item->setData(0, USERROLE_BASENAME, filterName);
602 filterMap[filterId] = newLogTable;
603 // Register as a general filter by default, so that any caller gets a table that
604 // actually receives messages. ensureAutoFilter() promotes the auto-generated ones
605 // into the dispatch map afterwards.
606 generalFilters.push_back(newLogTable);
607 Ice::StringSeq children;
608 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
609 {
610 children.push_back(ui.lvFilters->invisibleRootItem()->child(i)->text(0).toStdString());
611 }
612
613 QTreeWidgetItem* grpItem = NULL;
614 auto grpFilterId = loggingGroupNameToFilterName(loggingGroup);
615 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
616 {
617 if (ui.lvFilters->invisibleRootItem()
618 ->child(i)
619 ->data(0, USERROLE_BASENAME)
620 .toString() == grpFilterId)
621 {
622 grpItem = ui.lvFilters->invisibleRootItem()->child(i);
623 break;
624 }
625 }
626 if (grpItem)
627 {
628 grpItem->addChild(item);
629 // items.at(0)->sortChildren(0, Qt::AscendingOrder);
630 }
631 else
632 {
633 // ARMARX_INFO << loggingGroup.toStdString() << " group not found - adding " << filterId.toStdString() << " as toplevel\n" << children;
634 ui.lvFilters->insertTopLevelItem(0, item);
635 }
636 // The tree's own sorting is off, so re-sort explicitly now that the set changed.
637 sortFilterList();
638
639 ui.splitter->addWidget(newLogTable);
640 return newLogTable;
641 }
642
643 bool
644 LogViewer::checkAndAddNewFilter(const QString& loggingGroupName, const QString& componentName)
645 {
646 if (componentName.length() == 0 && loggingGroupName.length() == 0)
647 {
648 return false;
649 }
650
651 const QString filterId = autoFilterId(loggingGroupName, componentName);
652 const bool existedBefore = autoFilterMap.contains(filterId);
653 return ensureAutoFilter(filterId, loggingGroupName, componentName, true) != nullptr &&
654 !existedBefore;
655 }
656
657 void
659 {
660 FilterDialog filterDialog;
661
662 if (!filterDialog.exec())
663 {
664 return;
665 }
666
667 QString filterName = filterDialog.ui->edtFilterName->text();
668
669 if (filterMap.find(filterName) != filterMap.end())
670 {
671 QString msg = "A filter with this name already exists";
672 showMessageBox(msg);
673 return;
674 }
675
676 LogTable* newLogTable = new LogTable();
677 setupLogTable(newLogTable);
678 newLogTable->hide();
679
680 // newLogTable->setColumns(standardColumns);
681 if (filterDialog.ui->editComponent->text().length())
682 {
683 newLogTable->getModel()->addFilter(
684 ARMARX_LOG_COMPONENTSTR, filterDialog.ui->editComponent->text().toStdString());
685 }
686
687 if (filterDialog.ui->edtTag->text().length())
688 {
689 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
690 filterDialog.ui->edtTag->text().toStdString());
691 }
692
693 if (filterDialog.ui->edtMessage->text().length())
694 {
696 filterDialog.ui->edtMessage->text().toStdString());
697 }
698
699 if (filterDialog.ui->edtFile->text().length())
700 {
701 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
702 filterDialog.ui->edtFile->text().toStdString());
703 }
704
705 if (filterDialog.ui->edtFunction->text().length())
706 {
708 filterDialog.ui->edtFunction->text().toStdString());
709 }
710
711 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
712 {
713 newLogTable->getModel()->addFilter(
715 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
716 }
717
718 QTreeWidgetItem* item = new QTreeWidgetItem();
719 item->setText(0, filterName);
720 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
721 item->setData(0, USERROLE_LOGTABLEID, filterName);
722 item->setData(0, USERROLE_BASENAME, filterName);
723 filterMap[filterName] = newLogTable;
724 // User-created filters use arbitrary substring criteria, so they cannot be routed
725 // on and have to see every message.
726 generalFilters.push_back(newLogTable);
727 ui.lvFilters->insertTopLevelItem(ui.lvFilters->invisibleRootItem()->childCount(), item);
728 // The tree's own sorting is off, so re-sort explicitly now that the set changed.
729 sortFilterList();
730 ui.splitter->addWidget(newLogTable);
731 }
732
733 void
734 LogViewer::editFilter(QTreeWidgetItem* item, int column)
735 {
736 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
737 FilterDialog filterDialog;
738 filterDialog.ui->edtFilterName->setText(item->data(0, USERROLE_BASENAME).toString());
739
740 if (filterMap.find(filterId) == filterMap.end())
741 {
742 ARMARX_WARNING << "filter " << filterId.toStdString() << " does not exist" << flush;
743 return;
744 }
745
746 LogTable* logTable = filterMap.find(filterId)->second;
747
748 if (!logTable)
749 {
750 ARMARX_WARNING << "logtable ptr is NULL " << flush;
751 return;
752 }
753
754 for (const LogFilter& activeFilter : logTable->getModel()->getFilters())
755 {
756 const QString value = QString::fromStdString(activeFilter.value);
757
758 switch (activeFilter.column)
759 {
761 filterDialog.ui->editComponent->setText(value);
762 break;
763 case LogColumn::Tag:
764 filterDialog.ui->edtTag->setText(value);
765 break;
767 filterDialog.ui->cbVerbosity->setCurrentIndex(value.toInt());
768 break;
770 filterDialog.ui->edtMessage->setText(value);
771 break;
772 case LogColumn::File:
773 filterDialog.ui->edtFile->setText(value);
774 break;
776 filterDialog.ui->edtFunction->setText(value);
777 break;
778 default:
779 break;
780 }
781 }
782
783
784 if (!filterDialog.exec())
785 {
786 return;
787 }
788 item->setText(0, filterDialog.ui->edtFilterName->text());
789 item->setData(0, USERROLE_BASENAME, filterDialog.ui->edtFilterName->text());
790 logTable->getModel()->resetFilters();
791
792 if (filterDialog.ui->editComponent->text().length())
793 {
794 logTable->getModel()->addFilter(ARMARX_LOG_COMPONENTSTR,
795 filterDialog.ui->editComponent->text().toStdString());
796 }
797
798 if (filterDialog.ui->edtTag->text().length())
799 {
800 logTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
801 filterDialog.ui->edtTag->text().toStdString());
802 }
803
804 if (filterDialog.ui->edtMessage->text().length())
805 {
806 logTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR,
807 filterDialog.ui->edtMessage->text().toStdString());
808 }
809
810 if (filterDialog.ui->edtFile->text().length())
811 {
812 logTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
813 filterDialog.ui->edtFile->text().toStdString());
814 }
815
816 if (filterDialog.ui->edtFunction->text().length())
817 {
818 logTable->getModel()->addFilter(ARMARX_LOG_FUNCTIONSTR,
819 filterDialog.ui->edtFunction->text().toStdString());
820 }
821
822 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
823 {
824 logTable->getModel()->addFilter(
826 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
827 }
828
829 // An edited filter no longer matches the (group, component) pair that its id
830 // encodes, so it must not be routed on any more -- otherwise it would only ever be
831 // offered the messages of the component it was originally generated for, and its
832 // new criteria would silently never see anything else.
833 if (autoFilterMap.remove(filterId) > 0)
834 {
835 generalFilters.push_back(logTable);
836 }
837
838 logTable->getModel()->reapplyAllFilters();
839 logTable->update();
840 }
841
842 void
844 {
845 if (ui.lvFilters->invisibleRootItem()->childCount() == 1)
846 {
847 return;
848 }
849
850 auto item = ui.lvFilters->currentItem();
851 removeFilter(item);
852 }
853
854 void
855 LogViewer::removeFilter(QTreeWidgetItem* item)
856 {
857 if (!item)
858 {
859 return;
860 }
861
862 std::map<QString, LogTable*>::iterator it =
863 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
864
865 if (it == filterMap.end())
866 {
867 return;
868 }
869 QList<QTreeWidgetItem*> itemsToDelete, iterationList({item});
870 while (!iterationList.isEmpty())
871 {
872 auto curItem = iterationList.front();
873 itemsToDelete << curItem;
874 iterationList.pop_front();
875 iterationList << curItem->takeChildren();
876 }
877 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
878 for (auto& item : itemsToDelete)
879 {
880 auto parent = item->parent();
881 if (parent)
882 {
883 parent->removeChild(item);
884 }
885 else
886 {
887 ui.lvFilters->invisibleRootItem()->removeChild(item);
888 }
889 std::map<QString, LogTable*>::iterator it =
890 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
891 delete item;
892
893 if (it == filterMap.end())
894 {
895 continue;
896 }
897 LogTable* logTable = it->second;
898 // Drop every routing reference before the table is destroyed.
899 generalFilters.erase(
900 std::remove(generalFilters.begin(), generalFilters.end(), logTable),
901 generalFilters.end());
902 autoFilterMap.remove(it->first);
903 delete logTable;
904 filterBadgeCache.erase(it->first);
905 filterMap.erase(it);
906 }
907 }
908
909 void
910 LogViewer::filterSelectionChanged(QTreeWidgetItem* item, QTreeWidgetItem* previous)
911 {
912 if (!item)
913 {
914 return;
915 }
916 LogTable* oldLogTable = logTable;
917 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
918 // item->setText(0, filterId); Why?
919 QFont font;
920 font.setBold(false);
921 item->setFont(0, font);
922
923 if (filterMap.find(filterId) == filterMap.end())
924 {
925 showMessageBox("Filtername " + filterId + " not found.");
926 return;
927 }
928
929 logTable = filterMap[filterId];
930
931 if (!logTable)
932 {
933 ARMARX_ERROR << "logTable ptr is NULL" << flush;
934 return;
935 }
936
937
938 if (oldLogTable)
939 {
940 oldLogTable->hide();
941 }
942
943 logTable->show();
944 ui.edtLiveFilter->setText(logTable->getLiveFilterStr());
945 ui.edtLiveSearch->setText(logTable->getModel()->getCurrentSearchStr());
946 }
947
948 void
950 {
951 static QFont font;
952 QList<QTreeWidgetItem*> itemsToUpdate, iterationList({ui.lvFilters->invisibleRootItem()});
953 while (!iterationList.isEmpty())
954 {
955 auto curItem = iterationList.front();
956 itemsToUpdate << curItem;
957 iterationList.pop_front();
958 for (int i = 0; i < curItem->childCount(); ++i)
959 {
960 iterationList << curItem->child(i);
961 }
962 }
963 // Update new message count in filter list box
964 for (auto item : itemsToUpdate)
965 {
966 auto filterId = item->data(0, USERROLE_LOGTABLEID).toString();
967 auto filterIt = filterMap.find(filterId);
968 if (filterIt == filterMap.end())
969 {
970 // e.g. the invisible root item, whose id is empty and not in filterMap
971 continue;
972 }
973 LogTable* curLogtable = filterIt->second;
974 if (!curLogtable)
975 {
976 continue;
977 }
978
979 // Skip items whose badge has not changed. Every setText() / setFont() /
980 // setBackgroundColor() on the tree costs a relayout and a repaint, and in
981 // steady state almost every item is unchanged.
982 const int newCount =
983 (curLogtable == logTable) ? 0 : curLogtable->getNewMessageCount();
984 const MessageType newLevel = curLogtable->getMaxNewLogLevelType();
985 const auto cached = filterBadgeCache.find(filterId);
986 if (cached != filterBadgeCache.end() && cached->second.count == newCount &&
987 cached->second.level == newLevel)
988 {
989 continue;
990 }
991 filterBadgeCache[filterId] = FilterBadge{newCount, newLevel};
992
993 QString newContent;
994
995 if (newCount == 0)
996 {
997 font.setBold(false);
998 item->setFont(0, font);
999 newContent = item->data(0, USERROLE_BASENAME).toString();
1000 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
1001 }
1002 else
1003 {
1004 font.setBold(true);
1005 item->setFont(0, font);
1006 newContent = item->data(0, USERROLE_BASENAME).toString() + "(" +
1007 QString::number(newCount) + ")";
1008
1009 if (newLevel == eWARN)
1010 {
1011 item->setBackgroundColor(0, QColor(216, 120, 50));
1012 }
1013 else if (newLevel == eERROR)
1014 {
1015 item->setBackgroundColor(0, QColor(255, 90, 80));
1016 }
1017 else if (newLevel == eFATAL)
1018 {
1019 item->setBackgroundColor(0, QColor(255, 60, 50));
1020 }
1021 else
1022 {
1023 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
1024 }
1025 }
1026
1027 item->setText(0, newContent);
1028 item->setToolTip(0, newContent);
1029 }
1030
1031 // Surface ingest overload: writeLog drops messages once the staging queue is
1032 // saturated, and silently losing log lines would be worse than a slow GUI.
1033 const unsigned long long dropped = droppedEntryCount.load();
1034 if (dropped != lastReportedDropCount)
1035 {
1036 lastReportedDropCount = dropped;
1037 if (getMainWindow() && getMainWindow()->statusBar())
1038 {
1039 getMainWindow()->statusBar()->showMessage(
1040 QString("LogViewer: dropped %1 log messages (ingest overloaded)")
1041 .arg(dropped),
1042 5000);
1043 }
1044 }
1045 }
1046
1047 void
1049 {
1050 throw LocalException() << "Not yet implemented";
1051 }
1052
1053 void
1055 {
1056 ui.lvLabels->clear();
1057 for (const LogMarker& marker : markerRegistry.markers())
1058 {
1059 if (!store.contains(marker.seq))
1060 {
1061 continue;
1062 }
1063 const LogMessage& snapshot = store.at(marker.seq).message;
1064
1065 // Color swatch.
1066 QPixmap swatch(12, 12);
1067 swatch.fill(marker.color);
1068
1069 // Short, single-line description: time . component . message.
1070 IceUtil::Time time = IceUtil::Time::microSeconds(snapshot.time);
1071 std::string timeStr = time.toDateTime();
1072 const auto spacePos = timeStr.find(' ');
1073 if (spacePos != std::string::npos)
1074 {
1075 timeStr = timeStr.substr(spacePos + 1);
1076 }
1077
1078 QString what = QString::fromStdString(snapshot.what);
1079 const int newlinePos = what.indexOf('\n');
1080 if (newlinePos >= 0)
1081 {
1082 what.truncate(newlinePos);
1083 }
1084 constexpr int maxWhatLength = 60;
1085 if (what.length() > maxWhatLength)
1086 {
1087 what.truncate(maxWhatLength);
1088 what += "...";
1089 }
1090
1091 const QString text = QString::fromStdString(timeStr) + " " +
1092 QString::fromStdString(snapshot.who) + " " + what;
1093
1094 QListWidgetItem* item = new QListWidgetItem(QIcon(swatch), text, ui.lvLabels);
1095 item->setData(Qt::UserRole, static_cast<qulonglong>(marker.id));
1096 item->setToolTip(text);
1097 }
1098 }
1099
1100 void
1101 LogViewer::labelClicked(QListWidgetItem* item)
1102 {
1103 if (!item || !logTable)
1104 {
1105 return;
1106 }
1107 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1108
1109 for (const LogMarker& marker : markerRegistry.markers())
1110 {
1111 if (marker.id != id)
1112 {
1113 continue;
1114 }
1115 // The pinned line is present (matched or injected) in every filter, so it
1116 // resolves in whichever table is currently shown.
1117 const int row = logTable->getModel()->rowForSeq(marker.seq);
1118 if (row >= 0)
1119 {
1120 const QModelIndex index = logTable->getModel()->index(row, 0);
1121 logTable->scrollTo(index, QAbstractItemView::PositionAtCenter);
1122 logTable->selectRow(row);
1123 logTable->setFocus();
1124 }
1125 break;
1126 }
1127 }
1128
1129 void
1131 {
1132 QListWidgetItem* item = ui.lvLabels->itemAt(pos);
1133 if (!item)
1134 {
1135 return;
1136 }
1137 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1138
1139 QMenu menu;
1140 QAction* recolorAction = menu.addAction(tr("Change color..."));
1141 QAction* removeAction = menu.addAction(tr("Remove label"));
1142 QAction* chosen = menu.exec(ui.lvLabels->viewport()->mapToGlobal(pos));
1143
1144 if (chosen == removeAction)
1145 {
1146 markerRegistry.remove(id);
1147 }
1148 else if (chosen == recolorAction)
1149 {
1150 QColor initial;
1151 for (const LogMarker& marker : markerRegistry.markers())
1152 {
1153 if (marker.id == id)
1154 {
1155 initial = marker.color;
1156 break;
1157 }
1158 }
1159 const QColor color =
1160 QColorDialog::getColor(initial, getWidget(), tr("Choose label color"));
1161 if (color.isValid())
1162 {
1163 markerRegistry.recolor(id, color);
1164 }
1165 }
1166 }
1167
1168 void
1170 {
1171 QListWidgetItem* item = ui.lvLabels->currentItem();
1172 if (!item)
1173 {
1174 return;
1175 }
1176 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1177 markerRegistry.remove(id);
1178 }
1179
1180 void
1182 {
1183
1184 if (getState() >= eManagedIceObjectExiting)
1185 {
1186 return;
1187 }
1188
1189 std::vector<LogMessage> pendingEntriesTemp;
1190 {
1191 std::unique_lock lock(pendingEntriesMutex);
1192 if (pendingEntries.size() <= maxEntriesPerTick)
1193 {
1194 pendingEntriesTemp.swap(pendingEntries);
1195 }
1196 else
1197 {
1198 // Carry the remainder over to the next tick instead of letting a single
1199 // burst hold the GUI thread for an unbounded time.
1200 const auto batchEnd = pendingEntries.begin() + maxEntriesPerTick;
1201 pendingEntriesTemp.assign(std::make_move_iterator(pendingEntries.begin()),
1202 std::make_move_iterator(batchEnd));
1203 pendingEntries.erase(pendingEntries.begin(), batchEnd);
1204 }
1205 }
1206
1207
1208 // One pass over the batch: strip ANSI colours, create any missing auto-filter for
1209 // this group/component, and bucket the message by the auto-filters it can reach.
1210 // Bucketing here is what makes ingestion independent of the number of filters: a
1211 // message is afterwards only offered to the general filters plus the (at most two)
1212 // tables that can accept it, instead of to every table in filterMap.
1213 std::vector<LogSeq> wholeBatch;
1214 wholeBatch.reserve(pendingEntriesTemp.size());
1215 QHash<LogTable*, std::vector<LogSeq>> autoBuckets;
1216
1217 // Compile the ANSI-color regex once, not on every timer tick.
1218 static const boost::regex re(REGEX_COLORS);
1219 for (LogMessage& msg : pendingEntriesTemp)
1220 {
1221 // Only run the regex if an escape character is actually present.
1222 if (msg.what.find('\033') != std::string::npos)
1223 {
1224 msg.what = boost::regex_replace(msg.what, re, "");
1225 }
1226
1227 // The message is stored exactly once here; every filter that accepts it only
1228 // keeps this sequence number.
1229 const LogSeq seq = store.append(msg);
1230 wholeBatch.push_back(seq);
1231
1232 if (!getWidget())
1233 {
1234 continue;
1235 }
1236
1237 const QString group = QString::fromStdString(msg.group);
1238 const QString component = QString::fromStdString(msg.who);
1239 // Creating a filter stays gated on the selected verbosity, as before. Routing
1240 // is deliberately not gated: a message below the current level still reaches an
1241 // already existing filter, which is what testing it against every filter did.
1242 const bool mayCreate = ui.cbVerbosityLevel->currentIndex() <= msg.type;
1243
1244 const QString groupKey = autoFilterId(group, QString());
1245 const QString componentKey = autoFilterId(group, component);
1246
1247 if (LogTable* groupTable = ensureAutoFilter(groupKey, group, QString(), mayCreate))
1248 {
1249 autoBuckets[groupTable].push_back(seq);
1250 }
1251 if (componentKey != groupKey)
1252 {
1253 if (LogTable* componentTable =
1254 ensureAutoFilter(componentKey, group, component, mayCreate))
1255 {
1256 autoBuckets[componentTable].push_back(seq);
1257 }
1258 }
1259 }
1260
1261 // Sampled before inserting, because inserting rows moves the scrollbar maximum away
1262 // from the current value.
1263 const bool autoScroll =
1264 logTable &&
1265 logTable->verticalScrollBar()->value() == logTable->verticalScrollBar()->maximum();
1266
1267 for (LogTable* log : generalFilters)
1268 {
1269 ingestBatch(log, wholeBatch);
1270 }
1271 for (auto it = autoBuckets.begin(); it != autoBuckets.end(); ++it)
1272 {
1273 ingestBatch(it.key(), it.value());
1274 }
1275
1276 // Only the visible table is painted, so it is the only one that has to follow the
1277 // tail here; hidden tables scroll to the bottom in their showEvent.
1278 if (autoScroll && logTable)
1279 {
1280 logTable->scrollToBottom();
1281 }
1282
1283 // Bound the shared history, then let every filter discard the rows whose entry has
1284 // just been dropped. This has to happen after ingestion, so the sequence numbers of
1285 // the batch stay valid while the models consume them.
1286 if (store.trim())
1287 {
1288 const LogSeq firstSeq = store.firstSeq();
1289 for (auto& filterEntry : filterMap)
1290 {
1291 filterEntry.second->getModel()->dropRowsBefore(firstSeq);
1292 }
1293 // A pin whose line has just left the store is now orphaned.
1294 expireOrphanedMarkers();
1295 }
1296 }
1297
1298 void
1300 {
1301 if (index == 0)
1302 {
1303 ui.edtLiveSearch->show();
1304 ui.btnPreviousItem->show();
1305 ui.btnNextItem->show();
1306 ui.edtLiveFilter->hide();
1307 }
1308 else
1309 {
1310 ui.edtLiveSearch->hide();
1311 ui.btnPreviousItem->hide();
1312 ui.btnNextItem->hide();
1313 ui.edtLiveFilter->show();
1314 }
1315 }
1316
1317 void
1319 {
1320
1321 if (logTable)
1322 {
1323 if (!logTable->selectNextSearchResult(false))
1324 {
1325 getMainWindow()->statusBar()->showMessage(
1326 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1327 "' in the log!",
1328 5000);
1329 }
1330 }
1331 }
1332
1333 void
1335 {
1336 if (logTable)
1337 if (!logTable->selectNextSearchResult(true))
1338 {
1339 getMainWindow()->statusBar()->showMessage(
1340 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1341 "' in the log!",
1342 5000);
1343 }
1344 }
1345
1346 QPointer<QWidget>
1348 {
1349 if (customToolbar)
1350 {
1351 if (parent != customToolbar->parent())
1352 {
1353 customToolbar->setParent(parent);
1354 }
1355
1356 return customToolbar;
1357 }
1358
1359 customToolbar = new QToolBar(parent);
1360 customToolbar->setIconSize(QSize(16, 16));
1361 customToolbar->addAction(
1362 QIcon(":/icons/configure-3.png"), "Configure", this, SLOT(OpenConfigureDialog()));
1363
1364 return customToolbar;
1365 }
1366} // namespace armarx
uint8_t index
#define ARMARX_LOG_VERBOSITYSTR
Definition LogTable.h:44
#define ARMARX_LOG_COMPONENTSTR
Definition LogTable.h:42
#define ARMARX_LOG_FILESTR
Definition LogTable.h:46
#define ARMARX_LOG_TAGSTR
Definition LogTable.h:43
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:45
#define ARMARX_LOG_LOGGINGGROUPSTR
Definition LogTable.h:48
#define ARMARX_LOG_FUNCTIONSTR
Definition LogTable.h:47
#define USERROLE_BASENAME
Definition LogViewer.cpp:57
#define ALL_MESSAGES_FILTER
Definition LogViewer.cpp:59
#define USERROLE_LOGTABLEID
Definition LogViewer.cpp:55
#define REGEX_COLORS
Definition LogViewer.cpp:58
bool onClose() override
If you overwrite this method, make sure to call this implementation at the end of your implementation...
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
virtual QMainWindow * getMainWindow()
Returns the ArmarX MainWindow.
static int showMessageBox(const QString &msg)
Ui::FilterDialog * ui
bool expireBefore(LogSeq firstSeq)
Remove every marker whose line has been dropped from the store, i.e.
LogMarkerId markerIdForSeq(LogSeq seq) const
Id of the marker pinning seq, or 0 if it is not pinned.
void add(LogSeq seq)
Pin the store entry seq, assigning it the next Glasbey color.
void remove(LogMarkerId id)
Remove the marker with the given id (no-op if unknown).
static std::string levelToString(MessageTypeT type)
LogSeq firstSeq() const
Sequence number of the oldest retained entry.
Definition LogStore.cpp:111
void setMarkerRegistry(LogMarkerRegistry *registry)
Attach the shared pin registry.
void addFilter(const std::string &columnName, const std::string &filter, MatchMode mode=MatchMode::Contains)
void setStore(const LogStore *store)
Attach the shared backing store. Must be called before any ingestion.
MessageType getMaxNewLogLevelType()
Definition LogTable.h:74
LogTableModel * getModel()
Definition LogTable.cpp:281
void removeLabelRequested(LogSeq seq)
Emitted from the context menu to remove the pin on the given line.
void addLabelRequested(LogSeq seq)
Emitted from the context menu to pin the given line (assign a label).
int getNewMessageCount()
Definition LogTable.h:68
void pauseLogging(bool pause=false)
LogTable * logTable
Definition LogViewer.h:158
void componentConnected()
void selectPreviousSearchResult()
void onInitComponent() override
Pure virtual hook for the subclass.
void editFilter(QTreeWidgetItem *item, int column)
void insertPendingEntries()
bool onClose() override
If you overwrite this method, make sure to call this implementation at the end of your implementation...
void removeSelectedLabel()
Remove the pin currently selected in the Labels panel.
void refreshLabelsPanel()
Rebuild the Labels panel from the pin registry.
QPointer< QWidget > getCustomTitlebarWidget(QWidget *parent) override
getTitleToolbar returns a pointer to the a toolbar widget of this controller.
void selectNextSearchResult()
void removeSelectedFilter()
LogTable * addEmptyFilter()
void removeFilter(QTreeWidgetItem *item)
void loadSettings(QSettings *settings) override
Implement to load the settings that are part of the GUI configuration.
void saveSettings(QSettings *settings) override
Implement to save the settings as part of the GUI configuration.
void updateFilterListSignal()
void performLiveSearch(QString searchStr)
Ui_LogViewer ui
Definition LogViewer.h:157
~LogViewer() override
void onConnectComponent() override
Pure virtual hook for the subclass.
void write(const std::string &who, Ice::Long time, const std::string &tag, MessageType severity, const std::string &message, const std::string &file, Ice::Int line, const std::string &function, const Ice::Current &=Ice::emptyCurrent)
void filterSelectionChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous)
bool checkAndAddNewFilter(const QString &loggingGroupName, const QString &componentName)
This function checks, if there are new components in the log and if so, it creates new filters for th...
void onExitComponent() override
Hook for subclass.
void writeLog(const LogMessage &msg, const Ice::Current &=Ice::emptyCurrent) override
void performLiveFilter(QString searchStr, int startRow=0)
void labelClicked(QListWidgetItem *item)
Focus the clicked label's line in the currently shown message view.
void showLabelsContextMenu(const QPoint &pos)
Recolor / remove menu for the Labels panel.
void searchTypeChanged(int index)
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
int getState() const
Retrieve current state of the ManagedIceObject.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::uint64_t LogMarkerId
quint64 LogSeq
Identifies a stored log entry for as long as it is retained.
Definition LogStore.h:37
const LogSender::manipulator flush
Definition LogSender.h:251
MessageTypeT
Definition LogSender.h:46
A single pinned log line: the store entry it refers to plus the color assigned to it.