-
Cyril Moineau authoredCyril Moineau authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Log.cpp 7.98 KiB
/********************************************************************************
* Copyright (c) 2023 CEA-List
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
#include "aidge/utils/Log.hpp"
#include <fmt/core.h>
#include <cstdlib> // std::getenv
#include <memory>
#include <string>
#include <vector>
namespace Aidge {
/**
* @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.
*
* WARNING: Do not use this variable directly, use getConsoleLevel() instead.
*/
Log::Level Log::mConsoleLevel = []() {
#ifndef 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 : defaultLevel;
}
return defaultLevel;
}();
/**
* @brief Initialize color setting from environment or default to enabled
*/
bool Log::mConsoleColor = []() {
const char* color = std::getenv("AIDGE_LOG_COLOR");
return !color || (std::string(color) != "off" &&
std::string(color) != "OFF" &&
std::string(color) != "0");
}();
/**
* @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 = []() {
#ifndef NDEBUG
constexpr Log::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 : defaultLevel;
}
return defaultLevel;
}();
/**
* @brief Initialize file path from environment
*/
std::string Log::mFileName = []() {
const char* file = std::getenv("AIDGE_LOG_FILE");
return file ? std::string(file) : std::string();
}();
std::unique_ptr<FILE, Log::fcloseDeleter> Log::mFile{nullptr};
std::vector<std::string> Log::mContext;
int Log::mFloatingPointPrecision = 5;
/**
* @brief Internal logging implementation
* @param level Severity level of the message
* @param msg The message to log
*/
void Log::log(Level level, const std::string& msg) {
/**
* @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));
start = ++lineEnd;
}
return wrappedLines;
};
// Get the terminal color for the log level
const auto getColor = [](Level lvl) {
switch (lvl) {
case Debug: return fmt::terminal_color::green;
case Info: return fmt::terminal_color::blue;
case Notice: return fmt::terminal_color::bright_blue;
case Warn: return fmt::terminal_color::yellow;
case Error: return fmt::terminal_color::red;
case Fatal: return fmt::terminal_color::bright_magenta;
default: return fmt::terminal_color::white;
}
};
// 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 >= getConsoleLevel()) {
for (const auto& context : mContext) {
fmt::println("Context: {}", context);
}
// 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]);
}
}
}
}
if (level >= mFileLevel && !mFileName.empty()) {
if (!mFile) {
initFile(mFileName);
}
for (const auto& context : mContext) {
fmt::println(mFile.get(), "Context: {}", context);
}
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
* @throw std::runtime_error if file cannot be opened
*/
void Log::initFile(const std::string& fileName) {
if (FILE* file = std::fopen(fileName.c_str(), "a")) {
mFile.reset(file);
} else {
mFileName.clear();
throw std::runtime_error(
fmt::format("Could not create log file: {}", fileName));
}
}
void Log::setFileName(const std::string& fileName) {
if (fileName != mFileName) {
mFileName = fileName;
mFile.reset();
if (!fileName.empty()) {
initFile(fileName);
}
}
}
} // namespace Aidge