diff --git a/CMakeLists.txt b/CMakeLists.txt index a4d2137..c8a3ac7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,10 +36,6 @@ elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(enableWarningsFlags "/W4") set(disableWarningsFlags "/W0") - # Disable warning C4714: function '...' marked as __forceinline not inlined - # This is caused by Boost.Log - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4714") - # Disable warning C4456: declaration of '...' hides previous local declaration # I'm doing that on purpose. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4458") @@ -54,7 +50,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 log date_time thread chrono) +find_package(Boost REQUIRED COMPONENTS filesystem locale system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) # ... C++ Format diff --git a/src/Exporter.cpp b/src/Exporter.cpp index e3db9ef..91d20a5 100644 --- a/src/Exporter.cpp +++ b/src/Exporter.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using std::string; using boost::property_tree::ptree; diff --git a/src/audio/voiceActivityDetection.cpp b/src/audio/voiceActivityDetection.cpp index 4b2f82c..661b9c7 100644 --- a/src/audio/voiceActivityDetection.cpp +++ b/src/audio/voiceActivityDetection.cpp @@ -48,7 +48,7 @@ Timeline detectVoiceActivity(std::unique_ptr audioStream) { // Log for (const auto& element : activity) { - logTimedEvent("utterance", static_cast(element), std::string()); + logging::logTimedEvent("utterance", static_cast(element), std::string()); } return activity; diff --git a/src/logging.cpp b/src/logging.cpp index 0e99b4a..c93a748 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -1,123 +1,131 @@ #include "logging.h" -#include -#include -#include -#include -#include -#include -// ReSharper disable once CppUnusedIncludeDirective -#include -#include +#include +#include +using namespace logging; 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; using std::vector; using std::tuple; using std::make_tuple; - -namespace expr = boost::log::expressions; -namespace keywords = boost::log::keywords; -namespace sinks = boost::log::sinks; -namespace attr = boost::log::attributes; +using std::shared_ptr; +using std::lock_guard; template <> -const string& getEnumTypeName() { +const string& getEnumTypeName() { static const string name = "LogLevel"; return name; } template <> -const vector>& getEnumMembers() { - static const vector> values = { - make_tuple(LogLevel::Trace, "Trace"), - make_tuple(LogLevel::Debug, "Debug"), - make_tuple(LogLevel::Info, "Info"), - make_tuple(LogLevel::Warning, "Warning"), - make_tuple(LogLevel::Error, "Error"), - make_tuple(LogLevel::Fatal, "Fatal") +const vector>& getEnumMembers() { + static const vector> values = { + make_tuple(Level::Trace, "Trace"), + make_tuple(Level::Debug, "Debug"), + make_tuple(Level::Info, "Info"), + make_tuple(Level::Warn, "Warn"), + make_tuple(Level::Error, "Error"), + make_tuple(Level::Fatal, "Fatal") }; return values; } -std::ostream& operator<<(std::ostream& stream, LogLevel value) { +std::ostream& operator<<(std::ostream& stream, Level value) { return stream << enumToString(value); } -std::istream& operator>>(std::istream& stream, LogLevel& value) { +std::istream& operator>>(std::istream& stream, Level& value) { string name; stream >> name; - value = parseEnum(name); + value = parseEnum(name); return stream; } -PausableBackendAdapter::PausableBackendAdapter(boost::shared_ptr backend) : - backend(backend) {} - -PausableBackendAdapter::~PausableBackendAdapter() { - resume(); +Entry::Entry(Level level, const string& message) : + level(level), + message(message) +{ + time(×tamp); } -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); +string SimpleConsoleFormatter::format(const Entry& entry) { + return fmt::format("[{0}] {1}", entry.level, entry.message); +} + +string SimpleFileFormatter::format(const Entry& entry) { + return fmt::format("[{0}] {1}", formatTime(entry.timestamp, "%F %H:%M:%S"), consoleFormatter.format(entry)); +} + +LevelFilter::LevelFilter(shared_ptr innerSink, Level minLevel) : + innerSink(innerSink), + minLevel(minLevel) +{} + +void LevelFilter::receive(const Entry& entry) { + if (entry.level >= minLevel) { + innerSink->receive(entry); } } -void PausableBackendAdapter::pause() { - lock_guard lock(mutex); - isPaused = true; +StreamSink::StreamSink(shared_ptr stream, shared_ptr formatter) : + stream(stream), + formatter(formatter) +{} + +void StreamSink::receive(const Entry& entry) { + string line = formatter->format(entry); + *stream << line << std::endl; } -void PausableBackendAdapter::resume() { +StdErrSink::StdErrSink(shared_ptr formatter) : + StreamSink(std::shared_ptr(&std::cerr, [](void*){}), formatter) +{} + +PausableSink::PausableSink(shared_ptr innerSink) : + innerSink(innerSink) +{} + +void PausableSink::receive(const Entry& entry) { + lock_guard lock(mutex); + if (isPaused) { + buffer.push_back(entry); + } else { + innerSink->receive(entry); + } +} + +void PausableSink::pause() { + lock_guard lock(mutex); + isPaused = true; + +} + +void PausableSink::resume() { lock_guard lock(mutex); isPaused = false; - for (const auto& tuple : buffer) { - backend->consume(std::get(tuple), std::get(tuple)); + for (const Entry& entry : buffer) { + innerSink->receive(entry); } buffer.clear(); } -BOOST_LOG_GLOBAL_LOGGER_INIT(globalLogger, LoggerType) { - LoggerType logger; - - logger.add_attribute("TimeStamp", attr::local_clock()); - - return logger; +std::mutex& getLogMutex() { + static std::mutex mutex; + return mutex; } -BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", LogLevel) - -boost::shared_ptr addPausableStderrSink(LogLevel minLogLevel) { - // 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); - sink->set_formatter(expr::stream << "[" << severity << "] " << expr::smessage); - sink->set_filter(severity >= minLogLevel); - boost::log::core::get()->add_sink(sink); - - return pausableAdapter; +vector>& getSinks() { + static vector> sinks; + return sinks; } -void addFileSink(const boost::filesystem::path& logFilePath, LogLevel minLogLevel) { - auto textFileBackend = boost::make_shared( - keywords::file_name = logFilePath.string()); - auto sink = boost::make_shared>(textFileBackend); - sink->set_formatter(expr::stream - << "[" << expr::format_date_time("TimeStamp", "%Y-%m-%d %H:%M:%S") - << "] [" << severity << "] " << expr::smessage); - sink->set_filter(severity >= minLogLevel); - boost::log::core::get()->add_sink(sink); +void logging::addSink(shared_ptr sink) { + lock_guard lock(getLogMutex()); + getSinks().push_back(sink); +} + +void logging::log(Level level, const string& message) { + lock_guard lock(getLogMutex()); + for (auto& sink : getSinks()) { + sink->receive(Entry(level, message)); + } } diff --git a/src/logging.h b/src/logging.h index 0f1a7b4..1b9b0a3 100644 --- a/src/logging.h +++ b/src/logging.h @@ -1,86 +1,145 @@ #pragma once -#include -#include -#include -#include #include #include #include #include -#include "centiseconds.h" -#include -#include "tools.h" #include "enumTools.h" +#include "tools.h" #include "Timed.h" -enum class LogLevel { - Trace, - Debug, - Info, - Warning, - Error, - Fatal, - EndSentinel -}; +namespace logging { + + enum class Level { + Trace, + Debug, + Info, + Warn, + Error, + Fatal, + EndSentinel + }; + +} template<> -const std::string& getEnumTypeName(); +const std::string& getEnumTypeName(); template<> -const std::vector>& getEnumMembers(); +const std::vector>& getEnumMembers(); -std::ostream& operator<<(std::ostream& stream, LogLevel value); +std::ostream& operator<<(std::ostream& stream, logging::Level value); -std::istream& operator>>(std::istream& stream, LogLevel& value); +std::istream& operator>>(std::istream& stream, logging::Level& value); -using LoggerType = boost::log::sources::severity_logger_mt; +namespace logging { -BOOST_LOG_GLOBAL_LOGGER(globalLogger, LoggerType) + struct Entry { + Entry(Level level, const std::string& message); -#define LOG(level) \ - BOOST_LOG_STREAM_WITH_PARAMS(globalLogger::get(), (::boost::log::keywords::severity = level)) + time_t timestamp; + Level level; + std::string message; + }; -#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 Formatter { + public: + virtual ~Formatter() = default; + virtual std::string format(const Entry& entry) = 0; + }; -class PausableBackendAdapter : - public boost::log::sinks::basic_formatted_sink_backend -{ -public: - PausableBackendAdapter(boost::shared_ptr backend); - ~PausableBackendAdapter(); - 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; -}; + class SimpleConsoleFormatter : public Formatter { + public: + std::string format(const Entry& entry) override; + }; -boost::shared_ptr addPausableStderrSink(LogLevel minLogLevel); + class SimpleFileFormatter : public Formatter { + public: + std::string format(const Entry& entry) override; + private: + SimpleConsoleFormatter consoleFormatter; + }; -void addFileSink(const boost::filesystem::path& logFilePath, LogLevel minLogLevel); + class Sink { + public: + virtual ~Sink() = default; + virtual void receive(const Entry& entry) = 0; + }; -template -void logTimedEvent(const std::string& eventName, const Timed timedValue) { - LOG_DEBUG - << "##" << eventName << "[" << formatDuration(timedValue.getStart()) << "-" << formatDuration(timedValue.getEnd()) << "]: " - << timedValue.getValue(); -} - -template -void logTimedEvent(const std::string& eventName, const TimeRange& timeRange, const TValue& value) { - logTimedEvent(eventName, Timed(timeRange, value)); -} - -template -void logTimedEvent(const std::string& eventName, centiseconds start, centiseconds end, const TValue& value) { - logTimedEvent(eventName, Timed(start, end, value)); + class LevelFilter : public Sink { + public: + LevelFilter(std::shared_ptr innerSink, Level minLevel); + void receive(const Entry& entry) override; + private: + std::shared_ptr innerSink; + Level minLevel; + }; + + class StreamSink : public Sink { + public: + StreamSink(std::shared_ptr stream, std::shared_ptr formatter); + void receive(const Entry& entry) override; + private: + std::shared_ptr stream; + std::shared_ptr formatter; + }; + + class StdErrSink : public StreamSink { + public: + explicit StdErrSink(std::shared_ptr formatter); + }; + + class PausableSink : public Sink { + public: + explicit PausableSink(std::shared_ptr innerSink); + void receive(const Entry& entry) override; + void pause(); + void resume(); + private: + std::shared_ptr innerSink; + std::vector buffer; + std::mutex mutex; + bool isPaused = false; + }; + + void addSink(std::shared_ptr sink); + + void log(Level level, const std::string& message); + + template + void logFormat(Level level, fmt::CStringRef format, const Args&... args) { + log(level, fmt::format(format, args...)); + } + +#define LOG_WITH_LEVEL(levelName, levelEnum) \ + inline void levelName(const std::string& message) { \ + log(Level::levelEnum, message); \ + } \ + template \ + void levelName ## Format(fmt::CStringRef format, const Args&... args) { \ + logFormat(Level::levelEnum, format, args...); \ + } + + LOG_WITH_LEVEL(trace, Trace) + LOG_WITH_LEVEL(debug, Debug) + LOG_WITH_LEVEL(info, Info) + LOG_WITH_LEVEL(warn, Warn) + LOG_WITH_LEVEL(error, Error) + LOG_WITH_LEVEL(fatal, Fatal) + + template + void logTimedEvent(const std::string& eventName, const Timed timedValue) { + debugFormat("##{0} [{1}-{2}]: {3}", + eventName, formatDuration(timedValue.getStart()), formatDuration(timedValue.getEnd()), timedValue.getValue()); + } + + template + void logTimedEvent(const std::string& eventName, const TimeRange& timeRange, const TValue& value) { + logTimedEvent(eventName, Timed(timeRange, value)); + } + + template + void logTimedEvent(const std::string& eventName, centiseconds start, centiseconds end, const TValue& value) { + logTimedEvent(eventName, Timed(start, end, value)); + } } diff --git a/src/main.cpp b/src/main.cpp index 9fb2106..3fed566 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,8 @@ using std::string; using std::vector; using std::unique_ptr; using std::make_unique; +using std::shared_ptr; +using std::make_shared; using std::map; using std::chrono::duration; using std::chrono::duration_cast; @@ -47,7 +49,7 @@ unique_ptr createAudioStream(path filePath) { // Tell TCLAP how to handle our types namespace TCLAP { template<> - struct ArgTraits { + struct ArgTraits { typedef ValueLike ValueCategory; }; template<> @@ -56,8 +58,25 @@ namespace TCLAP { }; } +shared_ptr addPausableStdErrSink(logging::Level minLevel) { + auto stdErrSink = make_shared(make_shared()); + auto pausableSink = make_shared(stdErrSink); + auto levelFilter = make_shared(pausableSink, minLevel); + logging::addSink(levelFilter); + return pausableSink; +} + +void addFileSink(path path, logging::Level minLevel) { + auto file = make_shared(); + file->exceptions(std::ifstream::failbit | std::ifstream::badbit); + file->open(path); + auto FileSink = make_shared(file, make_shared()); + auto levelFilter = make_shared(FileSink, minLevel); + logging::addSink(levelFilter); +} + int main(int argc, char *argv[]) { - auto pausableStderrSink = addPausableStderrSink(LogLevel::Warning); + auto pausableStderrSink = addPausableStdErrSink(logging::Level::Warn); pausableStderrSink->pause(); // Define command-line parameters @@ -65,9 +84,9 @@ int main(int argc, char *argv[]) { tclap::CmdLine cmd(appName, argumentValueSeparator, appVersion); cmd.setExceptionHandling(false); cmd.setOutput(new NiceCmdLineOutput()); - auto logLevels = vector(getEnumValues()); - tclap::ValuesConstraint logLevelConstraint(logLevels); - tclap::ValueArg logLevel("", "logLevel", "The minimum log level to log", false, LogLevel::Debug, &logLevelConstraint, cmd); + auto logLevels = vector(getEnumValues()); + tclap::ValuesConstraint logLevelConstraint(logLevels); + tclap::ValueArg logLevel("", "logLevel", "The minimum log level to log", false, logging::Level::Debug, &logLevelConstraint, cmd); tclap::ValueArg logFileName("", "logFile", "The log file path.", false, string(), "string", cmd); tclap::ValueArg dialog("d", "dialog", "The text of the dialog.", false, string(), "string", cmd); auto exportFormats = vector(getEnumValues()); diff --git a/src/mouthAnimation.cpp b/src/mouthAnimation.cpp index f802140..d551b10 100644 --- a/src/mouthAnimation.cpp +++ b/src/mouthAnimation.cpp @@ -72,7 +72,7 @@ Timeline animate(const Timeline &phones) { for (auto& timedPhone : phones) { Timed timedShape(static_cast(timedPhone), getShape(timedPhone.getValue())); shapes.set(timedShape); - logTimedEvent("shape", timedShape); + logging::logTimedEvent("shape", timedShape); } return shapes; diff --git a/src/phoneExtraction.cpp b/src/phoneExtraction.cpp index 42b315e..3532f81 100644 --- a/src/phoneExtraction.cpp +++ b/src/phoneExtraction.cpp @@ -98,18 +98,18 @@ void processAudioStream(AudioStream& audioStream16kHz, function recognizeWords(unique_ptr audioStream, ps_decoder_t& recognizer, ProgressSink& progressSink) { @@ -169,7 +169,7 @@ vector recognizeWords(unique_ptr audioStream, ps_decoder_t& int firstFrame, lastFrame; ps_seg_frames(it, &firstFrame, &lastFrame); - logTimedEvent("word", centiseconds(firstFrame), centiseconds(lastFrame + 1), word); + logging::logTimedEvent("word", centiseconds(firstFrame), centiseconds(lastFrame + 1), word); } return result; @@ -278,7 +278,7 @@ Timeline getPhoneAlignment(const vector& wordIds, unique_ptr timedPhone(start, start + duration, parseEnum(phoneName)); result.set(timedPhone); - logTimedEvent("phone", timedPhone); + logging::logTimedEvent("phone", timedPhone); } return result; } diff --git a/src/tools.cpp b/src/tools.cpp index e36f9ef..81caec2 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,6 +1,7 @@ #include "tools.h" #include #include +#include using std::string; using std::chrono::duration; @@ -8,3 +9,14 @@ using std::chrono::duration; string formatDuration(duration seconds) { return fmt::format("{0:.2f}", seconds.count()); } + +string formatTime(time_t time, const string& format) { + tm* timeInfo = localtime(&time); + std::vector buffer(20); + bool success = false; + while (!success) { + success = strftime(buffer.data(), buffer.size(), format.c_str(), timeInfo) != 0; + if (!success) buffer.resize(buffer.size() * 2); + } + return string(buffer.data()); +} diff --git a/src/tools.h b/src/tools.h index fd15ff4..85d7827 100644 --- a/src/tools.h +++ b/src/tools.h @@ -9,4 +9,6 @@ template using lambda_unique_ptr = std::unique_ptr>; -std::string formatDuration(std::chrono::duration seconds); \ No newline at end of file +std::string formatDuration(std::chrono::duration seconds); + +std::string formatTime(time_t time, const std::string& format); \ No newline at end of file