From cbc703b852985cbfef0ed2a789589bde9e66c690 Mon Sep 17 00:00:00 2001
From: Brett <brettmaster1@gmail.com>
Date: Wed, 8 Feb 2023 17:22:27 -0500
Subject: [PATCH] Cleaned up logging and added file location + line number

---
 include/blt/profiling/profiler.h |   5 +-
 include/blt/std/format.h         |   1 +
 include/blt/std/logging.h        | 132 +++++++++++--------------------
 src/blt/profiling/profiler.cpp   |  17 ++--
 src/blt/std/logging.cpp          | 117 ++++++++++++---------------
 src/tests/main.cpp               |   7 ++
 src/tests/nbt_tests.h            |   4 +-
 7 files changed, 115 insertions(+), 168 deletions(-)

diff --git a/include/blt/profiling/profiler.h b/include/blt/profiling/profiler.h
index b7d9f60..ff47163 100644
--- a/include/blt/profiling/profiler.h
+++ b/include/blt/profiling/profiler.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <blt/std/queue.h>
 #include <unordered_map>
+#include <blt/std/logging.h>
 
 /**
  * Defines several disableable macros (#define BLT_DISABLE_PROFILING). If you do not use these macros profiling will not be disableable
@@ -39,8 +40,8 @@ namespace blt::profiling {
     CaptureInterval getInterval(const std::string& profileName, const std::string& intervalName);
     Profile getProfile(const std::string& profileName);
     
-    void printProfile(const std::string& profileName, int loggingLevel = -1);
-    void printOrderedProfile(const std::string& profileName, int loggingLevel = -1);
+    void printProfile(const std::string& profileName, blt::logging::LOG_LEVEL loggingLevel = logging::NONE);
+    void printOrderedProfile(const std::string& profileName, logging::LOG_LEVEL loggingLevel = logging::NONE);
     
     void discardProfiles();
     void discardIntervals(const std::string& profileName);
diff --git a/include/blt/std/format.h b/include/blt/std/format.h
index 0a0846e..51e5dd8 100644
--- a/include/blt/std/format.h
+++ b/include/blt/std/format.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 namespace blt::string {
+    // TODO template the padding functions:
     
     /**
      * Ensure that string str has expected length, pad after the string otherwise.
diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h
index be9e5d7..42763b3 100644
--- a/include/blt/std/logging.h
+++ b/include/blt/std/logging.h
@@ -8,17 +8,22 @@
 #define BLT_LOGGING_H
 
 #include <string>
+#include <type_traits>
 
 namespace blt::logging {
     
     enum LOG_LEVEL {
-        TRACE = 0, DEBUG = 1, INFO = 2, WARN = 3, ERROR = 4, FATAL = 5
+        TRACE = 0, DEBUG = 1, INFO = 2, WARN = 3, ERROR = 4, FATAL = 5, NONE = 6
     };
     
     struct LOG_PROPERTIES {
         bool m_useColor = true;
         bool m_logToConsole = true;
         bool m_logToFile = true;
+        // include file + line data?
+        bool m_logWithData = true;
+        // print the whole path or just the file name?
+        bool m_logFullPath = false;
         const char* m_directory = "./";
         LOG_LEVEL minLevel = TRACE;
         
@@ -30,7 +35,7 @@ namespace blt::logging {
     
     struct logger {
         LOG_LEVEL level;
-        void logi(const std::string& str) const;
+        void log_internal(const std::string& str) const;
         // evil hack, todo: better way
         #ifdef BLT_DISABLE_LOGGING
             void log(const std::string& str) const {
@@ -38,23 +43,21 @@ namespace blt::logging {
             }
         #else
             void log(const std::string& str) const {
-                logi(str);
+                log_internal(str);
             }
         #endif
             void flush() const;
+            static void flush_all();
     };
     
+    static logger std_out{NONE};
+    
     static logger tlog{TRACE};
     static logger dlog{DEBUG};
     static logger ilog{INFO};
     static logger wlog{WARN};
     static logger elog{ERROR};
     static logger flog{FATAL};
-    
-    /**
-     * Always in order of the enum!
-     */
-    static logger loggerLevelDecode[6]{tlog, dlog, ilog, wlog, elog, flog};
 
     static logger trace{TRACE};
     static logger debug{DEBUG};
@@ -63,73 +66,42 @@ namespace blt::logging {
     static logger error{ERROR};
     static logger fatal{FATAL};
     
+    static inline logger& getLoggerFromLevel(LOG_LEVEL level) {
+        static logger loggerLevelDecode[7]{tlog, dlog, ilog, wlog, elog, flog, std_out};
+        return loggerLevelDecode[level];
+    }
+    
     static inline logger& operator<<(logger& out, const std::string& str) {
         out.log(str);
         return out;
     }
     
-    static inline logger& operator<<(logger& out, const char chr) {
-        out.log(std::to_string(chr));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const unsigned char i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const short i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const unsigned short i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const int i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const unsigned int i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const long i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const unsigned long i) {
-        out.log(std::to_string(i));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const float f) {
-        out.log(std::to_string(f));
-        return out;
-    }
-    
-    static inline logger& operator<<(logger& out, const double f) {
-        out.log(std::to_string(f));
+    template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>
+    static inline logger& operator<<(logger& out, T t) {
+        out.log(std::to_string(t));
         return out;
     }
     
     void init(LOG_PROPERTIES properties);
-    void log(const std::string& format, LOG_LEVEL level, int auto_line, ...);
-    void log(int i, LOG_LEVEL level, int auto_line);
-    void log(long i, LOG_LEVEL level, int auto_line);
-    void log(unsigned int i, LOG_LEVEL level, int auto_line);
-    void log(unsigned long i, LOG_LEVEL level, int auto_line);
-    void log(char i, LOG_LEVEL level, int auto_line);
-    void log(unsigned char i, LOG_LEVEL level, int auto_line);
-    void log(short i, LOG_LEVEL level, int auto_line);
-    void log(unsigned short i, LOG_LEVEL level, int auto_line);
-    void log(float f, LOG_LEVEL level, int auto_line);
-    void log(double f, LOG_LEVEL level, int auto_line);
+    void log_internal(const std::string& format, LOG_LEVEL level, const char* file, int currentLine, int auto_line, ...);
+    
+    // template voodoo magic (SFINAE, "Substitution Failure Is Not An Error")
+    // https://stackoverflow.com/questions/44848011/c-limit-template-type-to-numbers
+    // std::enable_if has a member called type if the condition is true.
+    // What I am doing is saying that if the condition is true then the template has a non type
+    // template parameter of type type* (which is void* since the default type for enable_if is void) and it's value is nullptr.
+    // if the condition is false, the substitution fails, and the entire template is silently (no compiler error) discarded from the overload set.
+    /**
+     * Logs an is_arithmetic type to the console.
+     * @tparam T type to log
+     * @param t the value to log
+     * @param level logger level to log at
+     * @param auto_line put a new line at the end if none exists if true
+     */
+    template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>
+    inline void log_internal(T t, LOG_LEVEL level, const char* file, int currentLine, int auto_line){
+        log_internal(std::to_string(t), level, file, currentLine, auto_line);
+    }
     /**
      * Will flush all buffers! This might cause issues with threads!
      */
@@ -144,26 +116,12 @@ namespace blt::logging {
     #define BLT_ERROR(format, ...)
     #define BLT_FATAL(format, ...)
 #else
-    /*#define BLT_TRACE(format, ...) log(format, blt::logging::TRACE, false, ##__VA_ARGS__);
-    #define BLT_DEBUG(format, ...) log(format, blt::logging::DEBUG, false, ##__VA_ARGS__);
-    #define BLT_INFO(format, ...) log(format, blt::logging::INFO, false, ##__VA_ARGS__);
-    #define BLT_WARN(format, ...) log(format, blt::logging::WARN, false, ##__VA_ARGS__);
-    #define BLT_ERROR(format, ...) log(format, blt::logging::ERROR, false, ##__VA_ARGS__);
-    #define BLT_FATAL(format, ...) log(format, blt::logging::FATAL, false, ##__VA_ARGS__);
-    
-    #define BLT_TRACE_LN(format, ...) log(format, blt::logging::TRACE, true, ##__VA_ARGS__);
-    #define BLT_DEBUG_LN(format, ...) log(format, blt::logging::DEBUG, true, ##__VA_ARGS__);
-    #define BLT_INFO_LN(format, ...) log(format, blt::logging::INFO, true, ##__VA_ARGS__);
-    #define BLT_WARN_LN(format, ...) log(format, blt::logging::WARN, true, ##__VA_ARGS__);
-    #define BLT_ERROR_LN(format, ...) log(format, blt::logging::ERROR, true, ##__VA_ARGS__);
-    #define BLT_FATAL_LN(format, ...) log(format, blt::logging::FATAL, true, ##__VA_ARGS__);*/
-    
-    #define BLT_TRACE(format, ...) log(format, blt::logging::TRACE, true, ##__VA_ARGS__);
-    #define BLT_DEBUG(format, ...) log(format, blt::logging::DEBUG, true, ##__VA_ARGS__);
-    #define BLT_INFO(format, ...) log(format, blt::logging::INFO, true, ##__VA_ARGS__);
-    #define BLT_WARN(format, ...) log(format, blt::logging::WARN, true, ##__VA_ARGS__);
-    #define BLT_ERROR(format, ...) log(format, blt::logging::ERROR, true, ##__VA_ARGS__);
-    #define BLT_FATAL(format, ...) log(format, blt::logging::FATAL, true, ##__VA_ARGS__);
+    #define BLT_TRACE(format, ...) log_internal(format, blt::logging::TRACE, __FILE__, __LINE__, true, ##__VA_ARGS__);
+    #define BLT_DEBUG(format, ...) log_internal(format, blt::logging::DEBUG, __FILE__, __LINE__, true, ##__VA_ARGS__);
+    #define BLT_INFO(format, ...) log_internal(format, blt::logging::INFO, __FILE__, __LINE__, true, ##__VA_ARGS__);
+    #define BLT_WARN(format, ...) log_internal(format, blt::logging::WARN, __FILE__, __LINE__, true, ##__VA_ARGS__);
+    #define BLT_ERROR(format, ...) log_internal(format, blt::logging::ERROR, __FILE__, __LINE__, true, ##__VA_ARGS__);
+    #define BLT_FATAL(format, ...) log_internal(format, blt::logging::FATAL, __FILE__, __LINE__, true, ##__VA_ARGS__);
 #endif
 
-#endif //BLT_TESTS_LOGGING_H
+#endif //BLT_LOGGING_H
diff --git a/src/blt/profiling/profiler.cpp b/src/blt/profiling/profiler.cpp
index cf220b9..2eac68c 100644
--- a/src/blt/profiling/profiler.cpp
+++ b/src/blt/profiling/profiler.cpp
@@ -46,18 +46,13 @@ namespace blt::profiling {
         return profiles[profileName];
     }
     
-    inline void print(const std::vector<std::string>& lines, int level) {
-        if (level < 0) {
-            for (const auto& line : lines)
-                std::cout << line;
-        } else {
-            auto& logger = logging::loggerLevelDecode[level];
-            for (const auto& line : lines)
-                logger << line;
-        }
+    inline void print(const std::vector<std::string>& lines, logging::LOG_LEVEL level) {
+        auto& logger = logging::getLoggerFromLevel(level);
+        for (const auto& line : lines)
+            logger << line;
     }
     
-    void printProfile(const std::string& profileName, int loggingLevel) {
+    void printProfile(const std::string& profileName, blt::logging::LOG_LEVEL loggingLevel) {
         string::TableFormatter formatter {profileName};
         formatter.addColumn({"Interval"});
         formatter.addColumn({"Time (ns)"});
@@ -91,7 +86,7 @@ namespace blt::profiling {
         return container1.difference < container2.difference;
     }
     
-    void printOrderedProfile(const std::string& profileName, int loggingLevel) {
+    void printOrderedProfile(const std::string& profileName, logging::LOG_LEVEL loggingLevel) {
         const auto& profile = profiles[profileName];
         const auto& intervals = profile.intervals;
         const auto& points = profile.points;
diff --git a/src/blt/std/logging.cpp b/src/blt/std/logging.cpp
index 83687e9..8916d40 100644
--- a/src/blt/std/logging.cpp
+++ b/src/blt/std/logging.cpp
@@ -9,6 +9,7 @@
 #include <string>
 #include <iostream>
 #include "blt/std/string.h"
+#include "blt/std/format.h"
 #include <fstream>
 #include <unordered_map>
 #include <ios>
@@ -107,10 +108,28 @@ namespace blt::logging {
         BLT_LOGGING_PROPERTIES = properties;
     }
     
-    inline void log(const std::string& str, bool hasEndingLinefeed, LOG_LEVEL level, int auto_line){
+    inline std::string filename(const std::string& path){
+        if (BLT_LOGGING_PROPERTIES.m_logFullPath)
+            return path;
+        auto paths = blt::string::split(path, "/");
+        auto final = paths[paths.size()-1];
+        if (final == "/")
+            return paths[paths.size()-2];
+        return final;
+    }
+    
+    inline void log(const std::string& str, bool hasEndingLinefeed, LOG_LEVEL level, const char* file, int currentLine, int auto_line){
         if (level < BLT_LOGGING_PROPERTIES.minLevel)
             return;
         std::string outputString = System::getTimeStringLog();
+        bool includeExtras = BLT_LOGGING_PROPERTIES.m_logWithData && currentLine >= 0;
+        if (includeExtras) {
+            outputString += "[";
+            outputString += filename(file);
+            outputString += ":";
+            outputString += std::to_string(currentLine);
+            outputString += "] ";
+        }
         outputString += levelNames[level];
         outputString += str;
         
@@ -138,7 +157,7 @@ namespace blt::logging {
         }
     }
     
-    void log(const std::string& format, LOG_LEVEL level, int auto_line, ...) {
+    void log_internal(const std::string& format, LOG_LEVEL level, const char* file, int currentLine, int auto_line, ...) {
         va_list args;
         va_start(args, auto_line);
         
@@ -150,91 +169,57 @@ namespace blt::logging {
         if (hasEndingLinefeed)
             formattedString = formattedString.substr(0, formattedString.length()-1);
     
-        log(formattedString, hasEndingLinefeed, level, auto_line);
+        log(formattedString, hasEndingLinefeed, level, file, currentLine, auto_line);
     
         va_end(args);
     }
     
-    void log(int i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
+    // stores an association between thread -> log level -> current line buffer
+    std::unordered_map<std::thread::id, std::unordered_map<LOG_LEVEL, std::string>> thread_local_strings;
     
-    void log(long i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(unsigned int i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(unsigned long i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(char i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(unsigned char i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(short i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(unsigned short i, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(i), false, level, true);
-    }
-    
-    void log(float f, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(f), false, level, true);
-    }
-    
-    void log(double f, LOG_LEVEL level, int auto_line) {
-        log(std::to_string(f), false, level, true);
-    }
-    
-    std::unordered_map<std::thread::id, std::string> thread_local_strings;
-    
-    void logger::logi(const std::string& str) const {
+    void logger::log_internal(const std::string& str) const {
         auto id = std::this_thread::get_id();
-        auto th_str = thread_local_strings[id];
+        auto th_str = thread_local_strings[id][level];
         th_str += str;
         
         if (blt::string::contains(str, "\n")){
             // make sure new lines are properly formatted to prevent danging lines. Ie "[trace]: .... [debug]: ...."
             bool hasEndingLinefeed = str[str.length()-1] == '\n';
-            logging::log(th_str, false, level, !hasEndingLinefeed);
-            thread_local_strings[id] = "";
+            if (level == NONE) {
+                std::cout << th_str;
+            } else
+                logging::log(th_str, false, level, "", -1, !hasEndingLinefeed);
+            thread_local_strings[id][level] = "";
         } else {
-            thread_local_strings[id] = th_str;
+            thread_local_strings[id][level] = th_str;
         }
     }
     
+    void flushLogger_internal(std::thread::id id, LOG_LEVEL level){
+        auto th_str = thread_local_strings[id][level];
+        if (th_str.empty())
+            return;
+        bool hasEndingLinefeed = th_str[th_str.length() - 1] == '\n';
+        logging::log(th_str, false, level, "", -1, !hasEndingLinefeed);
+        thread_local_strings[id][level] = "";
+    }
+    
     void logger::flush() const {
         for (const auto& id : thread_local_strings) {
-            auto th_str = id.second;
-            bool hasEndingLinefeed = th_str[th_str.length() - 1] == '\n';
-            logging::log(th_str, false, level, !hasEndingLinefeed);
-            thread_local_strings[id.first] = "";
+            flushLogger_internal(id.first, level);
+        }
+    }
+    
+    void logger::flush_all() {
+        for (const auto& id : thread_local_strings) {
+            for (const auto& level : thread_local_strings[id.first]){
+                flushLogger_internal(id.first, level.first);
+            }
         }
     }
     
     void flush() {
-        // TODO: this will prevent proper level output. Please fixme
-        tlog.flush();
-        dlog.flush();
-        ilog.flush();
-        wlog.flush();
-        elog.flush();
-        flog.flush();
-        trace.flush();
-        debug.flush();
-        info.flush();
-        warn.flush();
-        error.flush();
-        fatal.flush();
+        logger::flush_all();
         std::cerr.flush();
         std::cout.flush();
     }
diff --git a/src/tests/main.cpp b/src/tests/main.cpp
index a81fdca..7e865a8 100644
--- a/src/tests/main.cpp
+++ b/src/tests/main.cpp
@@ -14,5 +14,12 @@ int main() {
     
     nbt_tests();
     
+    blt::logging::tlog << "Test Output!\n";
+    blt::logging::tlog << 5;
+    blt::logging::flog << 5;
+    blt::logging::ilog << 5;
+    
+    blt::logging::flush();
+    
     return 0;
 }
\ No newline at end of file
diff --git a/src/tests/nbt_tests.h b/src/tests/nbt_tests.h
index 123924c..efa3295 100644
--- a/src/tests/nbt_tests.h
+++ b/src/tests/nbt_tests.h
@@ -113,9 +113,9 @@ inline void nbt_read_tests(){
     }
     
     BLT_PRINT_ORDERED("nbt read");
-    BLT_TRACE("");
+    BLT_TRACE("{BLANK_LINE}");
     BLT_PRINT_ORDERED("nbt read block");
-    BLT_TRACE("");
+    BLT_TRACE("{BLANK_LINE}");
     BLT_PRINT_ORDERED("nbt read individual");
     
     delete[] read_buffer;