diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 67e7ecaaf3..f828bfae62 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -221,51 +221,40 @@ namespace Debug }; #else - class Tee : public DebugOutputBase + namespace { - public: - Tee(std::ostream& stream, std::ostream& stream2) - : out(stream) - , out2(stream2) + struct Record { - // TODO: check which stream is stderr? - mUseColor = useColoredOutput(); - - mColors[Error] = Red; - mColors[Warning] = Yellow; - mColors[Info] = Reset; - mColors[Verbose] = DarkGray; - mColors[Debug] = DarkGray; - mColors[NoLevel] = Reset; - } + std::string mValue; + Level mLevel; + }; - std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) override - { - out.write(str, size); - out.flush(); + std::vector globalBuffer; - if (mUseColor) - { - out2 << "\033[0;" << mColors[debugLevel] << "m"; - out2.write(str, size); - out2 << "\033[0;" << Reset << "m"; - } - else + Color getColor(Level level) + { + switch (level) { - out2.write(str, size); + case Error: + return Red; + case Warning: + return Yellow; + case Info: + return Reset; + case Verbose: + return DarkGray; + case Debug: + return DarkGray; + case NoLevel: + return Reset; } - out2.flush(); - - return size; + return Reset; } - virtual ~Tee() = default; - - private: - static bool useColoredOutput() + bool useColoredOutput() { #if defined(_WIN32) - if (getenv("NO_COLOR")) + if (std::getenv("NO_COLOR") != nullptr) return false; DWORD mode; @@ -273,22 +262,94 @@ namespace Debug return true; // some console emulators may not use the Win32 API, so try the Unixy approach - char* term = getenv("TERM"); - return term && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; + return std::getenv("TERM") != nullptr && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; #else - char* term = getenv("TERM"); - bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); - - return useColor; + return std::getenv("TERM") != nullptr && std::getenv("NO_COLOR") == nullptr && isatty(fileno(stderr)); #endif } - std::ostream& out; - std::ostream& out2; - bool mUseColor; + class Identity + { + public: + explicit Identity(std::ostream& stream) + : mStream(stream) + { + } - std::map mColors; - }; + void write(const char* str, std::streamsize size, Level /*level*/) + { + mStream.write(str, size); + mStream.flush(); + } + + private: + std::ostream& mStream; + }; + + class Coloured + { + public: + explicit Coloured(std::ostream& stream) + : mStream(stream) + // TODO: check which stream is stderr? + , mUseColor(useColoredOutput()) + { + } + + void write(const char* str, std::streamsize size, Level level) + { + if (mUseColor) + mStream << "\033[0;" << getColor(level) << 'm'; + mStream.write(str, size); + if (mUseColor) + mStream << "\033[0;" << Reset << 'm'; + mStream.flush(); + } + + private: + std::ostream& mStream; + bool mUseColor; + }; + + class Buffer + { + public: + explicit Buffer(std::vector& buffer) + : mBuffer(buffer) + { + } + + void write(const char* str, std::streamsize size, Level debugLevel) + { + mBuffer.push_back(Record{ std::string(str, size), debugLevel }); + } + + private: + std::vector& mBuffer; + }; + + template + class Tee : public DebugOutputBase + { + public: + explicit Tee(First first, Second second) + : mFirst(first) + , mSecond(second) + { + } + + std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) override + { + mFirst.write(str, size, debugLevel); + mSecond.write(str, size, debugLevel); + return size; + } + + private: + First mFirst; + Second mSecond; + }; + } #endif } @@ -301,8 +362,10 @@ static std::ofstream logfile; #if defined(_WIN32) && defined(_DEBUG) static boost::iostreams::stream_buffer sb; #else -static boost::iostreams::stream_buffer coutsb; -static boost::iostreams::stream_buffer cerrsb; +static boost::iostreams::stream_buffer> standardOut; +static boost::iostreams::stream_buffer> standardErr; +static boost::iostreams::stream_buffer> bufferedOut; +static boost::iostreams::stream_buffer> bufferedErr; #endif std::ostream& getRawStdout() @@ -323,20 +386,22 @@ Misc::Locked getLockedRawStderr() // Redirect cout and cerr to the log file void setupLogging(const std::filesystem::path& logDir, std::string_view appName, std::ios_base::openmode mode) { -#if defined(_WIN32) && defined(_DEBUG) - // Redirect cout and cerr to VS debug output when running in debug mode - sb.open(Debug::DebugOutput()); - std::cout.rdbuf(&sb); - std::cerr.rdbuf(&sb); -#else +#if !(defined(_WIN32) && defined(_DEBUG)) const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; logfile.open(logDir / logName, mode); - coutsb.open(Debug::Tee(logfile, *rawStdout)); - cerrsb.open(Debug::Tee(logfile, *rawStderr)); + Debug::Identity log(logfile); + + for (const Debug::Record& v : Debug::globalBuffer) + log.write(v.mValue.data(), v.mValue.size(), v.mLevel); - std::cout.rdbuf(&coutsb); - std::cerr.rdbuf(&cerrsb); + Debug::globalBuffer.clear(); + + standardOut.open(Debug::Tee(log, Debug::Coloured(*rawStdout))); + standardErr.open(Debug::Tee(log, Debug::Coloured(*rawStderr))); + + std::cout.rdbuf(&standardOut); + std::cerr.rdbuf(&standardErr); #endif #ifdef _WIN32 @@ -356,6 +421,19 @@ int wrapApplication(int (*innerApplication)(int argc, char* argv[]), int argc, c rawStderr = std::make_unique(std::cerr.rdbuf()); rawStderrMutex = std::make_unique(); +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode + sb.open(Debug::DebugOutput()); + std::cout.rdbuf(&sb); + std::cerr.rdbuf(&sb); +#else + bufferedOut.open(Debug::Tee(Debug::Buffer(Debug::globalBuffer), Debug::Coloured(*rawStdout))); + bufferedErr.open(Debug::Tee(Debug::Buffer(Debug::globalBuffer), Debug::Coloured(*rawStderr))); + + std::cout.rdbuf(&bufferedOut); + std::cerr.rdbuf(&bufferedErr); +#endif + int ret = 0; try {