* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
* \file logging.h
* \brief Logging framework with support for console, file, and in-memory logging
*
* This header provides a flexible logging system with:
* - Multiple log levels (DEBUG, INFO, WARN, ERROR, FATAL, EVENT)
* - Colored terminal output using ANSI escape codes
* - File-based logging with append/overwrite modes
* - In-memory line-based logging for programmatic access
* - Thread-safe logging operations
* - Stream-style and printf-style logging interfaces
*/
#pragma once
#include <chrono>
#include <cstdarg>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "core/error.h"
#include "securec.h"
namespace pypto {
* \brief Enumeration of available log levels
*
* Log levels in ascending order of severity:
* - DEBUG: Detailed information for debugging
* - INFO: General informational messages
* - WARN: Warning messages for potentially harmful situations
* - ERROR: Error messages for failures
* - FATAL: Critical errors that may cause termination
* - EVENT: Special events and milestones
* - NONE: Disable all logging
*/
enum class LogLevel : uint8_t {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
FATAL = 4,
EVENT = 5,
NONE = 6,
};
* \brief Standard error logger that writes to stderr
*
* Supports colored output using TTY commands.
*/
class StdLogger {
public:
* \brief Log a value of any type
* \tparam T Type of value to log
* \param t Value to log
* \return Reference to this logger for chaining
*/
template <typename T>
StdLogger& Log(T&& t)
{
std::cerr << (std::forward<T>(t));
return *this;
}
StdLogger() = default;
StdLogger(const StdLogger&) = delete;
StdLogger& operator=(const StdLogger&) = delete;
};
* \brief File-based logger that writes to a file stream
*
* Supports both append and overwrite modes. TTY commands are ignored
* since file output typically doesn't support colored text.
*/
class FileLogger {
public:
std::ofstream ofs;
* \brief Construct a file logger
* \param filepath Path to the log file
* \param append If true, append to existing file; otherwise overwrite
*/
FileLogger(const std::string& filepath, bool append)
{
if (append) {
ofs.open(filepath, std::ios_base::app);
} else {
ofs.open(filepath);
}
}
* \brief Log a value to the file
* \tparam T Type of value to log
* \param t Value to log
* \return Reference to this logger for chaining
*/
template <typename T>
FileLogger& Log(T&& t)
{
ofs << (std::forward<T>(t));
return *this;
}
FileLogger(const FileLogger&) = delete;
FileLogger& operator=(const FileLogger&) = delete;
};
* \brief In-memory logger that stores log lines as strings
*
* Useful for programmatic access to log messages or testing.
* Inherits from std::vector<std::string> for direct access to stored lines.
*/
class LineLogger : public std::vector<std::string> {
public:
* \brief Log a string value
* \param t String to store
* \return Reference to this logger for chaining
*/
LineLogger& Log(std::string&& t)
{
this->emplace_back(t);
return *this;
}
};
* \brief Central manager for all loggers
*
* This singleton class manages:
* - Global log level threshold
* - Standard output logger enable/disable
* - Multiple file loggers
* - Multiple line (in-memory) loggers
*
* All logging operations are thread-safe.
*/
class LoggerManager {
public:
std::mutex logMtx;
#ifdef NDEBUG
LogLevel level{LogLevel::ERROR};
#else
LogLevel level{LogLevel::DEBUG};
#endif
bool stdEnabled{true};
StdLogger stdLogger;
std::unordered_map<std::string, std::unique_ptr<FileLogger>> fileLoggerDict;
std::unordered_map<std::string, std::shared_ptr<LineLogger>> lineLoggerDict;
LoggerManager() = default;
* \brief Log a message to all active loggers
* \tparam T Type of the log message
* \param l Log level
* \param t Plain message (for file/line loggers)
* \param tRich Rich message with formatting (for std logger)
*/
template <typename T>
void Log(LogLevel l, T&& t, T&& tRich)
{
std::scoped_lock lock(logMtx);
if (l >= level) {
if (stdEnabled) {
stdLogger.Log(std::forward<T>(tRich));
}
}
for (auto& [filepath, logger] : fileLoggerDict) {
(void)filepath;
logger->Log(std::forward<T>(t));
}
for (auto& [name, logger] : lineLoggerDict) {
(void)name;
logger->Log(std::forward<T>(t));
}
}
* \brief Set the global log level threshold
* \param l New log level
*/
static void SetLevel(LogLevel l) { GetManager().level = l; }
* \brief Get the current global log level threshold
* \return Current log level
*/
static LogLevel GetLevel() { return GetManager().level; }
* \brief Enable or disable standard output logging
* \param enabled True to enable, false to disable
*/
static void StdLoggerEnable(bool enabled) { GetManager().stdEnabled = enabled; }
* \brief Register a file logger
* \param filepath Path to the log file
* \param append If true, append to existing file; otherwise overwrite
*/
static void FileLoggerRegister(const std::string& filepath, bool append)
{
GetManager().fileLoggerDict.try_emplace(filepath, std::make_unique<FileLogger>(filepath, append));
}
* \brief Unregister and close a file logger
* \param filepath Path to the log file to unregister
*/
static void FileLoggerUnregister(const std::string& filepath) { GetManager().fileLoggerDict.erase(filepath); }
* \brief Replace one file logger with another
* \param oldFilepath Path to the old log file
* \param newFilepath Path to the new log file
* \param append If true, append to new file; otherwise overwrite
*/
static void FileLoggerReplace(const std::string& oldFilepath, const std::string& newFilepath, bool append)
{
FileLoggerUnregister(oldFilepath);
FileLoggerRegister(newFilepath, append);
}
* \brief Register an in-memory line logger
* \param name Name identifier for the logger
* \return Shared pointer to the line logger for access to stored lines
*/
static std::shared_ptr<LineLogger> LineLoggerRegister(const std::string& name)
{
auto& dict = GetManager().lineLoggerDict;
auto it = dict.find(name);
if (it != dict.end()) {
return it->second;
}
auto logger = std::make_shared<LineLogger>();
dict[name] = logger;
return logger;
}
* \brief Unregister an in-memory line logger
* \param name Name identifier of the logger to unregister
*/
static void LineLoggerUnregister(const std::string& name) { GetManager().lineLoggerDict.erase(name); }
friend class Logger;
* \brief Get the singleton LoggerManager instance
* \return Reference to the singleton LoggerManager
*/
static LoggerManager& GetManager()
{
static LoggerManager manager;
return manager;
}
};
constexpr uint32_t MAX_LOG_BUF_SIZE = 1024;
* \brief Main logger class for creating log messages
*
* This class handles:
* - Automatic timestamp generation
* - Log level prefixing
* - Message buffering
* - Stream-style output via operator<<
* - Variadic output via operator()
*
* Logger objects should be created per log message (they flush on destruction).
*/
class Logger {
private:
std::stringstream ss;
std::stringstream ssRich;
#ifdef NDEBUG
LogLevel level{LogLevel::ERROR};
#else
LogLevel level{LogLevel::DEBUG};
#endif
bool enableLog = false;
public:
* \brief Construct a logger for a single log message
* \param levelIn Log level for this message
* \param func Function name (currently unused but available for future use)
* \param line Line number (currently unused but available for future use)
*/
Logger(LogLevel levelIn, [[maybe_unused]] int line) : level(levelIn)
{
enableLog = LoggerManager::GetManager().level <= level;
if (enableLog) {
static const char* MSG = "DIWEFVN";
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm {};
(void)localtime_r(&time, &tm);
char timeBuf[128];
std::strftime(timeBuf, sizeof(timeBuf), "%F %T.", &tm);
Log(timeBuf);
char buf[MAX_LOG_BUF_SIZE];
auto epoch = now.time_since_epoch();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(epoch).count() % 1000;
sprintf_s(buf, sizeof(buf), "%03d %c | ", static_cast<int>(ms), MSG[static_cast<int>(level)]);
Log(buf);
}
}
* \brief Destructor flushes the log message to all active loggers
*/
~Logger()
{
if (enableLog) {
Log("\n");
LoggerManager::GetManager().Log(level, ss.str(), ssRich.str());
}
}
* \brief Log a value
* \tparam T Type of value to log
* \param val Value to log
* \return Reference to this logger for chaining
*/
template <typename T>
Logger& Log(T&& val)
{
ss << (std::forward<T>(val));
ssRich << (std::forward<T>(val));
return *this;
}
* \brief Stream operator for convenient logging
* \tparam T Type of value to log
* \param val Value to log
* \return Reference to this logger for chaining
*/
template <typename T>
Logger& operator<<(T&& val)
{
if (enableLog) {
return Log(std::forward<T>(val));
} else {
return *this;
}
}
* \brief Variadic operator for logging multiple values at once
* \tparam Tys Types of values to log
* \param vals Values to log
* \return Reference to this logger for chaining
*/
template <typename... Tys>
Logger& operator()(Tys&&... vals)
{
if (enableLog) {
if constexpr (sizeof...(Tys) > 0) {
(Log(std::forward<Tys>(vals)), ...);
}
}
return *this;
}
};
#define IR_LOGI() pypto::Logger(pypto::LogLevel::INFO, __LINE__)
#define IR_LOGW() pypto::Logger(pypto::LogLevel::WARN, __LINE__)
#define IR_LOGE() pypto::Logger(pypto::LogLevel::ERROR, __LINE__)
#define IR_LOGF() pypto::Logger(pypto::LogLevel::FATAL, __LINE__)
#define IR_LOGV() pypto::Logger(pypto::LogLevel::EVENT, __LINE__)
* \brief Helper function for formatted logging to avoid redefining safe functions in macros
* \param fmt Format string
* \param ... Variable arguments
* \return Formatted string
*/
inline std::string FormatLogMessage(const char* fmt, ...)
{
constexpr int defaultBufSize = 1024;
std::string buf(defaultBufSize, '\0');
va_list args;
va_start(args, fmt);
int msgLength = vsnprintf_s(buf.data(), buf.size(), buf.size() - 1, fmt, args);
va_end(args);
if (msgLength < 0) {
return "[FormatLogMessage error]";
}
if (msgLength > defaultBufSize) {
buf.resize(msgLength + 1, '\0');
va_start(args, fmt);
int ret = vsnprintf_s(buf.data(), buf.size(), buf.size() - 1, fmt, args);
va_end(args);
if (ret < 0) {
return "[FormatLogMessage error]";
}
}
return buf;
}
#define LOG_F(lvl, fmt, args...) \
do { \
if (pypto::LoggerManager::GetManager().level <= pypto::LogLevel::lvl) { \
LOG_##lvl(pypto::FormatLogMessage(fmt, ##args).c_str()); \
} \
} while (false)
#define LOG_DEBUG_F(fmt, args...) LOG_F(DEBUG, fmt, ##args)
#define LOG_INFO_F(fmt, args...) LOG_F(INFO, fmt, ##args)
#define LOG_WARN_F(fmt, args...) LOG_F(WARN, fmt, ##args)
#define LOG_ERROR_F(fmt, args...) LOG_F(ERROR, fmt, ##args)
#define LOG_EVENT_F(fmt, args...) LOG_F(EVENT, fmt, ##args)
* \brief Helper class for IRCHECK, INTERNAL_CHECK, UNREACHABLE, and INTERNAL_UNREACHABLE macros
*
* This class collects error messages via operator<< and throws
* an exception on destruction if the check condition failed.
*
* \tparam ExceptionType The type of exception to throw (ValueError or InternalError)
*/
template <typename ExceptionType>
class FatalLogger;
template <typename ExceptionType>
class FatalLogger {
private:
std::stringstream ss;
const char* file;
int line;
const char* exprStr;
public:
FatalLogger(const char* exprStr_, const char* file_, int line_) : file(file_), line(line_), exprStr(exprStr_) {}
[[noreturn]] ~FatalLogger() noexcept(false)
{
ss << "\n"
<< "Check failed: " << exprStr << " at " << file << ":" << line;
throw ExceptionType(ss.str());
}
template <typename T>
FatalLogger& operator<<(T&& val)
{
ss << (std::forward<T>(val));
return *this;
}
std::stringstream& GetStream() { return ss; }
FatalLogger(const FatalLogger&) = delete;
FatalLogger& operator=(const FatalLogger&) = delete;
FatalLogger(FatalLogger&&) = delete;
FatalLogger& operator=(FatalLogger&&) = delete;
};
* \brief Check a condition and throw ValueError if it fails
*
* Usage: IRCHECK(condition) << "error message";
*/
#define IRCHECK(expr) \
if (!(expr)) \
pypto::FatalLogger<pypto::ir::ValueError>(#expr, __FILE__, __LINE__)
* \brief Check an internal invariant and throw InternalError if it fails
*
* Usage: INTERNAL_CHECK(condition) << "error message";
*/
#define INTERNAL_CHECK(expr) \
if (!(expr)) \
pypto::FatalLogger<pypto::ir::InternalError>(#expr, __FILE__, __LINE__)
* \brief Mark a code path as unreachable and throw ValueError if reached
*
* Usage: UNREACHABLE << "optional message";
*/
#define UNREACHABLE pypto::FatalLogger<pypto::ir::ValueError>("unreachable", __FILE__, __LINE__)
* \brief Mark a code path as internally unreachable and throw InternalError if reached
*
* Usage: INTERNAL_UNREACHABLE << "optional message";
*/
#define INTERNAL_UNREACHABLE pypto::FatalLogger<pypto::ir::InternalError>("unreachable", __FILE__, __LINE__)
* @brief Check an internal invariant with IR source location and throw InternalError if it fails
*
* Usage: INTERNAL_CHECK_SPAN(condition, node->span_) << "error message";
*/
#define INTERNAL_CHECK_SPAN(expr, span) \
if (!!(expr)) \
; \
else \
pypto::FatalLogger<pypto::ir::InternalError>(#expr, span.Filename().c_str(), span.BeginLine())
}