From: Soeren Apel Date: Tue, 27 Mar 2018 13:19:04 +0000 (+0200) Subject: Add logging mechanism X-Git-Url: https://sigrok.org/gitweb/?p=pulseview.git;a=commitdiff_plain;h=bcb4c327ee9b8d2172d126429ba017884d079d4c Add logging mechanism --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 1994a130..d64ed307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,7 @@ set(pulseview_SOURCES pv/application.cpp pv/devicemanager.cpp pv/globalsettings.cpp + pv/logging.cpp pv/mainwindow.cpp pv/session.cpp pv/storesession.cpp @@ -273,6 +274,7 @@ set(pulseview_SOURCES # This list includes only QObject derived class headers. set(pulseview_HEADERS + pv/logging.hpp pv/globalsettings.hpp pv/mainwindow.hpp pv/session.hpp diff --git a/main.cpp b/main.cpp index 640168ff..feff17aa 100644 --- a/main.cpp +++ b/main.cpp @@ -41,6 +41,8 @@ #include "pv/application.hpp" #include "pv/devicemanager.hpp" +#include "pv/globalsettings.hpp" +#include "pv/logging.hpp" #include "pv/mainwindow.hpp" #include "pv/session.hpp" @@ -189,6 +191,12 @@ int main(int argc, char *argv[]) if (argc - optind == 1) open_file = argv[argc - 1]; + // Prepare the global settings since logging needs them early on + pv::GlobalSettings settings; + settings.set_defaults_where_needed(); + + pv::logging.init(); + // Initialise libsigrok context = sigrok::Context::create(); pv::Session::sr_context = context; diff --git a/pv/dialogs/settings.cpp b/pv/dialogs/settings.cpp index d18d2d42..25994f1f 100644 --- a/pv/dialogs/settings.cpp +++ b/pv/dialogs/settings.cpp @@ -25,20 +25,26 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include #include #include #include #include +#include #include #include "settings.hpp" #include "pv/devicemanager.hpp" #include "pv/globalsettings.hpp" +#include "pv/logging.hpp" #include @@ -59,6 +65,10 @@ Settings::Settings(DeviceManager &device_manager, QWidget *parent) : resize(600, 400); + // Create log view + log_view_ = create_log_view(); + + // Create pages page_list = new QListWidget; page_list->setViewMode(QListView::IconMode); page_list->setIconSize(QSize(icon_size, icon_size)); @@ -121,6 +131,15 @@ void Settings::create_pages() aboutButton->setText(tr("About")); aboutButton->setTextAlignment(Qt::AlignHCenter); aboutButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + // Logging page + pages->addWidget(get_logging_page(pages)); + + QListWidgetItem *loggingButton = new QListWidgetItem(page_list); + loggingButton->setIcon(QIcon(":/icons/information.svg")); + loggingButton->setText(tr("Logging")); + loggingButton->setTextAlignment(Qt::AlignHCenter); + loggingButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const @@ -133,6 +152,23 @@ QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const return cb; } +QPlainTextEdit *Settings::create_log_view() const +{ + GlobalSettings settings; + + QPlainTextEdit *log_view = new QPlainTextEdit(); + + log_view->setReadOnly(true); + log_view->setWordWrapMode(QTextOption::NoWrap); + log_view->setCenterOnScroll(true); + + log_view->appendHtml(logging.get_log()); + connect(&logging, SIGNAL(logged_text(QString)), + log_view, SLOT(appendHtml(QString))); + + return log_view; +} + QWidget *Settings::get_view_settings_form(QWidget *parent) const { GlobalSettings settings; @@ -414,6 +450,64 @@ QWidget *Settings::get_about_page(QWidget *parent) const return page; } +QWidget *Settings::get_logging_page(QWidget *parent) const +{ + GlobalSettings settings; + + // Log level + QSpinBox *loglevel_sb = new QSpinBox(); + loglevel_sb->setMaximum(SR_LOG_SPEW); + loglevel_sb->setValue(logging.get_log_level()); + connect(loglevel_sb, SIGNAL(valueChanged(int)), this, + SLOT(on_log_logLevel_changed(int))); + + QHBoxLayout *loglevel_layout = new QHBoxLayout(); + loglevel_layout->addWidget(new QLabel(tr("Log level:"))); + loglevel_layout->addWidget(loglevel_sb); + + // Background buffer size + QSpinBox *buffersize_sb = new QSpinBox(); + buffersize_sb->setSuffix(tr(" lines")); + buffersize_sb->setMaximum(Logging::MAX_BUFFER_SIZE); + buffersize_sb->setValue( + settings.value(GlobalSettings::Key_Log_BufferSize).toInt()); + connect(buffersize_sb, SIGNAL(valueChanged(int)), this, + SLOT(on_log_bufferSize_changed(int))); + + QHBoxLayout *buffersize_layout = new QHBoxLayout(); + buffersize_layout->addWidget(new QLabel(tr("Length of background buffer:"))); + buffersize_layout->addWidget(buffersize_sb); + + // Save to file + QPushButton *save_log_pb = new QPushButton( + QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png")), + tr("&Save to File")); + connect(save_log_pb, SIGNAL(clicked(bool)), + this, SLOT(on_log_saveToFile_clicked(bool))); + + // Pop out + QPushButton *pop_out_pb = new QPushButton( + QIcon::fromTheme("window-new", QIcon(":/icons/window-new.png")), + tr("&Pop out")); + connect(pop_out_pb, SIGNAL(clicked(bool)), + this, SLOT(on_log_popOut_clicked(bool))); + + QHBoxLayout *control_layout = new QHBoxLayout(); + control_layout->addLayout(loglevel_layout); + control_layout->addLayout(buffersize_layout); + control_layout->addWidget(save_log_pb); + control_layout->addWidget(pop_out_pb); + + QVBoxLayout *root_layout = new QVBoxLayout(); + root_layout->addLayout(control_layout); + root_layout->addWidget(log_view_); + + QWidget *page = new QWidget(parent); + page->setLayout(root_layout); + + return page; +} + void Settings::accept() { GlobalSettings settings; @@ -504,5 +598,68 @@ void Settings::on_dec_initialStateConfigurable_changed(int state) settings.setValue(GlobalSettings::Key_Dec_InitialStateConfigurable, state ? true : false); } +void Settings::on_log_logLevel_changed(int value) +{ + logging.set_log_level(value); +} + +void Settings::on_log_bufferSize_changed(int value) +{ + GlobalSettings settings; + settings.setValue(GlobalSettings::Key_Log_BufferSize, value); +} + +void Settings::on_log_saveToFile_clicked(bool checked) +{ + (void)checked; + + const QString file_name = QFileDialog::getSaveFileName( + this, tr("Save Log"), "", tr("Log Files (*.txt *.log);;All Files (*)")); + + if (file_name.isEmpty()) + return; + + QFile file(file_name); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + QTextStream out_stream(&file); + out_stream << log_view_->toPlainText(); + + if (out_stream.status() == QTextStream::Ok) { + QMessageBox msg(this); + msg.setText(tr("Success")); + msg.setInformativeText(tr("Log saved to %1.").arg(file_name)); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Information); + msg.exec(); + + return; + } + } + + QMessageBox msg(this); + msg.setText(tr("Error")); + msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name)); + msg.setStandardButtons(QMessageBox::Ok); + msg.setIcon(QMessageBox::Warning); + msg.exec(); +} + +void Settings::on_log_popOut_clicked(bool checked) +{ + (void)checked; + + // Create the window as a sub-window so it closes when the main window closes + QMainWindow *window = new QMainWindow(0, Qt::SubWindow); + + window->setObjectName(QString::fromUtf8("Log Window")); + window->setWindowTitle(tr("%1 Log").arg(PV_TITLE)); + + // Use same width/height as the settings dialog + window->resize(width(), height()); + + window->setCentralWidget(create_log_view()); + window->show(); +} + } // namespace dialogs } // namespace pv diff --git a/pv/dialogs/settings.hpp b/pv/dialogs/settings.hpp index d548f52f..81d09fce 100644 --- a/pv/dialogs/settings.hpp +++ b/pv/dialogs/settings.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace pv { @@ -40,10 +41,12 @@ public: void create_pages(); QCheckBox *create_checkbox(const QString& key, const char* slot) const; + QPlainTextEdit *create_log_view() const; QWidget *get_view_settings_form(QWidget *parent) const; QWidget *get_decoder_settings_form(QWidget *parent) const; QWidget *get_about_page(QWidget *parent) const; + QWidget *get_logging_page(QWidget *parent) const; void accept(); void reject(); @@ -61,11 +64,17 @@ private Q_SLOTS: void on_view_defaultDivHeight_changed(int value); void on_view_defaultLogicHeight_changed(int value); void on_dec_initialStateConfigurable_changed(int state); + void on_log_logLevel_changed(int value); + void on_log_bufferSize_changed(int value); + void on_log_saveToFile_clicked(bool checked); + void on_log_popOut_clicked(bool checked); private: DeviceManager &device_manager_; QListWidget *page_list; QStackedWidget *pages; + + QPlainTextEdit *log_view_; }; } // namespace dialogs diff --git a/pv/globalsettings.cpp b/pv/globalsettings.cpp index 1a589fe9..93608290 100644 --- a/pv/globalsettings.cpp +++ b/pv/globalsettings.cpp @@ -39,6 +39,7 @@ const QString GlobalSettings::Key_View_ConversionThresholdDispMode = "View_Conve const QString GlobalSettings::Key_View_DefaultDivHeight = "View_DefaultDivHeight"; const QString GlobalSettings::Key_View_DefaultLogicHeight = "View_DefaultLogicHeight"; const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable"; +const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize"; vector GlobalSettings::callbacks_; bool GlobalSettings::tracking_ = false; @@ -71,6 +72,10 @@ void GlobalSettings::set_defaults_where_needed() if (!contains(Key_View_DefaultLogicHeight)) setValue(Key_View_DefaultLogicHeight, 2 * QFontMetrics(QApplication::font()).height()); + + // Default to 500 lines of backlog + if (!contains(Key_Log_BufferSize)) + setValue(Key_Log_BufferSize, 500); } void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb) diff --git a/pv/globalsettings.hpp b/pv/globalsettings.hpp index fe5bbc9e..eda3e03a 100644 --- a/pv/globalsettings.hpp +++ b/pv/globalsettings.hpp @@ -56,6 +56,7 @@ public: static const QString Key_View_DefaultDivHeight; static const QString Key_View_DefaultLogicHeight; static const QString Key_Dec_InitialStateConfigurable; + static const QString Key_Log_BufferSize; enum ConvThrDispMode { ConvThrDispMode_None = 0, diff --git a/pv/logging.cpp b/pv/logging.cpp new file mode 100644 index 00000000..ea079828 --- /dev/null +++ b/pv/logging.cpp @@ -0,0 +1,171 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "logging.hpp" +#include "globalsettings.hpp" + +#ifdef ENABLE_DECODE +#include /* First, so we avoid a _POSIX_C_SOURCE warning. */ +#endif + +#include + +#include + +namespace pv { + +Logging logging; + +const int Logging::MAX_BUFFER_SIZE = 50000; + +Logging::~Logging() +{ + qInstallMessageHandler(0); + sr_log_callback_set_default(); +#ifdef ENABLE_DECODE + srd_log_callback_set_default(); +#endif + + GlobalSettings::remove_change_handler(this); +} + +void Logging::init() +{ + GlobalSettings settings; + + buffer_size_ = + settings.value(GlobalSettings::Key_Log_BufferSize).toInt(); + + buffer_.reserve(buffer_size_); + + qInstallMessageHandler(log_pv); + sr_log_callback_set(log_libsigrok, nullptr); +#ifdef ENABLE_DECODE + srd_log_callback_set(log_libsrd, nullptr); +#endif + + GlobalSettings::add_change_handler(this); +} + +int Logging::get_log_level() const +{ + // We assume that libsigrok and libsrd always have the same log level + return sr_log_loglevel_get(); +} + +void Logging::set_log_level(int level) +{ + sr_log_loglevel_set(level); + srd_log_loglevel_set(level); +} + +QString Logging::get_log() const +{ + return buffer_.join("
\n"); +} + +void Logging::log(const QString &text, int source) +{ + if (buffer_.size() >= buffer_size_) + buffer_.removeFirst(); + + QString s; + + if (text.contains("warning", Qt::CaseInsensitive)) { + s = QString("%1").arg(text); + goto out; + } + + if (text.contains("error", Qt::CaseInsensitive)) { + s = QString("%1").arg(text); + goto out; + } + + switch (source) { + case LogSource_pv: + s = QString("pv: ") + text; // black is default color + break; + case LogSource_sr: + s = QString("sr: %1").arg(text); + break; + case LogSource_srd: + s = QString("srd: %1").arg(text); + break; + default: + s = text; + break; + } + +out: + buffer_.append(s); + + // If we're tearing down the program, sending out notifications to UI + // elements that can no longer function properly is a bad idea + if (!QApplication::closingDown()) + logged_text(s); +} + +void Logging::log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + (void)type; + (void)context; + + logging.log(msg, LogSource_pv); +} + +int Logging::log_libsigrok(void *cb_data, int loglevel, const char *format, va_list args) +{ + (void)cb_data; + (void)loglevel; + + char *text = g_strdup_vprintf(format, args); + logging.log(QString::fromUtf8(text), LogSource_sr); + g_free(text); + + return SR_OK; +} + +#ifdef ENABLE_DECODE +int Logging::log_libsrd(void *cb_data, int loglevel, const char *format, va_list args) +{ + (void)cb_data; + (void)loglevel; + + char *text = g_strdup_vprintf(format, args); + logging.log(QString::fromUtf8(text), LogSource_srd); + g_free(text); + + return SR_OK; +} +#endif + +void Logging::on_setting_changed(const QString &key, const QVariant &value) +{ + if (key == GlobalSettings::Key_Log_BufferSize) { + // Truncate buffer if needed + const int delta = buffer_.size() - value.toInt(); + if (delta > 0) + buffer_.erase(buffer_.begin(), buffer_.begin() + delta); + + buffer_size_ = value.toInt(); + buffer_.reserve(buffer_size_); + } +} + +} // namespace pv diff --git a/pv/logging.hpp b/pv/logging.hpp new file mode 100644 index 00000000..69957627 --- /dev/null +++ b/pv/logging.hpp @@ -0,0 +1,79 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef PULSEVIEW_PV_LOGGING_HPP +#define PULSEVIEW_PV_LOGGING_HPP + +#include "globalsettings.hpp" + +#include +#include +#include +#include + +namespace pv { + +class Logging : public QObject, public GlobalSettingsInterface +{ + Q_OBJECT + +public: + enum LogSource { + LogSource_pv, + LogSource_sr, + LogSource_srd + }; + + static const int MAX_BUFFER_SIZE; + +public: + ~Logging(); + void init(); + + int get_log_level() const; + void set_log_level(int level); + + QString get_log() const; + + void log(const QString &text, int source); + + static void log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg); + + static int log_libsigrok(void *cb_data, int loglevel, const char *format, va_list args); + +#ifdef ENABLE_DECODE + static int log_libsrd(void *cb_data, int loglevel, const char *format, va_list args); +#endif + +private: + void on_setting_changed(const QString &key, const QVariant &value); + +Q_SIGNALS: + void logged_text(QString s); + +private: + int buffer_size_; + QStringList buffer_; +}; + +extern Logging logging; + +} // namespace pv + +#endif // PULSEVIEW_PV_LOGGING_HPP diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp index 4ab78d1d..e88d382e 100644 --- a/pv/mainwindow.cpp +++ b/pv/mainwindow.cpp @@ -79,9 +79,6 @@ MainWindow::MainWindow(DeviceManager &device_manager, QWidget *parent) : GlobalSettings::add_change_handler(this); - GlobalSettings settings; - settings.set_defaults_where_needed(); - setup_ui(); restore_ui_settings(); }