From f66788691de916323357d90f5ba2a9ad752226c0 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 18 Sep 2020 21:23:11 +0100 Subject: [PATCH] Set up for Boost logging in WSJT-X uses a settings file to define log sink back-ends, by default uses :/wsjtx_log_config.ini from the resources file-system. Users may override by placing their own wsjtx_log_config.ini into the WSJT-X config location. The settings file format is as described in the Boost log documentation (https://www.boost.org/doc/libs/1_74_0/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.setup.settings_file) with the additional feature that allows some pre-defined variables to be expanded. The predefined variables refer to standard locations in the file-system, and allow log files and rotation target directory paths to be specified. The pre-defined variables are: DesktopLocation DocumentsLocation TempLocation HomeLocation CacheLocation GenericCacheLocation GenericDataLocation AppDataLocation and must be used enclosed on braces and preceded by a '$' character. E.g. to define the pattern for a sink's log file: FileName="${AppLocalDataLocation}/wsjtx_syslog.log" this would place the log file wsjtx_syslog.log in the WSJT-X log files directory, on all platforms. --- CMakeLists.txt | 1 + WSJTXLogging.cpp | 114 +++++++++++++++++++++++++++++++++++++++++++++++ WSJTXLogging.hpp | 23 ++++++++++ main.cpp | 33 ++++++-------- 4 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 WSJTXLogging.cpp create mode 100644 WSJTXLogging.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d88a3f7ad..69825b772 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,7 @@ set (jt9_FSRCS ) set (wsjtx_CXXSRCS + WSJTXLogging.cpp logbook/logbook.cpp Network/PSKReporter.cpp Modulator/Modulator.cpp diff --git a/WSJTXLogging.cpp b/WSJTXLogging.cpp new file mode 100644 index 000000000..f8fecdbbf --- /dev/null +++ b/WSJTXLogging.cpp @@ -0,0 +1,114 @@ +#include "WSJTXLogging.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "Logger.hpp" +#include "qt_helpers.hpp" + +namespace logging = boost::log; +namespace container = boost::container; + +WSJTXLogging::WSJTXLogging () +{ + QFile log_config {QStandardPaths::locate (QStandardPaths::ConfigLocation, "wsjtx_log_config.ini")}; + if (!log_config.exists ()) + { + log_config.setFileName (":/wsjtx_log_config.ini"); + } + if (log_config.open (QFile::ReadOnly) && log_config.isReadable ()) + { + QTextStream ts {&log_config}; + auto config = ts.readAll (); + + // substitute variable + container::flat_map replacements = + { + {"DesktopLocation", QStandardPaths::writableLocation (QStandardPaths::DesktopLocation)}, + {"DocumentsLocation", QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation)}, + {"TempLocation", QStandardPaths::writableLocation (QStandardPaths::TempLocation)}, + {"HomeLocation", QStandardPaths::writableLocation (QStandardPaths::HomeLocation)}, + {"CacheLocation", QStandardPaths::writableLocation (QStandardPaths::CacheLocation)}, + {"GenericCacheLocation", QStandardPaths::writableLocation (QStandardPaths::GenericCacheLocation)}, + {"GenericDataLocation", QStandardPaths::writableLocation (QStandardPaths::GenericDataLocation)}, + {"AppDataLocation", QStandardPaths::writableLocation (QStandardPaths::AppDataLocation)}, + {"AppLocalDataLocation", QStandardPaths::writableLocation (QStandardPaths::AppLocalDataLocation)}, + }; + QString new_config; + int pos {0}; + QRegularExpression subst_vars {R"(\${([^}]+)})"}; + auto iter = subst_vars.globalMatch (config); + while (iter.hasNext ()) + { + auto match = iter.next (); + auto const& name = match.captured (1); + auto repl_iter = replacements.find (name); + auto repl = repl_iter != replacements.end () ? repl_iter->second : "${" + name + "}"; + new_config += config.mid (pos, match.capturedStart (1) - 2 - pos) + repl; + pos = match.capturedEnd (0); + } + new_config += config.mid (pos); + std::stringbuf buffer {new_config.toStdString (), std::ios_base::in}; + std::istream stream {&buffer}; + Logger::init_from_config (stream); + } + else + { + LOG_WARN ("Unable to read logging configuration file: " << log_config.fileName ()); + } +} + +WSJTXLogging::~WSJTXLogging () +{ + LOG_INFO ("Log Finish"); + auto lg = logging::core::get (); + lg->flush (); + lg->remove_all_sinks (); +} + +// Reroute Qt messages to the system logger +void WSJTXLogging::qt_log_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg) +{ + // Convert Qt message types to logger severities + auto severity = boost::log::trivial::trace; + switch (type) + { + case QtDebugMsg: severity = boost::log::trivial::debug; break; + case QtInfoMsg: severity = boost::log::trivial::info; break; + case QtWarningMsg: severity = boost::log::trivial::warning; break; + case QtCriticalMsg: severity = boost::log::trivial::error; break; + case QtFatalMsg: severity = boost::log::trivial::fatal; break; + } + // Map non-default Qt categories to logger channels, Qt logger + // context is mapped to the appropriate logger attributes. + auto log = Logger::sys::get (); + if (!qstrcmp (context.category, "default")) + { + BOOST_LOG_SEV (log, severity) + << boost::log::add_value ("Line", context.line) + << boost::log::add_value ("File", context.file) + << boost::log::add_value ("Function", context.function) + << msg.toStdString (); + } + else + { + BOOST_LOG_CHANNEL_SEV (log, context.category, severity) + << boost::log::add_value ("Line", context.line) + << boost::log::add_value ("File", context.file) + << boost::log::add_value ("Function", context.function) + << msg.toStdString (); + } + if (QtFatalMsg == type) + { + // bail out + throw std::runtime_error {"Fatal Qt Error"}; + } +} diff --git a/WSJTXLogging.hpp b/WSJTXLogging.hpp new file mode 100644 index 000000000..bd0a12157 --- /dev/null +++ b/WSJTXLogging.hpp @@ -0,0 +1,23 @@ +#ifndef WSJTX_LOGGING_HPP__ +#define WSJTX_LOGGING_HPP__ + +#include + +class QString; + +// +// Class WSJTXLogging - wraps application specific logging +// +class WSJTXLogging final +{ +public: + explicit WSJTXLogging (); + ~WSJTXLogging (); + + // + // Install this as the Qt message handler (qInstallMessageHandler) + // to integrate Qt messages. + static void qt_log_handler (QtMsgType type, QMessageLogContext const& context, QString const&); +}; + +#endif diff --git a/main.cpp b/main.cpp index 6a7651ad2..116cb57ca 100644 --- a/main.cpp +++ b/main.cpp @@ -34,7 +34,8 @@ #include "qt_helpers.hpp" #include "L10nLoader.hpp" #include "SettingsGroup.hpp" -#include "TraceFile.hpp" +//#include "TraceFile.hpp" +#include "WSJTXLogging.hpp" #include "MultiSettings.hpp" #include "widgets/mainwindow.h" #include "commons.h" @@ -98,9 +99,7 @@ namespace int main(int argc, char *argv[]) { - // ### Add timestamps to all debug messages - // qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{message}"); - + ::qInstallMessageHandler (&WSJTXLogging::qt_log_handler); init_random_seed (); // make the Qt type magic happen @@ -113,15 +112,15 @@ int main(int argc, char *argv[]) ExceptionCatchingApplication a(argc, argv); try { - // qDebug () << "+++++++++++++++++++++++++++ Resources ++++++++++++++++++++++++++++"; + // LOG_INfO ("+++++++++++++++++++++++++++ Resources ++++++++++++++++++++++++++++"); // { // QDirIterator resources_iter {":/", QDirIterator::Subdirectories}; // while (resources_iter.hasNext ()) // { - // qDebug () << resources_iter.next (); + // LOG_INFO (resources_iter.next ()); // } // } - // qDebug () << "--------------------------- Resources ----------------------------"; + // LOG_INFO ("--------------------------- Resources ----------------------------"); QLocale locale; // get the current system locale setlocale (LC_NUMERIC, "C"); // ensure number forms are in @@ -179,9 +178,6 @@ int main(int argc, char *argv[]) } } - // load UI translations - L10nLoader l10n {&a, locale, parser.value (lang_option)}; - QStandardPaths::setTestModeEnabled (parser.isSet (test_option)); // support for multiple instances running from a single installation @@ -224,8 +220,8 @@ int main(int argc, char *argv[]) if (QLockFile::LockFailedError == instance_lock.error ()) { auto button = MessageBox::query_message (nullptr - , a.translate ("main", "Another instance may be running") - , a.translate ("main", "try to remove stale lock file?") + , "Another instance may be running" + , "try to remove stale lock file?" , QString {} , MessageBox::Yes | MessageBox::Retry | MessageBox::No , MessageBox::Yes); @@ -244,12 +240,11 @@ int main(int argc, char *argv[]) } } -#if WSJT_QDEBUG_TO_FILE - // Open a trace file - TraceFile trace_file {temp_dir.absoluteFilePath (a.applicationName () + "_trace.log")}; - qSetMessagePattern ("[%{time yyyyMMdd HH:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"); - qDebug () << program_title (revision ()) + " - Program startup"; -#endif + WSJTXLogging lg; + LOG_INFO (program_title (revision ()) << " - Program startup"); + + // load UI translations + L10nLoader l10n {&a, locale, parser.value (lang_option)}; // Create a unique writeable temporary directory in a suitable location bool temp_ok {false}; @@ -382,7 +377,7 @@ int main(int argc, char *argv[]) a.translate ("main", "Unable to create shared memory segment")); throw std::runtime_error {"Shared memory error"}; } - qDebug () << "shmem size:" << mem_jt9.size (); + LOG_INFO ("shmem size:" << mem_jt9.size ()); } else {