diff --git a/src/test/libslic3r/test_log.cpp b/src/test/libslic3r/test_log.cpp index 83764059e..33d250331 100644 --- a/src/test/libslic3r/test_log.cpp +++ b/src/test/libslic3r/test_log.cpp @@ -9,6 +9,8 @@ SCENARIO( "_Log output with std::string methods" ) { GIVEN("A log stream and a _Log object") { std::stringstream log; std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_level(log_t::DEBUG); + cut->set_inclusive(true); WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { log.clear(); cut->fatal_error("Topic", "This"); @@ -64,6 +66,8 @@ SCENARIO( "_Log output with << methods" ) { GIVEN("A log stream and a _Log object") { std::stringstream log; std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_level(log_t::DEBUG); + cut->set_inclusive(true); WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { log.clear(); cut->fatal_error("Topic") << "This"; @@ -117,3 +121,470 @@ SCENARIO( "_Log output with << methods" ) { } } +SCENARIO( "_Log output inclusive filtering with std::string methods" ) { + GIVEN("Single, inclusive log level of FERR (highest)") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->clear_level(log_t::FERR); + cut->set_inclusive(true); + cut->set_level(log_t::FERR); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("Single, inclusive log level of ERR (second-highest)") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(true); + cut->set_level(log_t::ERR); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is Topic ERR: This\\n") { + REQUIRE(log.str() == "Topic ERR: This\n"); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("Single, inclusive log level of WARN (third-highest)") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(true); + cut->set_level(log_t::WARN); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is Topic ERR: This\\n") { + REQUIRE(log.str() == "Topic ERR: This\n"); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is Topic WARN: This\\n") { + REQUIRE(log.str() == "Topic WARN: This\n"); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("Single, inclusive log level of INFO (fourth-highest)") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(true); + cut->set_level(log_t::INFO); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is Topic ERR: This\\n") { + REQUIRE(log.str() == "Topic ERR: This\n"); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is Topic WARN: This\\n") { + REQUIRE(log.str() == "Topic WARN: This\n"); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is Topic INFO: This\\n") { + REQUIRE(log.str() == "Topic INFO: This\n"); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("Single, inclusive log level of DEBUG (fifth-highest)") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(true); + cut->set_level(log_t::DEBUG); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is Topic ERR: This\\n") { + REQUIRE(log.str() == "Topic ERR: This\n"); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is Topic WARN: This\\n") { + REQUIRE(log.str() == "Topic WARN: This\n"); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is Topic INFO: This\\n") { + REQUIRE(log.str() == "Topic INFO: This\n"); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is Topic DEBUG: This\\n") { + REQUIRE(log.str() == "Topic DEBUG: This\n"); + } + } + } +} + +SCENARIO( "_Log output set filtering with std::string methods" ) { + + GIVEN("log level of DEBUG only") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::DEBUG); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is Topic DEBUG: This\\n") { + REQUIRE(log.str() == "Topic DEBUG: This\n"); + } + } + } + GIVEN("log level of INFO only") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::INFO); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is Topic INFO: This\\n") { + REQUIRE(log.str() == "Topic INFO: This\n"); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("log level of WARN only") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::WARN); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is Topic WARN: This\\n") { + REQUIRE(log.str() == "Topic WARN: This\n"); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("log level of FERR only") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::FERR); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is Topic FERR: This\\n") { + REQUIRE(log.str() == "Topic FERR: This\n"); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } + GIVEN("log level of DEBUG and ERR") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::DEBUG); + cut->set_level(log_t::ERR); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is Topic ERR: This\\n") { + REQUIRE(log.str() == "Topic ERR: This\n"); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is Topic DEBUG: This\\n") { + REQUIRE(log.str() == "Topic DEBUG: This\n"); + } + } + } + GIVEN("log level of INFO and WARN") { + std::stringstream log; + std::unique_ptr<_Log> cut { _Log::make_log(log) }; + cut->set_inclusive(false); + cut->clear_level(log_t::ALL); + cut->set_level(log_t::INFO); + cut->set_level(log_t::WARN); + cut->set_inclusive(false); + WHEN("fatal_error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->fatal_error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("error is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->error("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + WHEN("warn is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->warn("Topic", "This"); + THEN("Output string is Topic WARN: This\\n") { + REQUIRE(log.str() == "Topic WARN: This\n"); + } + } + WHEN("info is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->info("Topic", "This"); + THEN("Output string is Topic INFO: This\\n") { + REQUIRE(log.str() == "Topic INFO: This\n"); + } + } + WHEN("debug is called with topic \"Topic\" and text \"This\"") { + log.clear(); + cut->debug("Topic", "This"); + THEN("Output string is blank") { + REQUIRE(log.str() == ""); + } + } + } +} diff --git a/xs/src/libslic3r/Log.cpp b/xs/src/libslic3r/Log.cpp index f9d763447..07ccf4f7a 100644 --- a/xs/src/libslic3r/Log.cpp +++ b/xs/src/libslic3r/Log.cpp @@ -1,11 +1,22 @@ -#include "Log.hpp" #include #include #include #include +#include "Log.hpp" + +/// Local class to suppress output +class NullStream : public std::streambuf +{ +public: + int overflow(int c) { return c; } +}; + namespace Slic3r { +static NullStream log_null; +static std::ostream null_log(&log_null); + std::unique_ptr<_Log> slic3r_log {_Log::make_log()}; _Log::_Log() : _out(std::clog), _wout(std::wclog) { @@ -13,28 +24,46 @@ _Log::_Log() : _out(std::clog), _wout(std::wclog) { _Log::_Log(std::ostream& out) : _out(out), _wout(std::wclog) { } + _Log::_Log(std::wostream& out) : _out(std::clog), _wout(out) { } +bool _Log::_has_log_level(log_t lvl) { + if (!this->_inclusive_levels && this->_log_level.find(lvl) != this->_log_level.end()) { + return true; + } else if (this->_inclusive_levels && *(std::max_element(this->_log_level.cbegin(), this->_log_level.cend())) >= lvl) { + return true; + } + return false; +} + void _Log::fatal_error(const std::string& topic, const std::wstring& message) { // _wout << this->converter.from_bytes(topic); - _wout << std::setw(6) << "FERR" << ": "; - _wout << message << std::endl; + if (this->_has_log_level(log_t::FERR)) { + _wout << std::setw(6) << "FERR" << ": "; + _wout << message << std::endl; + } } void _Log::fatal_error(const std::string& topic, const std::string& message) { this->fatal_error(topic) << message << std::endl; } std::ostream& _Log::fatal_error(const std::string& topic) { - _out << topic << std::setfill(' ') << std::setw(6) << "FERR" << ": "; - return _out; + if (this->_has_log_level(log_t::FERR)) { + _out << topic << std::setfill(' ') << std::setw(6) << "FERR" << ": "; + return _out; + } + return null_log; } void _Log::error(const std::string& topic, const std::string& message) { this->error(topic) << message << std::endl; } std::ostream& _Log::error(const std::string& topic) { - _out << topic << std::setfill(' ') << std::setw(6) << "ERR" << ": "; - return _out; + if (this->_has_log_level(log_t::ERR)) { + _out << topic << std::setfill(' ') << std::setw(6) << "ERR" << ": "; + return _out; + } + return null_log; } void _Log::info(const std::string& topic, const std::string& message) { @@ -42,8 +71,11 @@ void _Log::info(const std::string& topic, const std::string& message) { } std::ostream& _Log::info(const std::string& topic) { - _out << topic << std::setfill(' ') << std::setw(6) << "INFO" << ": "; - return _out; + if (this->_has_log_level(log_t::INFO)) { + _out << topic << std::setfill(' ') << std::setw(6) << "INFO" << ": "; + return _out; + } + return null_log; } void _Log::warn(const std::string& topic, const std::string& message) { @@ -51,8 +83,11 @@ void _Log::warn(const std::string& topic, const std::string& message) { } std::ostream& _Log::warn(const std::string& topic) { - _out << topic << std::setfill(' ') << std::setw(6) << "WARN" << ": "; - return _out; + if (this->_has_log_level(log_t::WARN)) { + _out << topic << std::setfill(' ') << std::setw(6) << "WARN" << ": "; + return _out; + } + return null_log; } void _Log::debug(const std::string& topic, const std::string& message) { @@ -60,17 +95,43 @@ void _Log::debug(const std::string& topic, const std::string& message) { } std::ostream& _Log::debug(const std::string& topic) { - _out << topic << std::setfill(' ') << std::setw(6) << "DEBUG" << ": "; - return _out; + if (this->_has_log_level(log_t::DEBUG)) { + _out << topic << std::setfill(' ') << std::setw(6) << "DEBUG" << ": "; + return _out; + } + return null_log; } void _Log::raw(const std::string& message) { this->raw() << message << std::endl; } + std::ostream& _Log::raw() { return _out; } +void _Log::set_level(log_t level) { + if (this->_inclusive_levels) { + this->_log_level.clear(); + this->_log_level.insert(level); + } else if (level == log_t::ALL) { + this->_log_level.insert(log_t::FERR); + this->_log_level.insert(log_t::ERR); + this->_log_level.insert(log_t::WARN); + this->_log_level.insert(log_t::INFO); + this->_log_level.insert(log_t::DEBUG); + } else { + this->_log_level.insert(level); + } +} +void _Log::clear_level(log_t level) { + if (level == log_t::ALL) { + this->_log_level.clear(); + } else { + if (this->_log_level.find(level) != this->_log_level.end()) + this->_log_level.erase(level); + } +} } // Slic3r diff --git a/xs/src/libslic3r/Log.hpp b/xs/src/libslic3r/Log.hpp index bf9ab5128..78e470b3c 100644 --- a/xs/src/libslic3r/Log.hpp +++ b/xs/src/libslic3r/Log.hpp @@ -7,10 +7,20 @@ #include #include #include +#include #include // good until c++17 + namespace Slic3r { +/// All available logging levels. +enum class log_t : uint8_t { FERR = 0, ERR = 4, WARN = 8, INFO = 16, DEBUG = 32, ALL = 255 }; + +inline bool operator>(const log_t lhs, const log_t rhs) { return static_cast(lhs) > static_cast(rhs); } +inline bool operator<(const log_t lhs, const log_t rhs) { return static_cast(lhs) < static_cast(rhs); } +inline bool operator>=(const log_t lhs, const log_t rhs) { return static_cast(lhs) > static_cast(rhs) || lhs == rhs; } +inline bool operator<=(const log_t lhs, const log_t rhs) { return static_cast(lhs) < static_cast(rhs) || lhs == rhs; } + /// Singleton instance implementing logging functionality in Slic3r /// Basic functionality is stubbed in currently, may pass through to Boost::Log /// eventually. @@ -45,6 +55,15 @@ public: void raw(const std::string& message); std::ostream& raw(); + template + void debug_svg(const std::string& topic, const T& path, bool append = true); + template + void debug_svg(const std::string& topic, const T* path, bool append = true); + + void set_level(log_t level); + void clear_level(log_t level); + void set_inclusive(bool v) { this->_inclusive_levels = v; } + // _Log(_Log const&) = delete; // void operator=(_Log const&) = delete; private: @@ -53,9 +72,13 @@ private: _Log(); _Log(std::ostream& out); _Log(std::wostream& out); + bool _inclusive_levels { true }; + std::set _log_level { }; std::wstring_convert> converter; + bool _has_log_level(log_t lvl); + }; /// Global log reference; initialized in Log.cpp