From 5f3b39948f6f086ca29b00d1e8722a9594efbd38 Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Mon, 4 Mar 2024 15:33:43 +0100
Subject: [PATCH] Initial proposal for Aidge::Log system

---
 include/aidge/utils/ErrorHandling.hpp |   8 +-
 include/aidge/utils/Log.hpp           | 148 ++++++++++++++++++++++++++
 src/utils/Log.cpp                     |  59 ++++++++++
 unit_tests/utils/Test_Log.cpp         |  31 ++++++
 4 files changed, 243 insertions(+), 3 deletions(-)
 create mode 100644 include/aidge/utils/Log.hpp
 create mode 100644 src/utils/Log.cpp
 create mode 100644 unit_tests/utils/Test_Log.cpp

diff --git a/include/aidge/utils/ErrorHandling.hpp b/include/aidge/utils/ErrorHandling.hpp
index 653a774b9..d4235d2db 100644
--- a/include/aidge/utils/ErrorHandling.hpp
+++ b/include/aidge/utils/ErrorHandling.hpp
@@ -18,13 +18,15 @@
 #include <fmt/format.h>
 #include <fmt/ranges.h>
 
+#include "aidge/utils/Log.hpp"
+
 #ifdef NO_EXCEPTION
 #define AIDGE_THROW_OR_ABORT(ex, ...) \
-do { fmt::print(__VA_ARGS__); std::abort(); } while (false)
+do { Aidge::Log::fatal(__VA_ARGS__); std::abort(); } while (false)
 #else
 #include <stdexcept>
 #define AIDGE_THROW_OR_ABORT(ex, ...) \
-throw ex(fmt::format(__VA_ARGS__))
+do { Aidge::Log::fatal(__VA_ARGS__); throw ex(fmt::format(__VA_ARGS__)); } while (false)
 #endif
 
 /**
@@ -33,7 +35,7 @@ throw ex(fmt::format(__VA_ARGS__))
  * If it asserts, it means an user error.
 */
 #define AIDGE_ASSERT(stm, ...) \
-if (!(stm)) { fmt::print("Assertion failed: " #stm " in {}:{}", __FILE__, __LINE__); \
+if (!(stm)) { Aidge::Log::error("Assertion failed: " #stm " in {}:{}", __FILE__, __LINE__); \
     AIDGE_THROW_OR_ABORT(std::runtime_error, __VA_ARGS__); }
 
 /**
diff --git a/include/aidge/utils/Log.hpp b/include/aidge/utils/Log.hpp
new file mode 100644
index 000000000..1321f18a9
--- /dev/null
+++ b/include/aidge/utils/Log.hpp
@@ -0,0 +1,148 @@
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+
+
+#ifndef AIDGE_LOG_H_
+#define AIDGE_LOG_H_
+
+#include <memory>
+
+#include <fmt/format.h>
+#include <fmt/ranges.h>
+
+namespace Aidge {
+/**
+ * Aidge logging class, for displaying and file logging of events.
+*/
+class Log {
+public:
+    enum Level {
+        Debug = 0,
+        Info,
+        Notice,
+        Warn,
+        Error,
+        Fatal
+    };
+
+    /**
+     * Detailed messages for debugging purposes, providing information helpful 
+     * for developers to trace and identify issues.
+     * Detailed insights of what is appening in an operation, not useful for the
+     * end-user. The operation is performed nominally.
+     * @note This level is disabled at compile time for Release, therefore
+     * inducing no runtime overhead for Release.
+    */
+    template <typename... Args>
+    constexpr static void debug(Args&&... args) {
+#ifndef NDEBUG
+        // only when compiled in Debug
+        log(Debug, fmt::format(std::forward<Args>(args)...));
+#endif
+    }
+
+    /**
+     * Messages that provide a record of the normal operation, about 
+     * the application's state, progress, or important events.
+     * Reports normal start, end and key steps in an operation. The operation is
+     * performed nominally.
+    */
+    template <typename... Args>
+    constexpr static void info(Args&&... args) {
+        log(Info, fmt::format(std::forward<Args>(args)...));
+    }
+
+    /**
+     * Applies to normal but significant conditions that may require monitoring,
+     * like unusual or normal fallback events.
+     * Reports specific paths in an operation. The operation can still be
+     * performed normally.
+    */
+    template <typename... Args>
+    constexpr static void notice(Args&&... args) {
+        log(Notice, fmt::format(std::forward<Args>(args)...));
+    }
+
+    /**
+     * Indicates potential issues or situations that may lead to errors but do
+     * not necessarily cause immediate problems.
+     * Some specific steps of the operation could not be performed, but it can
+     * still provide an exploitable result.
+    */
+    template <typename... Args>
+    constexpr static void warn(Args&&... args) {
+        log(Warn, fmt::format(std::forward<Args>(args)...));
+    }
+
+    /**
+     * Signifies a problem or unexpected condition that the application can 
+     * recover from, but attention is needed to prevent further issues.
+     * The operation could not be performed, but it does not prevent potentiel
+     * further operations.
+    */
+    template <typename... Args>
+    constexpr static void error(Args&&... args) {
+        log(Error, fmt::format(std::forward<Args>(args)...));
+    }
+
+    /**
+     * Represents a critical error or condition that leads to the termination of
+     * the application, indicating a severe and unrecoverable problem.
+     * The operation could not be performed and any further operation is
+     * impossible.
+    */
+    template <typename... Args>
+    constexpr static void fatal(Args&&... args) {
+        log(Fatal, fmt::format(std::forward<Args>(args)...));
+    }
+
+    /**
+     * Set the minimum log level displayed in the console.
+    */
+    constexpr static void setConsoleLevel(Level level) {
+        mConsoleLevel = level;
+    }
+
+    /**
+     * Set the minimum log level saved in the log file.
+    */
+    constexpr static void setFileLevel(Level level) {
+        mFileLevel = level;
+    }
+
+    /**
+     * Set the log file name.
+     * Close the current log file and open the one with the new file name.
+     * If empty, stop logging into a file.
+    */
+    static void setFileName(const std::string& fileName) {
+        if (fileName != mFileName) {
+            mFileName = fileName;
+            mFile.release();
+
+            if (!fileName.empty()) {
+                initFile(fileName);
+            }
+        }
+    }
+
+private:
+    static void log(Level level, const std::string& msg);
+    static void initFile(const std::string& fileName);
+
+    static Level mConsoleLevel;
+    static Level mFileLevel;
+    static std::string mFileName;
+    static std::unique_ptr<FILE, decltype(&std::fclose)> mFile;
+};
+}
+
+#endif //AIDGE_LOG_H_
diff --git a/src/utils/Log.cpp b/src/utils/Log.cpp
new file mode 100644
index 000000000..a4993755d
--- /dev/null
+++ b/src/utils/Log.cpp
@@ -0,0 +1,59 @@
+/********************************************************************************
+ * 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 "aidge/utils/ErrorHandling.hpp"
+
+#include <fmt/color.h>
+#include <fmt/chrono.h>
+
+Aidge::Log::Level Aidge::Log::mConsoleLevel = Info;
+Aidge::Log::Level Aidge::Log::mFileLevel = Debug;
+std::string Aidge::Log::mFileName = "aidge_log.txt";
+std::unique_ptr<FILE, decltype(&std::fclose)> Aidge::Log::mFile {nullptr, nullptr};
+
+void Aidge::Log::log(Level level, const std::string& msg) {
+    if (level >= mConsoleLevel) {
+        // Apply log level style only for console.
+        // Styles that were already applied to msg with fmt are kept also in 
+        // the log file.
+        const auto modifier
+            = (level == Debug) ? fmt::fg(fmt::color::gray)
+            : (level == Notice) ? fmt::fg(fmt::color::light_yellow)
+            : (level == Warn) ? fmt::fg(fmt::color::orange)
+            : (level == Error) ? fmt::fg(fmt::color::red)
+            : (level == Fatal) ? fmt::bg(fmt::color::red)
+            : fmt::text_style();
+
+        fmt::println("{}", fmt::styled(msg, modifier));
+    }
+
+    if (level >= mFileLevel && !mFileName.empty()) {
+        if (!mFile) {
+            initFile(mFileName);
+        }
+
+        fmt::println(mFile.get(), msg);
+    }
+}
+
+void Aidge::Log::initFile(const std::string& fileName) {
+    mFile = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(fileName.c_str(), "a"), &std::fclose);
+
+    if (!mFile) {
+        mFileName.clear(); // prevents AIDGE_THROW_OR_ABORT() to try to log into file
+        AIDGE_THROW_OR_ABORT(std::runtime_error,
+            "Could not create log file: {}", fileName);
+    }
+
+    const std::time_t t = std::time(nullptr);
+    fmt::println(mFile.get(), "###### {:%Y-%m-%d %H:%M:%S} ######", fmt::localtime(t));
+}
diff --git a/unit_tests/utils/Test_Log.cpp b/unit_tests/utils/Test_Log.cpp
new file mode 100644
index 000000000..3d8e672b8
--- /dev/null
+++ b/unit_tests/utils/Test_Log.cpp
@@ -0,0 +1,31 @@
+/********************************************************************************
+ * 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 <catch2/catch_test_macros.hpp>
+
+#include "aidge/utils/Log.hpp"
+
+#include <fmt/color.h>
+
+using namespace Aidge;
+
+TEST_CASE("[core/log] Log") {
+    SECTION("TestLog") {
+        Log::setConsoleLevel(Log::Debug);
+        Log::debug("debug");
+        Log::debug("{}", fmt::styled("green debug", fmt::fg(fmt::color::green)));
+        Log::info("info");
+        Log::notice("notice");
+        Log::warn("warn");
+        Log::error("error");
+        Log::fatal("fatal");
+    }
+}
-- 
GitLab