From 52f46b5c51ba08dbd97641fc4e8952f2bdb3498f Mon Sep 17 00:00:00 2001 From: Nomango Date: Fri, 17 Jul 2020 16:02:52 +0800 Subject: [PATCH] add colored console log --- src/kiwano/utils/Logger.cpp | 475 +++++++++++++++++++++++++++--------- src/kiwano/utils/Logger.h | 25 +- 2 files changed, 372 insertions(+), 128 deletions(-) diff --git a/src/kiwano/utils/Logger.cpp b/src/kiwano/utils/Logger.cpp index fd14a1d4..45a383df 100644 --- a/src/kiwano/utils/Logger.cpp +++ b/src/kiwano/utils/Logger.cpp @@ -27,6 +27,9 @@ namespace kiwano { +// +// LogFormater +// String LogFormater::GetLevelLabel(LogLevel level) const { switch (level) @@ -68,8 +71,107 @@ public: }; +// +// LogProvider +// LogProvider::~LogProvider() {} +#if defined(KGE_PLATFORM_WINDOWS) +void SetWindowConsoleColor(std::ostream& os, int foreground, int background) +{ + static WORD defaultAttributes = 0; + + // get terminal handle + HANDLE hTerminal = INVALID_HANDLE_VALUE; + if (&os == &std::cout) + hTerminal = GetStdHandle(STD_OUTPUT_HANDLE); + else if (&os == &std::cerr) + hTerminal = GetStdHandle(STD_ERROR_HANDLE); + + if (hTerminal == INVALID_HANDLE_VALUE) + return; + + // save default terminal attributes if it unsaved + if (!defaultAttributes) + { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(hTerminal, &info)) + return; + defaultAttributes = info.wAttributes; + } + + // restore all default settings + if (foreground == -1 && background == -1) + { + SetConsoleTextAttribute(hTerminal, defaultAttributes); + return; + } + + // get current settings + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(hTerminal, &info)) + return; + + if (foreground != -1) + { + info.wAttributes &= ~(info.wAttributes & 0x0F); + info.wAttributes |= static_cast(foreground); + } + + if (background != -1) + { + info.wAttributes &= ~(info.wAttributes & 0xF0); + info.wAttributes |= static_cast(background); + } + + SetConsoleTextAttribute(hTerminal, info.wAttributes); +} +#endif + +template +std::ostream& ConsoleColorBrush(std::ostream& os) +{ +#if defined(KGE_PLATFORM_WINDOWS) + if (color > 0) + { + switch (color) + { + case 31: // red + SetWindowConsoleColor(os, FOREGROUND_RED | FOREGROUND_INTENSITY, -1); + break; + case 32: // green + SetWindowConsoleColor(os, FOREGROUND_GREEN | FOREGROUND_INTENSITY, -1); + break; + case 33: // yellow + SetWindowConsoleColor(os, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, -1); + break; + case 34: // blue + SetWindowConsoleColor(os, FOREGROUND_BLUE | FOREGROUND_INTENSITY, -1); + break; + case 36: // cyan + SetWindowConsoleColor(os, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY, -1); + break; + default: + break; + } + } + else if (color < 0) + { + SetWindowConsoleColor(os, -1, -1); + } +#else + if (color > 0) + { + os << "\033[1;" << color << "m"; + } + else if (color < 0) + { + os << "\033[0m"; + } +#endif + return os; +} + LogProviderPtr ConsoleLogProvider::Create() { LogProviderPtr ptr = new ConsoleLogProvider; @@ -88,9 +190,9 @@ void ConsoleLogProvider::Init() void ConsoleLogProvider::WriteMessage(LogLevel level, LogBuffer* msg) { if (level != LogLevel::Error) - std::cout << msg << std::flush; + std::cout << GetColor(level) << msg << std::flush << ConsoleColorBrush<-1>; else - std::cerr << msg; + std::cerr << GetColor(level) << msg << ConsoleColorBrush<-1>; #if defined(KGE_PLATFORM_WINDOWS) ::OutputDebugStringA(msg->GetRaw()); @@ -100,6 +202,22 @@ void ConsoleLogProvider::WriteMessage(LogLevel level, LogBuffer* msg) void ConsoleLogProvider::Flush() { std::cout.flush(); + std::cout.clear(); +} + +ConsoleLogProvider::ConsoleColor ConsoleLogProvider::GetColor(LogLevel level) +{ + std::initializer_list colors = { + ConsoleColorBrush<34>, // Debug Blue + ConsoleColorBrush<0>, // Info Default + ConsoleColorBrush<32>, // Notice Green + ConsoleColorBrush<33>, // Warn Yellow + ConsoleColorBrush<31>, // Error Red + }; + + if (size_t(level) < colors.size()) + return *std::next(colors.begin(), ptrdiff_t(level)); + return ConsoleColorBrush<0>; } LogProviderPtr FileLogProvider::Create(const String& filepath, std::ios_base::openmode mode) @@ -123,22 +241,242 @@ void FileLogProvider::Init() {} void FileLogProvider::WriteMessage(LogLevel level, LogBuffer* msg) { if (ofs_) + { ofs_ << msg << std::flush; + } } void FileLogProvider::Flush() { if (ofs_) + { ofs_.flush(); + ofs_.clear(); + } } +// +// LogBuffer +// +LogBuffer::LogBuffer(size_t buffer_size) + : buf_(buffer_size) + , seek_high_(nullptr) +{ + Reset(); +} + +void LogBuffer::Resize(size_t size) +{ + if (buf_.size() < size) + { + buf_.resize(size); + } +} + +void LogBuffer::Reset() +{ + const auto begin = buf_.data(); + const auto size = buf_.size(); + this->setp(begin, begin + size); + this->setg(begin, begin, begin); + seek_high_ = nullptr; +} + +const char* LogBuffer::GetRaw() const +{ + const auto pptr = this->pptr(); + if (!pptr) + return ""; + + const auto data = buf_.data(); + const auto size = buf_.size(); + if (pptr == data) + return ""; + + if (pptr > data && pptr < data + size) + { + *pptr = '\0'; + return data; + } + return ""; +} + +LogBuffer::int_type LogBuffer::overflow(int_type ch) +{ + if (traits_type::eq_int_type(ch, traits_type::eof())) + return traits_type::not_eof(ch); // EOF, return success + + const auto pptr = this->pptr(); + if (pptr) + return traits_type::eof(); + + const auto epptr = this->epptr(); + if (pptr < epptr) + { + seek_high_ = pptr + 1; + return ch; + } + + const auto old_ptr = buf_.data(); + const auto old_size = pptr - old_ptr; + + size_t new_size = 0; + if (old_size < INT_MAX / 2) + new_size = old_size << 1; + else if (old_size < INT_MAX) + new_size = INT_MAX; + else + return traits_type::eof(); // buffer can't grow, fail + + // grow + buf_.resize(new_size); + + const auto new_ptr = buf_.data(); + const auto new_pnext = new_ptr + old_size; + + seek_high_ = new_pnext + 1; + + this->setp(new_ptr, new_pnext, new_ptr + new_size); + this->setg(new_ptr, new_ptr + (this->gptr() - old_ptr), seek_high_); + return ch; +} + +LogBuffer::int_type LogBuffer::underflow() +{ + const auto gptr = this->gptr(); + if (!gptr) + return traits_type::eof(); + + if (gptr < this->egptr()) + return traits_type::to_int_type(*gptr); + + const auto pptr = this->pptr(); + if (!pptr) + return traits_type::eof(); + + const auto high = std::max(seek_high_, pptr); + if (high <= gptr) + return traits_type::eof(); + + seek_high_ = high; + this->setg(this->eback(), gptr, high); + return traits_type::to_int_type(*gptr); +} + +LogBuffer::pos_type LogBuffer::seekpos(pos_type pos, std::ios_base::openmode mode) +{ + const auto offset = static_cast(pos); + const auto old_gptr = this->gptr(); + const auto olg_pptr = this->pptr(); + if (olg_pptr && seek_high_ < olg_pptr) + { + seek_high_ = olg_pptr; + } + + const auto seek_low = this->eback(); + const auto seek_dist = seek_high_ - seek_low; + if (static_cast(offset) > static_cast(seek_dist)) + { + return pos_type(off_type(-1)); + } + + if (offset != 0 && (((mode & std::ios_base::in) && !old_gptr) || ((mode & std::ios_base::out) && !olg_pptr))) + { + return pos_type(off_type(-1)); + } + + const auto new_ptr = seek_low + offset; + if ((mode & std::ios_base::in) && old_gptr) + { + this->setg(seek_low, new_ptr, seek_high_); + } + + if ((mode & std::ios_base::out) && olg_pptr) + { + this->setp(seek_low, new_ptr, this->epptr()); + } + return pos_type(offset); +} + +LogBuffer::pos_type LogBuffer::seekoff(off_type offset, std::ios_base::seekdir way, std::ios_base::openmode mode) +{ + const auto old_gptr = this->gptr(); + const auto old_pptr = this->pptr(); + if (old_pptr && seek_high_ < old_pptr) + { + seek_high_ = old_pptr; + } + + const auto seek_low = this->eback(); + const auto seek_dist = seek_high_ - seek_low; + off_type new_offset; + switch (way) + { + case std::ios_base::beg: + new_offset = 0; + break; + case std::ios_base::end: + new_offset = seek_dist; + break; + case std::ios_base::cur: + { + constexpr auto both = std::ios_base::in | std::ios_base::out; + if ((mode & both) != both) + { + if (mode & std::ios_base::in) + { + if (old_gptr || !seek_low) + { + new_offset = old_gptr - seek_low; + break; + } + } + else if ((mode & std::ios_base::out) && (old_pptr || !seek_low)) + { + new_offset = old_pptr - seek_low; + break; + } + } + } + + // fallthrough + default: + return pos_type(off_type(-1)); + } + + if (std::streamsize(offset) + new_offset > std::streamsize(seek_dist)) + { + return pos_type(off_type(-1)); + } + + offset += new_offset; + if (offset != 0 && (((mode & std::ios_base::in) && !old_gptr) || ((mode & std::ios_base::out) && !old_pptr))) + { + return pos_type(off_type(-1)); + } + + const auto new_ptr = seek_low + offset; + if ((mode & std::ios_base::in) && old_gptr) + { + this->setg(seek_low, new_ptr, seek_high_); + } + + if ((mode & std::ios_base::out) && old_pptr) + { + this->setp(seek_low, new_ptr, this->epptr()); + } + return pos_type(offset); +} + +// +// Logger +// Logger::Logger() : enabled_(true) , level_(LogLevel::Debug) + , buffer_(1024) { - ResizeBuffer(1024); - LogFormaterPtr formater = new TextFormater; SetFormater(formater); @@ -203,16 +541,18 @@ LogFormaterPtr Logger::GetFormater() void Logger::ResizeBuffer(size_t buffer_size) { - buffer_.resize(buffer_size); + buffer_.Resize(buffer_size); } void Logger::Write(LogLevel level, std::streambuf* raw_msg) { std::lock_guard lock(mutex_); - LogBuffer buf(buffer_); - std::iostream stream(&buf); + // reset buffer + buffer_.Reset(); + // format message + std::iostream stream(&buffer_); if (formater_) { formater_->Format(stream, level, Time::Now(), raw_msg); @@ -222,10 +562,11 @@ void Logger::Write(LogLevel level, std::streambuf* raw_msg) stream << raw_msg << "\n"; } + // write message for (auto provider : providers_) { - buf.pubseekpos(0, std::ios_base::out); - provider->WriteMessage(level, &buf); + buffer_.pubseekpos(0, std::ios_base::in); + provider->WriteMessage(level, &buffer_); } } @@ -333,8 +674,11 @@ HWND GetAllocatedConsole() } } // namespace +#endif + void Logger::ShowConsole(bool show) { +#if defined(KGE_PLATFORM_WINDOWS) HWND current_console = ::GetConsoleWindow(); if (show) { @@ -372,119 +716,10 @@ void Logger::ShowConsole(bool show) } } } -} - +#else + // NOT IMPLEMENT #endif - -LogBuffer::LogBuffer(Vector& buf) - : buf_(buf) - , seek_high_(nullptr) -{ - this->setbuf(buf.data(), buf.size()); } -const char* LogBuffer::GetRaw() const -{ - const auto pptr = this->pptr(); - if (!pptr) - return ""; - - const auto data = buf_.data(); - const auto size = buf_.size(); - if (pptr == data) - return ""; - - if (pptr > data && pptr < data + size) - { - *pptr = '\0'; - return data; - } - return ""; -} - -LogBuffer::int_type LogBuffer::overflow(int_type ch) -{ - if (traits_type::eq_int_type(ch, traits_type::eof())) - return traits_type::not_eof(ch); // EOF, return success - - const auto pptr = this->pptr(); - if (pptr) - return traits_type::eof(); - - const auto epptr = this->epptr(); - if (pptr < epptr) - { - seek_high_ = pptr + 1; - return ch; - } - - const auto old_ptr = buf_.data(); - const auto old_size = pptr - old_ptr; - - size_t new_size = 0; - if (old_size < INT_MAX / 2) - new_size = old_size << 1; - else if (old_size < INT_MAX) - new_size = INT_MAX; - else - return traits_type::eof(); // buffer can't grow, fail - - // grow - buf_.resize(new_size); - - const auto new_ptr = buf_.data(); - const auto new_pnext = new_ptr + old_size; - - seek_high_ = new_pnext + 1; - - this->setp(new_ptr, new_pnext, new_ptr + new_size); - this->setg(new_ptr, new_ptr + (this->gptr() - old_ptr), seek_high_); - return ch; -} - -LogBuffer::int_type LogBuffer::underflow() -{ - const auto gptr = this->gptr(); - if (!gptr) - return traits_type::eof(); - - if (gptr < this->egptr()) - return traits_type::to_int_type(*gptr); - - const auto pptr = this->pptr(); - if (!pptr) - return traits_type::eof(); - - const auto high = std::max(seek_high_, pptr); - if (high <= gptr) - return traits_type::eof(); - - seek_high_ = high; - this->setg(this->eback(), gptr, high); - return traits_type::to_int_type(*gptr); -} - -LogBuffer::pos_type LogBuffer::seekpos(pos_type sp, std::ios_base::openmode which) -{ - return this->seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which); -} - -LogBuffer::pos_type LogBuffer::seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) -{ - if (dir == std::ios_base::cur) - gbump(int(off)); - else if (dir == std::ios_base::end) - setg(eback(), egptr() + off, egptr()); - else if (dir == std::ios_base::beg) - setg(eback(), eback() + off, egptr()); - return gptr() - eback(); -} - -std::streambuf* LogBuffer::setbuf(char_type* s, std::streamsize n) -{ - this->setp(s, s + n); - this->setg(s, s, s); - return this; -} } // namespace kiwano diff --git a/src/kiwano/utils/Logger.h b/src/kiwano/utils/Logger.h index 3d600028..91a1776e 100644 --- a/src/kiwano/utils/Logger.h +++ b/src/kiwano/utils/Logger.h @@ -113,10 +113,18 @@ public: class LogBuffer : public std::streambuf { public: - LogBuffer(Vector& buf); + LogBuffer(size_t buffer_size); + + void Resize(size_t size); + + void Reset(); const char* GetRaw() const; + LogBuffer(const LogBuffer&) = delete; + + LogBuffer& operator=(const LogBuffer&) = delete; + protected: int_type overflow(int_type ch) override; @@ -127,11 +135,9 @@ protected: pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override; - std::streambuf* setbuf(char_type* s, std::streamsize n) override; - private: - Vector& buf_; - char_type* seek_high_; + Vector buf_; + char_type* seek_high_; }; /** @@ -166,6 +172,11 @@ public: void WriteMessage(LogLevel level, LogBuffer* msg) override; void Flush() override; + +private: + typedef std::ostream&(*ConsoleColor)(std::ostream&); + + ConsoleColor GetColor(LogLevel level); }; /** @@ -253,11 +264,9 @@ public: /// @param raw_msg 日志内容 void Write(LogLevel level, std::streambuf* raw_msg); -#if defined(KGE_PLATFORM_WINDOWS) /// \~chinese /// @brief 显示或关闭控制台 void ShowConsole(bool show); -#endif virtual ~Logger(); @@ -268,8 +277,8 @@ private: bool enabled_; LogLevel level_; LogFormaterPtr formater_; + LogBuffer buffer_; Vector providers_; - Vector buffer_; std::mutex mutex_; };