diff --git a/CMakeLists.txt b/CMakeLists.txt index c5041782f..d88a3f7ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ set (wsjtx_CXXSRCS ) set (wsjt_CXXSRCS + Logger.cpp lib/crc10.cpp lib/crc13.cpp lib/crc14.cpp @@ -749,6 +750,7 @@ set (all_C_and_CXXSRCS ) set (TOP_LEVEL_RESOURCES + wsjtx_log_config.ini icons/Darwin/wsjtx.iconset/icon_128x128.png contrib/gpl-v3-logo.svg artwork/splash.png @@ -880,7 +882,7 @@ find_program(ETAGS etags) # set (BOOST_ROOT ${PROJECT_SOURCE_DIR}/boost) # endif () set (Boost_USE_STATIC_LIBS OFF) -find_package (Boost 1.63 REQUIRED COMPONENTS log_setup) +find_package (Boost 1.63 REQUIRED COMPONENTS log_setup log) # # OpenMP @@ -1219,6 +1221,7 @@ endif (WIN32) # build a library of package functionality (without and optionally with OpenMP support) add_library (wsjt_cxx STATIC ${wsjt_CSRCS} ${wsjt_CXXSRCS}) +target_link_libraries (wsjt_cxx Boost::log_setup) # build an OpenMP variant of the Fortran library routines add_library (wsjt_fort STATIC ${wsjt_FSRCS}) @@ -1257,7 +1260,7 @@ target_link_libraries (qcp Qt5::Widgets Qt5::PrintSupport) add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${wsjt_qt_GENUISRCS} ${GENAXSRCS}) # set wsjtx_udp exports to static variants target_compile_definitions (wsjt_qt PUBLIC UDP_STATIC_DEFINE) -target_link_libraries (wsjt_qt Boost::log_setup qcp Qt5::Widgets Qt5::Network Qt5::Sql) +target_link_libraries (wsjt_qt Boost::log qcp Qt5::Widgets Qt5::Network Qt5::Sql) target_include_directories (wsjt_qt BEFORE PRIVATE ${hamlib_INCLUDE_DIRS}) if (WIN32) target_link_libraries (wsjt_qt Qt5::AxContainer Qt5::AxBase) diff --git a/Logger.cpp b/Logger.cpp new file mode 100644 index 000000000..211cac17c --- /dev/null +++ b/Logger.cpp @@ -0,0 +1,189 @@ +#include "Logger.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace logger = boost::log; +namespace srcs = logger::sources; +namespace sinks = logger::sinks; +namespace keywords = logger::keywords; +namespace expr = logger::expressions; +namespace attrs = logger::attributes; +namespace ptime = boost::posix_time; + +namespace Logger +{ + BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS (sys, + srcs::severity_channel_logger_mt, + (keywords::channel = "SYSLOG")); + BOOST_LOG_GLOBAL_LOGGER_CTOR_ARGS (data, + srcs::severity_channel_logger_mt, + (keywords::channel = "DATALOG")); + + namespace + { + // Custom formatter factory to add TimeStamp format support in config ini file. + // Allows %TimeStamp(format=\"%Y.%m.%d %H:%M:%S.%f\")% to be used in ini config file for property Format. + class TimeStampFormatterFactory + : public logger::basic_formatter_factory + { + public: + formatter_type create_formatter (logger::attribute_name const& name, args_map const& args) + { + args_map::const_iterator it = args.find ("format"); + if (it != args.end ()) + { + return expr::stream + << expr::format_date_time + ( + expr::attr (name), it->second + ); + } + else + { + return expr::stream + << expr::attr (name); + } + } + }; + + // Custom formatter factory to add Uptime format support in config ini file. + // Allows %Uptime(format=\"%O:%M:%S.%f\")% to be used in ini config file for property Format. + // attrs::timer value type is ptime::time_duration + class UptimeFormatterFactory + : public logger::basic_formatter_factory + { + public: + formatter_type create_formatter (logger::attribute_name const& name, args_map const& args) + { + args_map::const_iterator it = args.find ("format"); + if (it != args.end ()) + { + return expr::stream + << expr::format_date_time + ( + expr::attr (name), it->second + ); + } + else + { + return expr::stream + << expr::attr (name); + } + } + }; + + class CommonInitialization + { + public: + CommonInitialization () + { + // Disable all exceptions + logger::core::get ()->set_exception_handler (logger::make_exception_suppressor ()); + + // Add common attributes: LineID, TimeStamp, ProcessID, ThreadID + logger::add_common_attributes (); + // Add boost log timer as global attribute Uptime + logger::core::get ()->add_global_attribute ("Uptime", attrs::timer ()); + // Allows %Severity% to be used in ini config file for property Filter. + logger::register_simple_filter_factory ("Severity"); + // Allows %Severity% to be used in ini config file for property Format. + logger::register_simple_formatter_factory ("Severity"); + // Allows %TimeStamp(format=\"%Y.%m.%d %H:%M:%S.%f\")% to be used in ini config file for property Format. + logger::register_formatter_factory ("TimeStamp", boost::make_shared ()); + // Allows %Uptime(format=\"%O:%M:%S.%f\")% to be used in ini config file for property Format. + logger::register_formatter_factory ("Uptime", boost::make_shared ()); + } + ~CommonInitialization () + { + // Indicate start of logging + LOG_INFO ("Log Start"); + } + }; + } + + void init () + { + CommonInitialization ci; + // auto sink = boost::make_shared> (); + // sink->set_filter (expr::is_debugger_present ()); + // logger::core::get ()->add_sink (sink); + } + + void init_from_config (std::istream& stream) + { + CommonInitialization ci; + try + { + // Still can throw even with the exception suppressor above. + logger::init_from_stream (stream); + } + catch (std::exception& e) + { + std::string err = "Caught exception initializing boost logging: "; + err += e.what (); + // Since we cannot be sure of boost log state, output to cerr and cout. + std::cerr << "ERROR: " << err << std::endl; + std::cout << "ERROR: " << err << std::endl; + LOG_ERROR (err); + } + } + + void disable () + { + logger::core::get ()->set_logging_enabled (false); + } + + void add_datafile_log (std::string const& log_file_name) + { + // Create a text file sink + boost::shared_ptr backend + ( + new sinks::text_ostream_backend() + ); + backend->add_stream (boost::shared_ptr (new std::ofstream (log_file_name))); + + // Flush after each log record + backend->auto_flush (true); + + // Create a sink for the backend + typedef sinks::synchronous_sink sink_t; + boost::shared_ptr sink (new sink_t (backend)); + + // The log output formatter + sink->set_formatter (expr::format ("[%1%][%2%] %3%") + % expr::attr ("TimeStamp") + % logger::trivial::severity + % expr::smessage + ); + + // Filter by severity and by DATALOG channel + sink->set_filter (logger::trivial::severity >= logger::trivial::info && + expr::attr ("Channel") == "DATALOG"); + + // Add it to the core + logger::core::get ()->add_sink (sink); + } +} diff --git a/Logger.hpp b/Logger.hpp new file mode 100644 index 000000000..f5af4094d --- /dev/null +++ b/Logger.hpp @@ -0,0 +1,57 @@ +#ifndef LOGGER_HPP__ +#define LOGGER_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +namespace Logger +{ + BOOST_LOG_GLOBAL_LOGGER (sys, + boost::log::sources::severity_channel_logger_mt); + BOOST_LOG_GLOBAL_LOGGER (data, + boost::log::sources::severity_channel_logger_mt); + + // trivial logging to console + void init (); + + // define logger(s) and sinks from a configuration stream + void init_from_config (std::istream& config_stream); + + // disable logging - useful for unit testing etc. + void disable (); + + // add a new file sink for LOG_DATA_* for Severity >= INFO + // this file sink will be used alongside any configured above + void add_data_file_log (std::string const& log_file_name); +} + +#define LOG_LOG_LOCATION(LOGGER, LEVEL, ARG) \ + BOOST_LOG_SEV (LOGGER, boost::log::trivial::LEVEL) \ + << boost::log::add_value ("Line", __LINE__) \ + << boost::log::add_value ("File", __FILE__) \ + << boost::log::add_value ("Function", __FUNCTION__) << ARG; + +/// System Log macros. +/// TRACE < DEBUG < INFO < WARN < ERROR < FATAL +#define LOG_TRACE(ARG) LOG_LOG_LOCATION (Logger::sys::get(), trace, ARG); +#define LOG_DEBUG(ARG) LOG_LOG_LOCATION (Logger::sys::get(), debug, ARG); +#define LOG_INFO(ARG) LOG_LOG_LOCATION (Logger::sys::get(), info, ARG); +#define LOG_WARN(ARG) LOG_LOG_LOCATION (Logger::sys::get(), warning, ARG); +#define LOG_ERROR(ARG) LOG_LOG_LOCATION (Logger::sys::get(), error, ARG); +#define LOG_FATAL(ARG) LOG_LOG_LOCATION (Logger::sys::get(), fatal, ARG); + +/// Data Log macros. Does not include LINE, FILE, FUNCTION. +/// TRACE < DEBUG < INFO < WARN < ERROR < FATAL +#define LOG_DATA_TRACE(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::trace) << ARG +#define LOG_DATA_DEBUG(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::debug) << ARG +#define LOG_DATA_INFO(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::info) << ARG +#define LOG_DATA_WARN(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::warning) << ARG +#define LOG_DATA_ERROR(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::error) << ARG +#define LOG_DATA_FATAL(ARG) BOOST_LOG_SEV (Logger::data::get(), boost::log::trivial::fatal) << ARG + +#endif diff --git a/main.cpp b/main.cpp index a3cb166a4..6a7651ad2 100644 --- a/main.cpp +++ b/main.cpp @@ -28,8 +28,10 @@ #include #include +#include "Logger.hpp" #include "revision_utils.hpp" #include "MetaDataRegistry.hpp" +#include "qt_helpers.hpp" #include "L10nLoader.hpp" #include "SettingsGroup.hpp" #include "TraceFile.hpp" diff --git a/qt_helpers.hpp b/qt_helpers.hpp index aae8c5f07..1d21789b4 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -2,6 +2,7 @@ #define QT_HELPERS_HPP_ #include +#include #include #include @@ -57,6 +58,18 @@ Qt::SplitBehaviorFlags const SkipEmptyParts = Qt::SkipEmptyParts; QString::SplitBehavior const SkipEmptyParts = QString::SkipEmptyParts; #endif +inline +std::ostream& operator << (std::ostream& os, QByteArray const& b) +{ + return os << b.constData (); +} + +inline +std::ostream& operator << (std::ostream& os, QString const& s) +{ + return os << s.toStdString (); +} + inline void throw_qstring (QString const& qs) { diff --git a/wsjtx_log_config.ini b/wsjtx_log_config.ini new file mode 100644 index 000000000..0a508d63e --- /dev/null +++ b/wsjtx_log_config.ini @@ -0,0 +1,90 @@ +# +# See http://www.boost.org/doc/libs/1_60_0/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.setup.filter_formatter +# +# Many of the property values have to be in quotes, best to just use quotes for all of them. +# +# SYSLOG is the System Log File for logging standard 'debug' type info. +# DATALOG is the Data log File for logging modification to business data. +# +[Core] +# Set DisableLogging to true to disable all logging. +DisableLogging="false" + +# SYSLOG - system log +[Sinks.SYSLOG] +Destination="TextFile" +# If Asynchronous true then thread dedicated to writing to log, otherwise blocks main thread to write. +Asynchronous="true" +# If AutoFlush is true then non-buffered output +AutoFlush="true" +Append="true" +# Line Formats available: TimeStamp, Uptime, Severity, LineID (counter), ProcessID, ThreadID, Line, File, Function +# TimeStamp and Uptime support boost date time format: +# http://www.boost.org/doc/libs/1_60_0/doc/html/date_time/date_time_io.html#date_time.format_flags +#Format="[%TimeStamp(format=\"%Y-%m-%d %H:%M:%S.%f\")%][%Uptime(format=\"%O:%M:%S.%f\")%][%Severity%] %File%(%Line%) %Function%: %Message%" +Format="[%TimeStamp(format=\"%Y-%m-%d %H:%M:%S.%f\")%][%Severity%\t] %Message%" +# Target directory in which rotated files will be stored. +Target="${AppLocalDataLocation}/old_logs" +# FileName pattern to use. %N is a counter for files. +FileName="${AppLocalDataLocation}/wsjtx_syslog.log" +TargetFileName="wsjtx_syslog_%5N.log" +# RotationSize in bytes, File size, in bytes, upon which file rotation will be performed. +# Time based rotation also available via RotationInterval and RotationTimePoint see boost log docs. +RotationSize="1048576" +EnableFinalRotation="false" +# Matching used so that only files matching FileName pattern are deleted. +ScanForFiles="Matching" +# MaxSize - Total size of files in the target directory in bytes upon which the oldest file will be deleted. +#MaxSize=100485760 +# MinFreeSpace - Minimum free space in the Target directory in bytes. Above this value oldest file is deleted. +#MinFreeSpace=1485760 +MaxFiles="10" +# Specify level of log, options are: trace, debug, info, warning, error, fatal +# Since Channel not part of filter all log output will be included. +# If only SYSLOG logging desired, change to: Filter="%Severity% >= trace & %Channel% matches \"SYSLOG\"" +Filter="%Severity% >= warning" + +# DATALOG - data log +[Sinks.DATALOG] +Destination="TextFile" +# If Asynchronous true then thread dedicated to writing to log, otherwise blocks main thread to write. +Asynchronous="true" +# If AutoFlush is true then non-buffered output +AutoFlush="true" +Append="true" +# Line Formats available: TimeStamp, Uptime, Severity, LineID (counter), ProcessID, ThreadID +# TimeStamp and Uptime support boost date time format: +# http://www.boost.org/doc/libs/1_60_0/doc/html/date_time/date_time_io.html#date_time.format_flags +Format="[%TimeStamp(format=\"%Y-%m-%d %H:%M:%S.%f\")%][%Uptime(format=\"%O:%M:%S.%f\")%][%Severity%] %Message%" +# Target directory in which rotated files will be stored. +Target="${AppLocalDataLocation}/old_logs" +# FileName pattern to use. %N is a counter for files. +FileName="${AppLocalDataLocation}/wsjtx_datalog.log" +TargetFileName="wsjtx_datalog_%5N.log" +# RotationSize in bytes, File size, in bytes, upon which file rotation will be performed. +# Time based rotation also available via RotationInterval and RotationTimePoint see boost log docs. +RotationSize="1048576" +EnableFinalRotation="false" +# Matching used so that only files matching FileName pattern are deleted. +ScanForFiles="Matching" +# MaxSize - Total size of files in the target directory in bytes upon which the oldest file will be deleted. +#MaxSize=100485760 +# MinFreeSpace - Minimum free space in the Target directory in bytes. Above this value oldest file is deleted. +#MinFreeSpace=1485760 +MaxFiles="10" +# Specify level of log, options are: trace, debug, info, warning, error, fatal +# Specify Channel otherwise all log output will be included. +Filter="%Severity% >= info & %Channel% matches \"DATALOG\"" + +# Console log, logs both DATALOG and SYSLOG +[Sinks.Console] +# Remove the following lines to disable console logging +Destination="Console" +# If AutoFlush is true then non-buffered output +AutoFlush="true" +# Line Formats available: TimeStamp, Uptime, Severity, LineID (counter), ProcessID, ThreadID +# TimeStamp and Uptime support boost date time format: +# http://www.boost.org/doc/libs/1_60_0/doc/html/date_time/date_time_io.html#date_time.format_flags +Format="[%TimeStamp(format=\"%Y-%m-%d %H:%M:%S.%f\")%][%Uptime(format=\"%O:%M:%S.%f\")%][%Severity%] - %Message%" +# Specify level of log, options are: trace, debug, info, warning, error, fatal +Filter="%Severity% >= warning"