diff --git a/CMakeLists.txt b/CMakeLists.txt index 48f6712..ca689e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,13 +28,16 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") add_compile_options(/MT$<$:d>) endif() -# Define flags variables for later use +# Set global flags and define flags variables for later use if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(enableWarningsFlags "-Wall;-Wextra") set(disableWarningsFlags "-w") elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(enableWarningsFlags "/W4") set(disableWarningsFlags "/W0") + + # Boost.Log causes this warning + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4714") endif() # Enable project folders @@ -46,7 +49,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(Boost_USE_STATIC_LIBS ON) # Use static libs set(Boost_USE_MULTITHREADED ON) # Enable multithreading support set(Boost_USE_STATIC_RUNTIME ON) # Use static C++ runtime -find_package(Boost REQUIRED COMPONENTS filesystem locale system) +find_package(Boost REQUIRED COMPONENTS filesystem locale system log date_time thread chrono) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) # ... C++ Format @@ -105,6 +108,7 @@ set(SOURCE_FILES src/NiceCmdLineOutput.cpp src/TablePrinter.cpp src/ProgressBar.cpp + src/logging.cpp ) add_executable(rhubarb ${SOURCE_FILES}) target_link_libraries(rhubarb ${Boost_LIBRARIES} cppFormat sphinxbase pocketSphinx) diff --git a/src/logging.cpp b/src/logging.cpp new file mode 100644 index 0000000..3c8e327 --- /dev/null +++ b/src/logging.cpp @@ -0,0 +1,66 @@ +#include "logging.h" +#include +#include +#include + +using std::string; +using std::lock_guard; +using boost::log::sinks::text_ostream_backend; +using boost::log::record_view; +using boost::log::sinks::unlocked_sink; + +namespace expr = boost::log::expressions; + +string toString(LogLevel level) { + constexpr size_t levelCount = static_cast(LogLevel::EndSentinel); + static const std::array strings = { + "Trace", "Debug", "Info", "Warning", "Error", "Fatal" + }; + return strings.at(static_cast(level)); +} + +PausableBackendAdapter::PausableBackendAdapter(boost::shared_ptr backend) : + backend(backend) {} + +void PausableBackendAdapter::consume(const record_view& recordView, const string message) { + lock_guard lock(mutex); + if (isPaused) { + buffer.push_back(std::make_tuple(recordView, message)); + } else { + backend->consume(recordView, message); + } +} + +void PausableBackendAdapter::pause() { + lock_guard lock(mutex); + isPaused = true; +} + +void PausableBackendAdapter::resume() { + lock_guard lock(mutex); + isPaused = false; + for (const auto& tuple : buffer) { + backend->consume(std::get(tuple), std::get(tuple)); + } + buffer.clear(); +} + +boost::shared_ptr initLogging() { + // Create logging backend that logs to stderr + auto streamBackend = boost::make_shared(); + streamBackend->add_stream(boost::shared_ptr(&std::cerr, [](std::ostream*) {})); + streamBackend->auto_flush(true); + + // Create an adapter that allows us to pause, buffer, and resume log output + auto pausableAdapter = boost::make_shared(streamBackend); + + // Create a sink that feeds into the adapter + auto sink = boost::make_shared>(pausableAdapter); + + // Set output formatting + sink->set_formatter(expr::stream << "[" << expr::attr("Severity") << "] " << expr::smessage); + + boost::log::core::get()->add_sink(sink); + return pausableAdapter; +} + diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 0000000..c1312e9 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +enum class LogLevel { + Trace, + Debug, + Info, + Warning, + Error, + Fatal, + EndSentinel +}; + +std::string toString(LogLevel level); + +template +std::basic_ostream& operator<< (std::basic_ostream& stream, LogLevel level) { + return stream << toString(level); +} + +using LoggerType = boost::log::sources::severity_logger_mt; + +BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(globalLogger, LoggerType) + +#define LOG(level) \ + BOOST_LOG_STREAM_WITH_PARAMS(globalLogger::get(), (::boost::log::keywords::severity = level)) + +#define LOG_TRACE LOG(LogLevel::Trace) +#define LOG_DEBUG LOG(LogLevel::Debug) +#define LOG_INFO LOG(LogLevel::Info) +#define LOG_WARNING LOG(LogLevel::Warning) +#define LOG_ERROR LOG(LogLevel::Error) +#define LOG_FATAL LOG(LogLevel::Fatal) + +class PausableBackendAdapter : + public boost::log::sinks::basic_formatted_sink_backend +{ +public: + PausableBackendAdapter(boost::shared_ptr backend); + void consume(const boost::log::record_view& recordView, const std::string message); + void pause(); + void resume(); +private: + boost::shared_ptr backend; + std::vector> buffer; + std::mutex mutex; + bool isPaused = false; +}; + +boost::shared_ptr initLogging(); diff --git a/src/main.cpp b/src/main.cpp index 622ea70..9230d73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include "appInfo.h" #include "NiceCmdLineOutput.h" #include "ProgressBar.h" +#include "logging.h" +#include using std::exception; using std::string; @@ -79,6 +81,9 @@ namespace TCLAP { } int main(int argc, char *argv[]) { + auto logOutputController = initLogging(); + logOutputController->pause(); + // Define command-line parameters const char argumentValueSeparator = ' '; TCLAP::CmdLine cmd(appName, argumentValueSeparator, appVersion); @@ -88,6 +93,12 @@ int main(int argc, char *argv[]) { TCLAP::ValueArg> dialog("d", "dialog", "The text of the dialog.", false, boost::optional(), "string", cmd); try { + auto resumeLogging = gsl::finally([&]() { + std::cerr << std::endl << std::endl; + logOutputController->resume(); + std::cerr << std::endl; + }); + // Parse command line cmd.parse(argc, argv); @@ -120,13 +131,15 @@ int main(int argc, char *argv[]) { } catch (TCLAP::ArgException& e) { // Error parsing command-line args. cmd.getOutput()->failure(cmd, e); + std::cerr << std::endl; return 1; } catch (TCLAP::ExitException&) { // A built-in TCLAP command (like --help) has finished. Exit application. + std::cerr << std::endl; return 0; } catch (const exception& e) { // Generic error - std::cerr << "An error occurred. " << getMessage(e); + std::cerr << "An error occurred. " << getMessage(e) << std::endl; return 1; } }