diff --git a/CMakeLists.txt b/CMakeLists.txt index e0333b17..29d5913e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,7 +187,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h" "${CMAKE_SOURCE_DIR}/include/loot/exception/file_access_error.h" "${CMAKE_SOURCE_DIR}/include/loot/exception/git_state_error.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/game_type.h" - "${CMAKE_SOURCE_DIR}/include/loot/enum/log_verbosity.h" + "${CMAKE_SOURCE_DIR}/include/loot/enum/log_level.h" "${CMAKE_SOURCE_DIR}/include/loot/enum/message_type.h" "${CMAKE_SOURCE_DIR}/include/loot/game_interface.h" "${CMAKE_SOURCE_DIR}/include/loot/loot_version.h" diff --git a/include/loot/api.h b/include/loot/api.h index 55e3575c..8e7f1777 100644 --- a/include/loot/api.h +++ b/include/loot/api.h @@ -25,6 +25,7 @@ #ifndef LOOT_API_H #define LOOT_API_H +#include #include #include @@ -36,7 +37,7 @@ #include "loot/exception/file_access_error.h" #include "loot/exception/git_state_error.h" #include "loot/enum/game_type.h" -#include "loot/enum/log_verbosity.h" +#include "loot/enum/log_level.h" #include "loot/loot_version.h" namespace loot { @@ -47,21 +48,14 @@ namespace loot { /**@{*/ /** - * @brief Set the API's logging verbosity. - * @details The default is ``LogVerbosity::off``. - * @param verbosity - * The logging verbosity to set. + * @brief Set the callback function that is called when logging. + * @details If this function is not called, the default behaviour is to + * print messages to the console. + * @param callback + * The function called when logging. The first parameter is the + * level of the message being logged, and the second is the message. */ -LOOT_API void SetLoggingVerbosity(LogVerbosity verbosity); - -/** - * @brief Set the file path that logging statements are written to. - * @details If no file is set the default behaviour is to print logging - * statements to the console. - * @param path - * The log file path. - */ -LOOT_API void SetLogFile(const std::string& path); +LOOT_API void SetLoggingCallback(std::function callback); /**@}*/ /**********************************************************************//** diff --git a/include/loot/enum/log_verbosity.h b/include/loot/enum/log_level.h similarity index 82% rename from include/loot/enum/log_verbosity.h rename to include/loot/enum/log_level.h index b0989622..d1a75d0d 100644 --- a/include/loot/enum/log_verbosity.h +++ b/include/loot/enum/log_level.h @@ -22,20 +22,23 @@ along with LOOT. If not, see . */ -#ifndef LOOT_LOG_VERBOSITY -#define LOOT_LOG_VERBOSITY +#ifndef LOOT_LOG_LEVEL +#define LOOT_LOG_LEVEL /** * The namespace used by the LOOT API. */ namespace loot { /** - * @brief Codes used to specify different levels of API logging verbosity. + * @brief Codes used to specify different levels of API logging. */ -enum struct LogVerbosity : unsigned int { - off, - warning, +enum struct LogLevel : unsigned int { trace, + debug, + info, + warning, + error, + fatal }; } diff --git a/src/api/api.cpp b/src/api/api.cpp index d85ad51e..71b72103 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -46,34 +46,54 @@ std::string ResolvePath(const std::string& path) { return fs::read_symlink(path).string(); } -LOOT_API void SetLoggingVerbosity(LogVerbosity verbosity) { - switch (verbosity) { - case LogVerbosity::off: - boost::log::core::get()->set_logging_enabled(false); - break; - case LogVerbosity::warning: - boost::log::core::get()->set_filter(boost::log::trivial::severity > boost::log::trivial::warning); - boost::log::core::get()->set_logging_enabled(true); - break; - case LogVerbosity::trace: - boost::log::core::get()->reset_filter(); - boost::log::core::get()->set_logging_enabled(true); - break; +LogLevel mapFromBoostLog(boost::log::trivial::severity_level severity) { + using boost::log::trivial::severity_level; + switch (severity) { + case severity_level::trace: + return LogLevel::trace; + case severity_level::debug: + return LogLevel::debug; + case severity_level::info: + return LogLevel::info; + case severity_level::warning: + return LogLevel::warning; + case severity_level::error: + return LogLevel::error; + case severity_level::fatal: + return LogLevel::fatal; + default: + return LogLevel::trace; } } -LOOT_API void SetLogFile(const std::string& path) { - boost::log::add_file_log( - boost::log::keywords::file_name = path, - boost::log::keywords::auto_flush = true, - boost::log::keywords::format = ( - boost::log::expressions::stream - << "[" << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%H:%M:%S.%f") << "]" - << " [" << boost::log::trivial::severity << "]: " - << boost::log::expressions::smessage - ) - ); - boost::log::add_common_attributes(); +class LoggingSink : public boost::log::sinks::basic_formatted_sink_backend { +public: + LoggingSink(std::function callback) { + this->callback = callback; + } + + void consume(const boost::log::record_view& rec, const std::string& str) { + using boost::log::trivial::severity_level; + auto severity = rec.attribute_values()[boost::log::aux::default_attribute_names::severity()].extract(); + if (!severity) { + return; + } + + callback(mapFromBoostLog(*severity), str.c_str()); + } + +private: + std::function callback; +}; + +LOOT_API void SetLoggingCallback(std::function callback) { + typedef boost::log::sinks::synchronous_sink sink_t; + + auto sink_backend = boost::make_shared(callback); + boost::shared_ptr sink(new sink_t(sink_backend)); + + boost::log::core::get()->remove_all_sinks(); + boost::log::core::get()->add_sink(sink); } LOOT_API bool IsCompatible(const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch) { diff --git a/src/tests/api/interface/main.cpp b/src/tests/api/interface/main.cpp index 46a0f084..4657b68a 100644 --- a/src/tests/api/interface/main.cpp +++ b/src/tests/api/interface/main.cpp @@ -24,6 +24,7 @@ #include +#include "loot/api.h" #include "tests/api/interface/create_game_handle_test.h" #include "tests/api/interface/database_interface_test.h" #include "tests/api/interface/game_interface_test.h" @@ -33,15 +34,37 @@ #include int main(int argc, char **argv) { - //Set the locale to get encoding conversions working correctly. + //Set the locale to get encoding conversions working correctly. std::locale::global(boost::locale::generator().generate("")); boost::filesystem::path::imbue(std::locale()); loot::InitialiseLocale(""); - //Disable logging or else stdout will get overrun. - boost::log::core::get()->set_logging_enabled(false); - loot::SetLoggingVerbosity(loot::LogVerbosity::off); + //Set a null log callback or else stdout will get overrun. + loot::SetLoggingCallback([](loot::LogLevel, const char *) {}); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } + +namespace loot { +namespace test { +TEST(SetLoggingCallback, shouldWriteMessagesToGivenCallback) { + std::string loggedMessages; + SetLoggingCallback([&](LogLevel level, const char * string) { + loggedMessages += std::string(string); + }); + + try { + CreateGameHandle(GameType::tes4, "", ""); + } + catch (...) { + EXPECT_EQ("Initialising load order data for game of type 0 at: \"\"", loggedMessages); + + SetLoggingCallback([](LogLevel, const char *) {}); + return; + } + + FAIL(); +} +} +}