diff --git a/src/utils/Log.cpp b/src/utils/Log.cpp index fb567e355fef28391c894be9ca8ca01e56f36418..b2aad14c9ed9f21b9f262d72939abbed0488d830 100644 --- a/src/utils/Log.cpp +++ b/src/utils/Log.cpp @@ -21,18 +21,25 @@ namespace Aidge { /** - * @brief Initialize console level from environment or default to Info + * @brief Initialize console log level from environment. If compile mode is + * DEBUG, then the default level is Log::Level::Debug, else it is + * Log::Level::Notice. */ Log::Level Log::mConsoleLevel = []() { +#ifdef NDEBUG + constexpr Level defaultLevel = Level::Debug; +#else + constexpr Log::Level defaultLevel = Level::Notice; +#endif if (const char* level = std::getenv("AIDGE_LOGLEVEL_CONSOLE")) { return level[0] == 'D' ? Debug : level[0] == 'I' ? Info : level[0] == 'N' ? Notice : level[0] == 'W' ? Warn : level[0] == 'E' ? Error : - level[0] == 'F' ? Fatal : Info; + level[0] == 'F' ? Fatal : defaultLevel; } - return Info; + return defaultLevel; }(); /** @@ -46,18 +53,24 @@ bool Log::mConsoleColor = []() { }(); /** - * @brief Initialize file level from environment or default to Debug + * @brief Initialize file log level from environment. If compile mode is DEBUG, + * then the default level is Log::Level::Debug, else it is Log::Level::Notice. */ Log::Level Log::mFileLevel = []() { +#ifdef NDEBUG + constexpr Level defaultLevel = Level::Debug; +#else + constexpr Log::Level defaultLevel = Level::Notice; +#endif if (const char* level = std::getenv("AIDGE_LOGLEVEL_FILE")) { return level[0] == 'D' ? Debug : level[0] == 'I' ? Info : level[0] == 'N' ? Notice : level[0] == 'W' ? Warn : level[0] == 'E' ? Error : - level[0] == 'F' ? Fatal : Debug; + level[0] == 'F' ? Fatal : defaultLevel; } - return Debug; + return defaultLevel; }(); /** @@ -79,8 +92,65 @@ int Log::mFloatingPointPrecision = 5; */ void Log::log(Level level, const std::string& msg) { /** - * @brief Get the terminal color for a log level + * @brief Helper function to wrap text to a specified width, respecting + * explicit line breaks (\n) and accounting for ANSI escape sequences. */ + const auto wrapText = [](const std::string& text, const std::size_t width) -> std::vector<std::string> { + std::vector<std::string> wrappedLines; + std::size_t start = 0; + + while (start < text.size()) { + std::size_t lineWidth = 0; + std::size_t current = start; + + while (current < text.size() && lineWidth < width) { + if (text[current] == '\033') { + // Found ANSI escape sequence, skip until 'm' + std::size_t ansiEnd = text.find('m', current); + if (ansiEnd != std::string::npos) { + // Add the length of the ANSI sequence to the width allowance + // width += (ansiEnd - current + 1); + current = ansiEnd + 1; + } else { + // Malformed sequence, treat as normal characters + ++current; + } + } else if (text[current] == '\n') { + // Handle explicit line break + break; + } else { + // Normal character, increase line width + ++lineWidth; + ++current; + } + } + + // Find the end of the current line + std::size_t lineEnd = current; + + // Adjust for spaces if needed + if (lineEnd < text.size() && text[lineEnd] != '\n') { + std::size_t lastSpace = text.rfind(' ', lineEnd); + if (lastSpace != std::string::npos && lastSpace > start) { + lineEnd = lastSpace; + } + } + + // Add the wrapped line to the result + wrappedLines.push_back(text.substr(start, lineEnd - start)); + + // Move to the next segment, skipping spaces + start = lineEnd; + while (start < text.size() && (text[start] == ' ' || text[start] == '\n')) { + ++start; + } + } + + return wrappedLines; + }; + + + // Get the terminal color for the log level const auto getColor = [](Level lvl) { switch (lvl) { case Debug: return fmt::terminal_color::green; @@ -93,24 +163,36 @@ void Log::log(Level level, const std::string& msg) { } }; - /** - * @brief Get the string representation of a log level - */ + // Get the string representation of the log level const auto levelStr = EnumStrings<Level>::data[static_cast<std::size_t>(level)]; + const std::size_t levelIndentSizes[6] = {10, 9, 11, 12, 10, 10}; + const std::size_t width = 80 - levelIndentSizes[static_cast<std::size_t>(level)]; if (level >= mConsoleLevel) { - // Print active contexts for (const auto& context : mContext) { fmt::println("Context: {}", context); } - // Print message with colored level - if (mConsoleColor) { - fmt::print("["); - fmt::print(fg(getColor(level)), "{}", levelStr); - fmt::print("] - {}\n", msg); - } else { - fmt::print("[{}] - {}\n", levelStr, msg); + // Wrap the message and print each line + auto wrappedLines = wrapText(msg, width); + for (std::size_t i = 0; i < wrappedLines.size(); ++i) { + if (mConsoleColor) { + if (i == 0) { + fmt::print("["); + fmt::print(fg(getColor(level)), "{}", levelStr); + fmt::print("] - {}\n", wrappedLines[i]); + } else { + fmt::print("["); + fmt::print(fg(getColor(level)), "{}", levelStr); + fmt::print("] {}\n", wrappedLines[i]); + } + } else { + if (i == 0) { + fmt::print("[{}] - {}\n", levelStr, wrappedLines[i]); + } else { + fmt::print("[{}] {}\n", levelStr, wrappedLines[i]); + } + } } } @@ -118,14 +200,24 @@ void Log::log(Level level, const std::string& msg) { if (!mFile) { initFile(mFileName); } - // Write contexts and message to file + for (const auto& context : mContext) { fmt::println(mFile.get(), "Context: {}", context); } - fmt::println(mFile.get(), "{}: {}", levelStr, msg); + + auto wrappedLines = wrapText(msg, width); + for (std::size_t i = 0; i < wrappedLines.size(); ++i) { + if (i == 0) { + fmt::println(mFile.get(), "{}: {}", levelStr, wrappedLines[i]); + } else { + fmt::println(mFile.get(), "{} {}", levelStr, wrappedLines[i]); + } + } } } + + /** * @brief Initialize or re-initialize the log file * @param fileName Path to the log file