diff --git a/CMakeLists.txt b/CMakeLists.txt
index 54387ac..6480ed1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake)
-set(BLT_VERSION 5.1.9)
+set(BLT_VERSION 5.1.10)
set(BLT_TARGET BLT)
@@ -78,7 +78,7 @@ if (${BUILD_LOGGING})
message(STATUS "Building ${Yellow}logging${ColourReset} cxx files")
file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp")
else ()
- set(PARSE_FILES "")
+ set(LOGGING_FILES "")
endif ()
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap)
diff --git a/include/blt/logging/ansi.h b/include/blt/logging/ansi.h
new file mode 100644
index 0000000..1aa7c83
--- /dev/null
+++ b/include/blt/logging/ansi.h
@@ -0,0 +1,184 @@
+#pragma once
+/*
+ * Copyright (C) 2024 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BLT_LOGGING_COLORS_H
+#define BLT_LOGGING_COLORS_H
+
+#include
+#include
+
+#define BLT_ANSI_ESCAPE "\x1B"
+#define BLT_ANSI_CSI BLT_ANSI_ESCAPE "["
+#define BLT_ANSI_DSC BLT_ANSI_ESCAPE "P"
+#define BLT_ANSI_OSC BLT_ANSI_ESCAPE "]"
+
+// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
+namespace blt::logging::ansi
+{
+ namespace color
+ {
+ enum class color_mode : i32
+ {
+ RESET_ALL = 0,
+ BOLD = 1,
+ DIM = 2,
+ ITALIC = 3,
+ UNDERLINE = 4,
+ BLINK = 5,
+ REVERSE = 7,
+ HIDDEN = 8,
+ STRIKE_THROUGH = 9,
+ };
+
+ enum class color8 : i32
+ {
+ BLACK = 0,
+ RED = 1,
+ GREEN = 2,
+ YELLOW = 3,
+ BLUE = 4,
+ MAGENTA = 5,
+ CYAN = 6,
+ WHITE = 7,
+ DEFAULT = 9
+ };
+
+ enum class color8_bright : i32
+ {
+ BLACK = 0,
+ RED = 1,
+ GREEN = 2,
+ YELLOW = 3,
+ BLUE = 4,
+ MAGENTA = 5,
+ CYAN = 6,
+ WHITE = 7,
+ };
+
+ namespace detail
+ {
+ template
+ struct color_converter
+ {
+ };
+
+ template <>
+ struct color_converter
+ {
+ static std::string to_string(color8 color, const bool background)
+ {
+ return (background ? "4" : "3") + std::to_string(static_cast(color));
+ }
+ };
+
+ template <>
+ struct color_converter
+ {
+ static std::string to_string(color8_bright color, const bool background)
+ {
+ return (background ? "10" : "9") + std::to_string(static_cast(color));
+ }
+ };
+ }
+ }
+
+ namespace general
+ {
+ inline const std::string bell = "\x07";
+ inline const std::string bs = "\x08";
+ inline const std::string horizontal_tab = "\x09";
+ inline const std::string linefeed = "\x0A";
+ inline const std::string vertical_tab = "\x0B";
+ inline const std::string form_feed = "\x0C";
+ inline const std::string carriage_return = "\x0D";
+ inline const std::string escape = BLT_ANSI_ESCAPE;
+ inline const std::string del = "\0x7F";
+ inline const std::string csi = BLT_ANSI_CSI;
+ inline const std::string dsc = BLT_ANSI_DSC;
+ inline const std::string osc = BLT_ANSI_OSC;
+ }
+
+ namespace cursor
+ {
+ inline const std::string home = BLT_ANSI_CSI "H";
+
+ template
+ inline std::string move_to(i64 line, i64 column)
+ {
+ if constexpr (UseH)
+ return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H";
+ else
+ return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f";
+ }
+
+ inline std::string move_up(i64 lines)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A";
+ }
+
+ inline std::string move_down(i64 lines)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B";
+ }
+
+ inline std::string move_right(i64 columns)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C";
+ }
+
+ inline std::string move_left(i64 columns)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D";
+ }
+
+ inline std::string move_begin_down(i64 lines)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E";
+ }
+
+ inline std::string move_begin_up(i64 lines)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F";
+ }
+
+ inline std::string move_to(i64 column)
+ {
+ return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G";
+ }
+
+ inline const std::string request_cursor_position = BLT_ANSI_CSI "6n";
+ inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M";
+ inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7";
+ inline const std::string resotre_cursor_position_dec = BLT_ANSI_ESCAPE " 8";
+ inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s";
+ inline const std::string resotre_cursor_position_sco = BLT_ANSI_CSI "u";
+ }
+
+ namespace erase
+ {
+ inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J";
+ inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J";
+ inline const std::string entire_screen = BLT_ANSI_CSI "2J";
+ inline const std::string saved_lines = BLT_ANSI_CSI "3J";
+ inline const std::string to_end_of_line = BLT_ANSI_CSI "0K";
+ inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K";
+ inline const std::string entire_line = BLT_ANSI_CSI "2K";
+ }
+}
+
+#endif //BLT_LOGGING_COLORS_H
diff --git a/include/blt/logging/fmt_tokenizer.h b/include/blt/logging/fmt_tokenizer.h
index 8f1cffe..7f98833 100644
--- a/include/blt/logging/fmt_tokenizer.h
+++ b/include/blt/logging/fmt_tokenizer.h
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
namespace blt::logging
{
@@ -38,6 +39,8 @@ namespace blt::logging
POUND,
LEFT_CHEVRON,
RIGHT_CHEVRON,
+ OPEN_BRACKET,
+ CLOSE_BRACKET,
CARET
};
@@ -102,14 +105,16 @@ namespace blt::logging
private:
size_t m_pos = 0;
- std::string_view m_fmt;
+ std::string_view m_fmt{};
};
class fmt_parser_t
{
public:
- explicit fmt_parser_t() = default;
+ explicit fmt_parser_t(std::vector>& handlers): m_handlers(handlers)
+ {
+ }
fmt_token_t& peek(const size_t offset)
{
@@ -153,14 +158,17 @@ namespace blt::logging
void parse_fmt_field();
void parse_arg_id();
+ std::string parse_arg_or_number();
void parse_fmt_spec();
+ void parse_fmt_spec_fill();
void parse_fmt_spec_align();
void parse_fmt_spec_sign();
void parse_fmt_spec_form();
void parse_fmt_spec_width();
void parse_fmt_spec_precision();
+ void parse_fill();
void parse_align();
void parse_sign();
void parse_form();
@@ -172,6 +180,8 @@ namespace blt::logging
std::vector m_tokens;
fmt_tokenizer_t m_tokenizer;
fmt_spec_t m_spec;
+
+ std::vector>& m_handlers;
};
}
diff --git a/include/blt/logging/logging.h b/include/blt/logging/logging.h
index 91c577f..f2d1b3c 100644
--- a/include/blt/logging/logging.h
+++ b/include/blt/logging/logging.h
@@ -43,11 +43,11 @@ namespace blt::logging
template
std::string log(std::string fmt, Args&&... args)
{
- compile(std::move(fmt));
auto sequence = std::make_integer_sequence{};
m_arg_print_funcs.clear();
m_arg_print_funcs.resize(sizeof...(Args));
create_conv_funcs(sequence, std::forward(args)...);
+ compile(std::move(fmt));
process_strings();
return to_string();
}
@@ -137,6 +137,7 @@ namespace blt::logging
};
}
+ [[nodiscard]] size_t find_ending_brace(size_t begin) const;
void setup_stream(const fmt_spec_t& spec) const;
void process_strings();
static void handle_type(std::ostream& stream, const fmt_spec_t& spec);
@@ -150,7 +151,7 @@ namespace blt::logging
std::string m_fmt;
std::ostream& m_stream;
- fmt_parser_t m_parser;
+ fmt_parser_t m_parser{m_arg_print_funcs};
// normal sections of string
std::vector m_string_sections;
// processed format specs
diff --git a/include/blt/logging/logging_config.h b/include/blt/logging/logging_config.h
new file mode 100644
index 0000000..3447e41
--- /dev/null
+++ b/include/blt/logging/logging_config.h
@@ -0,0 +1,58 @@
+#pragma once
+/*
+ * Copyright (C) 2024 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BLT_LOGGING_LOGGING_CONFIG_H
+#define BLT_LOGGING_LOGGING_CONFIG_H
+
+#include
+#include
+#include
+#include
+#include
+
+namespace blt::logging
+{
+ enum class log_level_t
+ {
+ TRACE,
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR,
+ FATAL
+ };
+
+ inline constexpr size_t LOG_LEVEL_COUNT = 6;
+
+ class logging_config_t
+ {
+ public:
+ std::optional log_file_path;
+ std::string log_format = "";
+ std::array log_level_colors = {
+
+ };
+ log_level_t level = log_level_t::TRACE;
+ bool use_color = true;
+ bool log_to_console = true;
+
+ private:
+ };
+}
+
+#endif //BLT_LOGGING_LOGGING_CONFIG_H
diff --git a/include/blt/std/logging.h b/include/blt/std/logging.h
index 42bc3f4..86d6b41 100644
--- a/include/blt/std/logging.h
+++ b/include/blt/std/logging.h
@@ -344,494 +344,6 @@ namespace blt::logging
void setMaxFileSize(size_t fileSize);
}
-//#define BLT_LOGGING_IMPLEMENTATION
-#ifdef BLT_LOGGING_IMPLEMENTATION
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS)
- #include
- #elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS)
- #include
- #else
- #include
- #endif
- #include
- #include
-
-template
-using hashmap = std::unordered_map;
-
-namespace blt::logging {
-
-/**
- * Used to store fast associations between built in tags and their respective values
- */
-class tag_map {
- private:
- tag* tags;
- size_t size;
-
- [[nodiscard]] static inline size_t hash(const tag& t) {
- size_t h = t.tag[1] * 3 - t.tag[0];
- return h - 100;
- }
-
- // TODO: fix
- void expand() {
- auto newSize = size * 2;
- auto newTags = new tag[newSize];
- for (size_t i = 0; i < size; i++)
- newTags[i] = tags[i];
- delete[] tags;
- tags = newTags;
- size = newSize;
- }
- public:
- tag_map(std::initializer_list initial_tags){
- size_t max = 0;
- for (const auto& t : initial_tags)
- max = std::max(max, hash(t));
- tags = new tag[(size = max+1)];
- for (const auto& t : initial_tags)
- insert(t);
- }
- tag_map(const tag_map& copy) {
- tags = new tag[(size = copy.size)];
- for (size_t i = 0; i < size; i++)
- tags[i] = copy.tags[i];
- }
-
- void insert(const tag& t) {
- auto h = hash(t);
- //if (h > size)
- // expand();
- if (!tags[h].tag.empty())
- std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n";
- tags[h] = t;
- }
-
- tag& operator[](const std::string& name) const {
- auto h = hash(tag{name, nullptr});
- if (h > size)
- std::cerr << "Tag out of bounds";
- return tags[h];
- }
-
- ~tag_map(){
- delete[] tags;
- tags = nullptr;
- }
-};
-
-class LogFileWriter {
- private:
- std::string m_path;
- std::fstream* output = nullptr;
- public:
- explicit LogFileWriter() = default;
-
- void writeLine(const std::string& path, const std::string& line){
- if (path != m_path || output == nullptr){
- clear();
- delete output;
- output = new std::fstream(path, std::ios::out | std::ios::app);
- if (!output->good()){
- throw std::runtime_error("Unable to open console filestream!\n");
- }
- }
- if (!output->good()){
- std::cerr << "There has been an error in the logging file stream!\n";
- output->clear();
- }
- *output << line;
- }
-
- void clear(){
- if (output != nullptr) {
- try {
- output->flush();
- output->close();
- } catch (std::exception& e){
- std::cerr << e.what() << "\n";
- }
- }
- }
-
- ~LogFileWriter() {
- clear();
- delete(output);
- }
-};
-
-#ifdef WIN32
- #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t)
-#else
- #define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr
-#endif
-
-//#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t)
- #define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \
- S += '-'; \
- S += ensureHasDigits(now.tm_mon+1, 2); \
- S += '-'; \
- S += ensureHasDigits(now.tm_mday, 2);
- #define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \
- S += ':'; \
- S += ensureHasDigits(now.tm_min, 2); \
- S += ':'; \
- S += ensureHasDigits(now.tm_sec, 2);
-
-static inline std::string ensureHasDigits(int current, int digits) {
- std::string asString = std::to_string(current);
- auto length = digits - asString.length();
- if (length <= 0)
- return asString;
- std::string zeros;
- zeros.reserve(length);
- for (unsigned int i = 0; i < length; i++){
- zeros += '0';
- }
- return zeros + asString;
-}
-
-log_format loggingFormat {};
-hashmap loggingThreadNames;
-hashmap> loggingStreamLines;
-LogFileWriter writer;
-
-const std::unique_ptr tagMap = std::make_unique(tag_map{
- {"YEAR", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return std::to_string(now.tm_year);
- }},
- {"MONTH", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return ensureHasDigits(now.tm_mon+1, 2);
- }},
- {"DAY", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return ensureHasDigits(now.tm_mday, 2);
- }},
- {"HOUR", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return ensureHasDigits(now.tm_hour, 2);
- }},
- {"MINUTE", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return ensureHasDigits(now.tm_min, 2);
- }},
- {"SECOND", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- return ensureHasDigits(now.tm_sec, 2);
- }},
- {"MS", [](const tag_func_param&) -> std::string {
- return std::to_string(std::chrono::duration_cast(
- std::chrono::high_resolution_clock::now().time_since_epoch()).count()
- );
- }},
- {"NS", [](const tag_func_param&) -> std::string {
- return std::to_string(std::chrono::duration_cast(
- std::chrono::high_resolution_clock::now().time_since_epoch()).count()
- );
- }},
- {"ISO_YEAR", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- BLT_ISO_YEAR(returnStr);
- return returnStr;
- }},
- {"TIME", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- BLT_CUR_TIME(returnStr);
- return returnStr;
- }},
- {"FULL_TIME", [](const tag_func_param&) -> std::string {
- BLT_NOW();
- BLT_ISO_YEAR(ISO);
- BLT_CUR_TIME(TIME);
- ISO += ' ';
- ISO += TIME;
- return ISO;
- }},
- {"LF", [](const tag_func_param& f) -> std::string {
- return loggingFormat.levelColors[(int)f.level];
- }},
- {"ER", [](const tag_func_param&) -> std::string {
- return loggingFormat.levelColors[(int)log_level::ERROR];
- }},
- {"CNR", [](const tag_func_param& f) -> std::string {
- return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : "";
- }},
- {"RC", [](const tag_func_param&) -> std::string {
- return "\033[0m";
- }},
- {"LOG_LEVEL", [](const tag_func_param& f) -> std::string {
- return loggingFormat.levelNames[(int)f.level];
- }},
- {"THREAD_NAME", [](const tag_func_param&) -> std::string {
- if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end())
- return "UNKNOWN";
- return loggingThreadNames[std::this_thread::get_id()];
- }},
- {"FILE", [](const tag_func_param& f) -> std::string {
- return f.file;
- }},
- {"LINE", [](const tag_func_param& f) -> std::string {
- return f.line;
- }},
- {"RAW_STR", [](const tag_func_param& f) -> std::string {
- return f.raw_string;
- }},
- {"STR", [](const tag_func_param& f) -> std::string {
- return f.formatted_string;
- }}
-});
-
-static inline std::vector split(std::string s, const std::string& delim) {
- size_t pos = 0;
- std::vector tokens;
- while ((pos = s.find(delim)) != std::string::npos) {
- auto token = s.substr(0, pos);
- tokens.push_back(token);
- s.erase(0, pos + delim.length());
- }
- tokens.push_back(s);
- return tokens;
-}
-
-inline std::string filename(const std::string& path){
- if (loggingFormat.printFullFileName)
- return path;
- auto paths = split(path, "/");
- auto final = paths[paths.size()-1];
- if (final == "/")
- return paths[paths.size()-2];
- return final;
-}
-
-class string_parser {
- private:
- std::string _str;
- size_t _pos;
- public:
- explicit string_parser(std::string str): _str(std::move(str)), _pos(0) {}
-
- inline char next(){
- return _str[_pos++];
- }
-
- [[nodiscard]] inline bool has_next() const {
- return _pos < _str.size();
- }
-};
-
-std::string stripANSI(const std::string& str){
- string_parser parser(str);
- std::string out;
- while (parser.has_next()){
- char c = parser.next();
- if (c == '\033'){
- while (parser.has_next() && parser.next() != 'm');
- } else
- out += c;
- }
- return out;
-}
-
-void applyCFormatting(const std::string& format, std::string& output, std::va_list& args){
- // args must be copied because they will be consumed by the first vsnprintf
- va_list args_copy;
- va_copy(args_copy, args);
-
- auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1;
- auto* buffer = new char[static_cast(buffer_size)];
-
- vsnprintf(buffer, buffer_size, format.c_str(), args);
- output = std::string(buffer);
-
- delete[] buffer;
-
- va_end(args_copy);
-}
-
-/**
- * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string
- */
-inline bool tagOpening(string_parser& parser, std::string& out){
- char c = ' ';
- if (parser.has_next() && (c = parser.next()) == '{')
- if (parser.has_next() && (c = parser.next()) == '{')
- return true;
- else
- out += c;
- else
- out += c;
- return false;
-}
-
-void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line){
- while (parser.has_next()){
- char c = parser.next();
- std::string nonTag;
- if (c == '$' && tagOpening(parser, nonTag)){
- std::string tag;
- while (parser.has_next()){
- c = parser.next();
- if (c == '}')
- break;
- tag += c;
- }
- c = parser.next();
- if (parser.has_next() && c != '}') {
- std::cerr << "Error processing tag, is not closed with two '}'!\n";
- break;
- }
- if (loggingFormat.ensureAlignment && tag == "STR") {
- auto currentOutputWidth = out.size();
- auto& longestWidth = loggingFormat.currentWidth;
- longestWidth = std::max(longestWidth, currentOutputWidth);
- // pad with spaces
- if (currentOutputWidth != longestWidth){
- for (size_t i = currentOutputWidth; i < longestWidth; i++)
- out += ' ';
- }
- }
- tag_func_param param{
- level, filename({file}), std::to_string(line), userStr, userStr
- };
- out += (*tagMap)[tag].func(param);
- } else {
- out += c;
- out += nonTag;
- }
- }
-}
-
-std::string applyFormatString(const std::string& str, log_level level, const char* file, int line){
- // this can be speedup by preprocessing the string into an easily callable class
- // where all the variables are ready to be substituted in one step
- // and all static information already entered
- string_parser parser(loggingFormat.logOutputFormat);
- std::string out;
- parseString(parser, out, str, level, file, line);
-
- return out;
-}
-
-void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) {
- std::string withoutLn = format;
- auto len = withoutLn.length();
-
- if (len > 0 && withoutLn[len - 1] == '\n')
- withoutLn = withoutLn.substr(0, len-1);
-
- std::string out;
-
- applyCFormatting(withoutLn, out, args);
-
- if (level == log_level::NONE){
- std::cout << out << std::endl;
- return;
- }
-
- std::string finalFormattedOutput = applyFormatString(out, level, file, line);
-
- if (loggingFormat.logToConsole)
- std::cout << finalFormattedOutput;
-
-
- if (loggingFormat.logToFile){
- string_parser parser(loggingFormat.logFileName);
- std::string fileName;
- parseString(parser, fileName, withoutLn, level, file, line);
-
- auto path = loggingFormat.logFilePath;
- if (!path.empty() && path[path.length()-1] != '/')
- path += '/';
-
- // if the file has changed (new day in default case) we should reset the rollover count
- if (loggingFormat.lastFile != fileName){
- loggingFormat.currentRollover = 0;
- loggingFormat.lastFile = fileName;
- }
-
- path += fileName;
- path += '-';
- path += std::to_string(loggingFormat.currentRollover);
- path += ".log";
-
- if (std::filesystem::exists(path)) {
- auto fileSize = std::filesystem::file_size(path);
-
- // will start on next file
- if (fileSize > loggingFormat.logMaxFileSize)
- loggingFormat.currentRollover++;
- }
-
- writer.writeLine(path, stripANSI(finalFormattedOutput));
- }
- //std::cout.flush();
-}
-
-void log_stream_internal(const std::string& str, const logger& logger) {
- auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level];
-// s += str;
- for (char c : str){
- s += c;
- if (c == '\n'){
- log(s, logger.level, logger.file, logger.line);
- s = "";
- }
- }
-}
-
-void setThreadName(const std::string& name) {
- loggingThreadNames[std::this_thread::get_id()] = name;
-}
-
-void setLogFormat(const log_format& format){
- loggingFormat = format;
-}
-void setLogColor(log_level level, const std::string& newFormat){
- loggingFormat.levelColors[(int)level] = newFormat;
-}
-void setLogName(log_level level, const std::string& newFormat){
- loggingFormat.levelNames[(int)level] = newFormat;
-}
-void setLogOutputFormat(const std::string& newFormat){
- loggingFormat.logOutputFormat = newFormat;
-}
-void setLogToFile(bool shouldLogToFile){
- loggingFormat.logToFile = shouldLogToFile;
-}
-void setLogToConsole(bool shouldLogToConsole){
- loggingFormat.logToConsole = shouldLogToConsole;
-}
-void setLogPath(const std::string& path){
- loggingFormat.logFilePath = path;
-}
-void setLogFileName(const std::string& fileName){
- loggingFormat.logFileName = fileName;
-}
-void setMaxFileSize(size_t fileSize) {
- loggingFormat.logMaxFileSize = fileSize;
-}
-
-void flush() {
- std::cerr.flush();
- std::cout.flush();
-}
-
-}
-
-#endif
-
#if defined(__clang__) || defined(__llvm__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
diff --git a/src/blt/logging/ansi.cpp b/src/blt/logging/ansi.cpp
new file mode 100644
index 0000000..6c2e611
--- /dev/null
+++ b/src/blt/logging/ansi.cpp
@@ -0,0 +1,18 @@
+/*
+ *
+ * Copyright (C) 2025 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
\ No newline at end of file
diff --git a/src/blt/logging/fmt_tokenizer.cpp b/src/blt/logging/fmt_tokenizer.cpp
index 5fe4362..9ff97b5 100644
--- a/src/blt/logging/fmt_tokenizer.cpp
+++ b/src/blt/logging/fmt_tokenizer.cpp
@@ -55,6 +55,10 @@ namespace blt::logging
return fmt_token_type::RIGHT_CHEVRON;
case '^':
return fmt_token_type::CARET;
+ case '{':
+ return fmt_token_type::OPEN_BRACKET;
+ case '}':
+ return fmt_token_type::CLOSE_BRACKET;
default:
return fmt_token_type::STRING;
}
@@ -64,6 +68,12 @@ namespace blt::logging
{
if (m_pos >= m_fmt.size())
return {};
+ bool is_escaped = false;
+ if (m_fmt[m_pos] == '\\')
+ {
+ is_escaped = true;
+ ++m_pos;
+ }
switch (const auto base_type = get_type(m_fmt[m_pos]))
{
case fmt_token_type::SPACE:
@@ -75,6 +85,10 @@ namespace blt::logging
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CARET:
+ case fmt_token_type::OPEN_BRACKET:
+ case fmt_token_type::CLOSE_BRACKET:
+ if (is_escaped)
+ return fmt_token_t{fmt_token_type::STRING, std::string_view{m_fmt.data() + m_pos++, 1}};
return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}};
default:
{
@@ -164,6 +178,29 @@ namespace blt::logging
m_spec.arg_id = std::stoll(std::string(value));
}
+ std::string fmt_parser_t::parse_arg_or_number()
+ {
+ auto [type, value] = next();
+ if (type == fmt_token_type::NUMBER)
+ return std::string(value);
+ if (type == fmt_token_type::OPEN_BRACKET)
+ {
+ auto [next_type, next_value] = next();
+ if (next_type != fmt_token_type::NUMBER)
+ throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(next_value) + '\'');
+ if (next().type != fmt_token_type::CLOSE_BRACKET)
+ throw std::runtime_error("Expected closing bracket when parsing arg or number, unexpected value '" + std::string(next_value) + '\'');
+ // TODO: this feels like an evil hack.
+ const auto arg_id = std::stoul(std::string(next_value));
+ if (arg_id >= m_handlers.size())
+ throw std::runtime_error("Invalid arg id " + std::to_string(arg_id) + ", max arg supported: " + std::to_string(m_handlers.size()));
+ std::stringstream ss;
+ m_handlers[arg_id](ss, fmt_spec_t{});
+ return ss.str();
+ }
+ throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(value) + '\'');
+ }
+
void fmt_parser_t::parse_fmt_spec()
{
// consume :
@@ -172,12 +209,9 @@ namespace blt::logging
switch (type)
{
case fmt_token_type::STRING:
+ // if there is a token beyond this string, it is not a type string
if (has_next(1))
- {
- const auto [next_type, next_value] = peek(1);
- if (is_align_t(next_type))
- parse_fmt_spec_align();
- }
+ parse_fmt_spec_fill();
return;
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::LEFT_CHEVRON:
@@ -185,12 +219,14 @@ namespace blt::logging
parse_fmt_spec_align();
break;
case fmt_token_type::COLON:
+ case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
+ case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
@@ -207,6 +243,46 @@ namespace blt::logging
}
}
+ void fmt_parser_t::parse_fmt_spec_fill()
+ {
+ parse_fill();
+ if (!has_next())
+ return;
+ auto [type, value] = peek();
+ switch (type)
+ {
+ case fmt_token_type::RIGHT_CHEVRON:
+ case fmt_token_type::LEFT_CHEVRON:
+ case fmt_token_type::CARET:
+ parse_fmt_spec_align();
+ break;
+ case fmt_token_type::NUMBER:
+ case fmt_token_type::OPEN_BRACKET:
+ parse_fmt_spec_width();
+ break;
+ case fmt_token_type::DOT:
+ parse_fmt_spec_precision();
+ break;
+ case fmt_token_type::SPACE:
+ case fmt_token_type::MINUS:
+ case fmt_token_type::PLUS:
+ parse_fmt_spec_sign();
+ break;
+ case fmt_token_type::POUND:
+ parse_fmt_spec_form();
+ break;
+ case fmt_token_type::STRING:
+ return;
+ case fmt_token_type::COLON:
+ case fmt_token_type::CLOSE_BRACKET:
+ {
+ std::stringstream ss;
+ ss << "(Stage (Fill)) Invalid token type " << static_cast(type) << " value " << value;
+ throw std::runtime_error(ss.str());
+ }
+ }
+ }
+
void fmt_parser_t::parse_fmt_spec_align()
{
parse_align();
@@ -218,6 +294,7 @@ namespace blt::logging
case fmt_token_type::STRING:
return;
case fmt_token_type::NUMBER:
+ case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
@@ -235,9 +312,10 @@ namespace blt::logging
case fmt_token_type::COLON:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
+ case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
- ss << "(Stage (Begin)) Invalid token type " << static_cast(type) << " value " << value;
+ ss << "(Stage (Align)) Invalid token type " << static_cast(type) << " value " << value;
throw std::runtime_error(ss.str());
}
}
@@ -261,12 +339,14 @@ namespace blt::logging
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
+ case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Sign)) Invalid token type " << static_cast(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
+ case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
@@ -296,12 +376,14 @@ namespace blt::logging
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
+ case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Form)) Invalid token type " << static_cast(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
+ case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
@@ -330,6 +412,8 @@ namespace blt::logging
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
+ case fmt_token_type::OPEN_BRACKET:
+ case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Width)) Invalid token type " << static_cast(type) << " value " << value;
@@ -348,17 +432,22 @@ namespace blt::logging
parse_precision();
}
+ void fmt_parser_t::parse_fill()
+ {
+ auto [type, value] = next();
+ if (type != fmt_token_type::STRING)
+ {
+ std::stringstream ss;
+ ss << "Expected string when parsing fill, got " << static_cast(type) << " value " << value;
+ throw std::runtime_error(ss.str());
+ }
+ m_spec.prefix_char = value.front();
+ }
+
void fmt_parser_t::parse_align()
{
auto [type, value] = next();
- fmt_token_type process_type = type;
- if (type == fmt_token_type::STRING)
- {
- auto [next_type, next_value] = next();
- process_type = next_type;
- m_spec.prefix_char = value.front();
- }
- switch (process_type)
+ switch (type)
{
case fmt_token_type::LEFT_CHEVRON:
m_spec.alignment = fmt_align_t::LEFT;
@@ -372,7 +461,7 @@ namespace blt::logging
default:
{
std::stringstream ss;
- ss << "Invalid align type " << static_cast(process_type) << " value " << value;
+ ss << "Invalid align type " << static_cast(type) << " value " << value;
throw std::runtime_error(ss.str());
}
}
@@ -415,19 +504,17 @@ namespace blt::logging
void fmt_parser_t::parse_width()
{
- auto [_, value] = next();
+ const auto value = parse_arg_or_number();
if (value.front() == '0' && !m_spec.prefix_char.has_value())
m_spec.prefix_char = '0';
- m_spec.width = std::stoll(std::string(value));
+ m_spec.width = std::stoll(value);
}
void fmt_parser_t::parse_precision()
{
if (!has_next())
throw std::runtime_error("Missing token when parsing precision");
- auto [type, value] = next();
- if (type != fmt_token_type::NUMBER)
- throw std::runtime_error("Expected number when parsing precision");
+ auto value = parse_arg_or_number();
m_spec.precision = std::stoll(std::string(value));
}
diff --git a/src/blt/logging/logging.cpp b/src/blt/logging/logging.cpp
index 8009f0a..18fc5e7 100644
--- a/src/blt/logging/logging.cpp
+++ b/src/blt/logging/logging.cpp
@@ -35,6 +35,21 @@ namespace blt::logging
return dynamic_cast(m_stream).str();
}
+ size_t logger_t::find_ending_brace(size_t begin) const
+ {
+ size_t braces = 0;
+ for (; begin < m_fmt.size(); ++begin)
+ {
+ if (m_fmt[begin] == '{')
+ ++braces;
+ else if (m_fmt[begin] == '}')
+ --braces;
+ if (braces == 0)
+ return begin;
+ }
+ return std::string::npos;
+ }
+
void logger_t::setup_stream(const fmt_spec_t& spec) const
{
if (spec.prefix_char)
@@ -167,9 +182,8 @@ namespace blt::logging
const auto begin = m_fmt.find('{', m_last_fmt_pos);
if (begin == std::string::npos)
return {};
- const auto next_begin = m_fmt.find('{', begin + 1);
- const auto end = m_fmt.find('}', begin);
- if (end == std::string::npos || (next_begin != std::string::npos && next_begin < end))
+ const auto end = find_ending_brace(begin);
+ if (end == std::string::npos)
{
std::stringstream ss;
ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast(begin) - 5, 0l));
diff --git a/src/blt/logging/logging_config.cpp b/src/blt/logging/logging_config.cpp
new file mode 100644
index 0000000..06f1880
--- /dev/null
+++ b/src/blt/logging/logging_config.cpp
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright (C) 2025 Brett Terpstra
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+
+namespace blt::logging
+{
+
+}
\ No newline at end of file
diff --git a/src/blt/std/logging.cpp b/src/blt/std/logging.cpp
index 129d1f2..393f8f9 100644
--- a/src/blt/std/logging.cpp
+++ b/src/blt/std/logging.cpp
@@ -3,5 +3,625 @@
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
-#define BLT_LOGGING_IMPLEMENTATION
#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS)
+ #include
+#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS)
+ #include
+#else
+#include
+#endif
+#include
+#include
+
+template
+using hashmap = std::unordered_map;
+
+namespace blt::logging
+{
+ /**
+ * Used to store fast associations between built in tags and their respective values
+ */
+ class tag_map
+ {
+ private:
+ tag* tags;
+ size_t size;
+
+ [[nodiscard]] static inline size_t hash(const tag& t)
+ {
+ size_t h = t.tag[1] * 3 - t.tag[0];
+ return h - 100;
+ }
+
+ // TODO: fix
+ void expand()
+ {
+ auto newSize = size * 2;
+ auto newTags = new tag[newSize];
+ for (size_t i = 0; i < size; i++)
+ newTags[i] = tags[i];
+ delete[] tags;
+ tags = newTags;
+ size = newSize;
+ }
+
+ public:
+ tag_map(std::initializer_list initial_tags)
+ {
+ size_t max = 0;
+ for (const auto& t : initial_tags)
+ max = std::max(max, hash(t));
+ tags = new tag[(size = max + 1)];
+ for (const auto& t : initial_tags)
+ insert(t);
+ }
+
+ tag_map(const tag_map& copy)
+ {
+ tags = new tag[(size = copy.size)];
+ for (size_t i = 0; i < size; i++)
+ tags[i] = copy.tags[i];
+ }
+
+ void insert(const tag& t)
+ {
+ auto h = hash(t);
+ //if (h > size)
+ // expand();
+ if (!tags[h].tag.empty())
+ std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n";
+ tags[h] = t;
+ }
+
+ tag& operator[](const std::string& name) const
+ {
+ auto h = hash(tag{name, nullptr});
+ if (h > size)
+ std::cerr << "Tag out of bounds";
+ return tags[h];
+ }
+
+ ~tag_map()
+ {
+ delete[] tags;
+ tags = nullptr;
+ }
+ };
+
+ class LogFileWriter
+ {
+ private:
+ std::string m_path;
+ std::fstream* output = nullptr;
+
+ public:
+ explicit LogFileWriter() = default;
+
+ void writeLine(const std::string& path, const std::string& line)
+ {
+ if (path != m_path || output == nullptr)
+ {
+ clear();
+ delete output;
+ output = new std::fstream(path, std::ios::out | std::ios::app);
+ if (!output->good())
+ {
+ throw std::runtime_error("Unable to open console filestream!\n");
+ }
+ }
+ if (!output->good())
+ {
+ std::cerr << "There has been an error in the logging file stream!\n";
+ output->clear();
+ }
+ *output << line;
+ }
+
+ void clear()
+ {
+ if (output != nullptr)
+ {
+ try
+ {
+ output->flush();
+ output->close();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << e.what() << "\n";
+ }
+ }
+ }
+
+ ~LogFileWriter()
+ {
+ clear();
+ delete(output);
+ }
+ };
+
+#ifdef WIN32
+ #define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t)
+#else
+#define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr
+#endif
+
+ //#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t)
+#define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \
+ S += '-'; \
+ S += ensureHasDigits(now.tm_mon+1, 2); \
+ S += '-'; \
+ S += ensureHasDigits(now.tm_mday, 2);
+#define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \
+ S += ':'; \
+ S += ensureHasDigits(now.tm_min, 2); \
+ S += ':'; \
+ S += ensureHasDigits(now.tm_sec, 2);
+
+ static inline std::string ensureHasDigits(int current, int digits)
+ {
+ std::string asString = std::to_string(current);
+ auto length = digits - asString.length();
+ if (length <= 0)
+ return asString;
+ std::string zeros;
+ zeros.reserve(length);
+ for (unsigned int i = 0; i < length; i++)
+ {
+ zeros += '0';
+ }
+ return zeros + asString;
+ }
+
+ log_format loggingFormat{};
+ hashmap loggingThreadNames;
+ hashmap> loggingStreamLines;
+ LogFileWriter writer;
+
+ const std::unique_ptr tagMap = std::make_unique(tag_map{
+ {
+ "YEAR", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return std::to_string(now.tm_year);
+ }
+ },
+ {
+ "MONTH", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return ensureHasDigits(now.tm_mon + 1, 2);
+ }
+ },
+ {
+ "DAY", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return ensureHasDigits(now.tm_mday, 2);
+ }
+ },
+ {
+ "HOUR", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return ensureHasDigits(now.tm_hour, 2);
+ }
+ },
+ {
+ "MINUTE", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return ensureHasDigits(now.tm_min, 2);
+ }
+ },
+ {
+ "SECOND", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ return ensureHasDigits(now.tm_sec, 2);
+ }
+ },
+ {
+ "MS", [](const tag_func_param&) -> std::string
+ {
+ return std::to_string(std::chrono::duration_cast(
+ std::chrono::high_resolution_clock::now().time_since_epoch()).count()
+ );
+ }
+ },
+ {
+ "NS", [](const tag_func_param&) -> std::string
+ {
+ return std::to_string(std::chrono::duration_cast(
+ std::chrono::high_resolution_clock::now().time_since_epoch()).count()
+ );
+ }
+ },
+ {
+ "ISO_YEAR", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ BLT_ISO_YEAR(returnStr);
+ return returnStr;
+ }
+ },
+ {
+ "TIME", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ BLT_CUR_TIME(returnStr);
+ return returnStr;
+ }
+ },
+ {
+ "FULL_TIME", [](const tag_func_param&) -> std::string
+ {
+ BLT_NOW();
+ BLT_ISO_YEAR(ISO);
+ BLT_CUR_TIME(TIME);
+ ISO += ' ';
+ ISO += TIME;
+ return ISO;
+ }
+ },
+ {
+ "LF", [](const tag_func_param& f) -> std::string
+ {
+ return loggingFormat.levelColors[(int)f.level];
+ }
+ },
+ {
+ "ER", [](const tag_func_param&) -> std::string
+ {
+ return loggingFormat.levelColors[(int)log_level::ERROR];
+ }
+ },
+ {
+ "CNR", [](const tag_func_param& f) -> std::string
+ {
+ return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : "";
+ }
+ },
+ {
+ "RC", [](const tag_func_param&) -> std::string
+ {
+ return "\033[0m";
+ }
+ },
+ {
+ "LOG_LEVEL", [](const tag_func_param& f) -> std::string
+ {
+ return loggingFormat.levelNames[(int)f.level];
+ }
+ },
+ {
+ "THREAD_NAME", [](const tag_func_param&) -> std::string
+ {
+ if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end())
+ return "UNKNOWN";
+ return loggingThreadNames[std::this_thread::get_id()];
+ }
+ },
+ {
+ "FILE", [](const tag_func_param& f) -> std::string
+ {
+ return f.file;
+ }
+ },
+ {
+ "LINE", [](const tag_func_param& f) -> std::string
+ {
+ return f.line;
+ }
+ },
+ {
+ "RAW_STR", [](const tag_func_param& f) -> std::string
+ {
+ return f.raw_string;
+ }
+ },
+ {
+ "STR", [](const tag_func_param& f) -> std::string
+ {
+ return f.formatted_string;
+ }
+ }
+ });
+
+ static inline std::vector split(std::string s, const std::string& delim)
+ {
+ size_t pos = 0;
+ std::vector tokens;
+ while ((pos = s.find(delim)) != std::string::npos)
+ {
+ auto token = s.substr(0, pos);
+ tokens.push_back(token);
+ s.erase(0, pos + delim.length());
+ }
+ tokens.push_back(s);
+ return tokens;
+ }
+
+ inline std::string filename(const std::string& path)
+ {
+ if (loggingFormat.printFullFileName)
+ return path;
+ auto paths = split(path, "/");
+ auto final = paths[paths.size() - 1];
+ if (final == "/")
+ return paths[paths.size() - 2];
+ return final;
+ }
+
+ class string_parser
+ {
+ private:
+ std::string _str;
+ size_t _pos;
+
+ public:
+ explicit string_parser(std::string str): _str(std::move(str)), _pos(0)
+ {
+ }
+
+ inline char next()
+ {
+ return _str[_pos++];
+ }
+
+ [[nodiscard]] inline bool has_next() const
+ {
+ return _pos < _str.size();
+ }
+ };
+
+ std::string stripANSI(const std::string& str)
+ {
+ string_parser parser(str);
+ std::string out;
+ while (parser.has_next())
+ {
+ char c = parser.next();
+ if (c == '\033')
+ {
+ while (parser.has_next() && parser.next() != 'm');
+ }
+ else
+ out += c;
+ }
+ return out;
+ }
+
+ void applyCFormatting(const std::string& format, std::string& output, std::va_list& args)
+ {
+ // args must be copied because they will be consumed by the first vsnprintf
+ va_list args_copy;
+ va_copy(args_copy, args);
+
+ auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1;
+ auto* buffer = new char[static_cast(buffer_size)];
+
+ vsnprintf(buffer, buffer_size, format.c_str(), args);
+ output = std::string(buffer);
+
+ delete[] buffer;
+
+ va_end(args_copy);
+ }
+
+ /**
+ * Checks if the next character in the parser is a tag opening, if not output the buffer to the out string
+ */
+ inline bool tagOpening(string_parser& parser, std::string& out)
+ {
+ char c = ' ';
+ if (parser.has_next() && (c = parser.next()) == '{')
+ if (parser.has_next() && (c = parser.next()) == '{')
+ return true;
+ else
+ out += c;
+ else
+ out += c;
+ return false;
+ }
+
+ void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line)
+ {
+ while (parser.has_next())
+ {
+ char c = parser.next();
+ std::string nonTag;
+ if (c == '$' && tagOpening(parser, nonTag))
+ {
+ std::string tag;
+ while (parser.has_next())
+ {
+ c = parser.next();
+ if (c == '}')
+ break;
+ tag += c;
+ }
+ c = parser.next();
+ if (parser.has_next() && c != '}')
+ {
+ std::cerr << "Error processing tag, is not closed with two '}'!\n";
+ break;
+ }
+ if (loggingFormat.ensureAlignment && tag == "STR")
+ {
+ auto currentOutputWidth = out.size();
+ auto& longestWidth = loggingFormat.currentWidth;
+ longestWidth = std::max(longestWidth, currentOutputWidth);
+ // pad with spaces
+ if (currentOutputWidth != longestWidth)
+ {
+ for (size_t i = currentOutputWidth; i < longestWidth; i++)
+ out += ' ';
+ }
+ }
+ tag_func_param param{
+ level, filename({file}), std::to_string(line), userStr, userStr
+ };
+ out += (*tagMap)[tag].func(param);
+ }
+ else
+ {
+ out += c;
+ out += nonTag;
+ }
+ }
+ }
+
+ std::string applyFormatString(const std::string& str, log_level level, const char* file, int line)
+ {
+ // this can be speedup by preprocessing the string into an easily callable class
+ // where all the variables are ready to be substituted in one step
+ // and all static information already entered
+ string_parser parser(loggingFormat.logOutputFormat);
+ std::string out;
+ parseString(parser, out, str, level, file, line);
+
+ return out;
+ }
+
+ void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args)
+ {
+ std::string withoutLn = format;
+ auto len = withoutLn.length();
+
+ if (len > 0 && withoutLn[len - 1] == '\n')
+ withoutLn = withoutLn.substr(0, len - 1);
+
+ std::string out;
+
+ applyCFormatting(withoutLn, out, args);
+
+ if (level == log_level::NONE)
+ {
+ std::cout << out << std::endl;
+ return;
+ }
+
+ std::string finalFormattedOutput = applyFormatString(out, level, file, line);
+
+ if (loggingFormat.logToConsole)
+ std::cout << finalFormattedOutput;
+
+
+ if (loggingFormat.logToFile)
+ {
+ string_parser parser(loggingFormat.logFileName);
+ std::string fileName;
+ parseString(parser, fileName, withoutLn, level, file, line);
+
+ auto path = loggingFormat.logFilePath;
+ if (!path.empty() && path[path.length() - 1] != '/')
+ path += '/';
+
+ // if the file has changed (new day in default case) we should reset the rollover count
+ if (loggingFormat.lastFile != fileName)
+ {
+ loggingFormat.currentRollover = 0;
+ loggingFormat.lastFile = fileName;
+ }
+
+ path += fileName;
+ path += '-';
+ path += std::to_string(loggingFormat.currentRollover);
+ path += ".log";
+
+ if (std::filesystem::exists(path))
+ {
+ auto fileSize = std::filesystem::file_size(path);
+
+ // will start on next file
+ if (fileSize > loggingFormat.logMaxFileSize)
+ loggingFormat.currentRollover++;
+ }
+
+ writer.writeLine(path, stripANSI(finalFormattedOutput));
+ }
+ //std::cout.flush();
+ }
+
+ void log_stream_internal(const std::string& str, const logger& logger)
+ {
+ auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level];
+ // s += str;
+ for (char c : str)
+ {
+ s += c;
+ if (c == '\n')
+ {
+ log(s, logger.level, logger.file, logger.line);
+ s = "";
+ }
+ }
+ }
+
+ void setThreadName(const std::string& name)
+ {
+ loggingThreadNames[std::this_thread::get_id()] = name;
+ }
+
+ void setLogFormat(const log_format& format)
+ {
+ loggingFormat = format;
+ }
+
+ void setLogColor(log_level level, const std::string& newFormat)
+ {
+ loggingFormat.levelColors[(int)level] = newFormat;
+ }
+
+ void setLogName(log_level level, const std::string& newFormat)
+ {
+ loggingFormat.levelNames[(int)level] = newFormat;
+ }
+
+ void setLogOutputFormat(const std::string& newFormat)
+ {
+ loggingFormat.logOutputFormat = newFormat;
+ }
+
+ void setLogToFile(bool shouldLogToFile)
+ {
+ loggingFormat.logToFile = shouldLogToFile;
+ }
+
+ void setLogToConsole(bool shouldLogToConsole)
+ {
+ loggingFormat.logToConsole = shouldLogToConsole;
+ }
+
+ void setLogPath(const std::string& path)
+ {
+ loggingFormat.logFilePath = path;
+ }
+
+ void setLogFileName(const std::string& fileName)
+ {
+ loggingFormat.logFileName = fileName;
+ }
+
+ void setMaxFileSize(const size_t fileSize)
+ {
+ loggingFormat.logMaxFileSize = fileSize;
+ }
+
+ void flush()
+ {
+ std::cerr.flush();
+ std::cout.flush();
+ }
+}
diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp
index 64cccb2..bf815c5 100644
--- a/tests/logger_tests.cpp
+++ b/tests/logger_tests.cpp
@@ -57,27 +57,32 @@ This is a println with boolean as hex 0x1
This is a println with boolean as octal 1
This is a println with alignment left 64 end value
This is a println with alignment right 46 end value
-This is a println with alignment left (fill) 46******** end value
+This is a println with alignment left (fill) 46******** end value
This is a println with alignment right (fill) ********46 end value
+This is a println with alignment right (fill with reserved character) ^^^^^^^^46 end value
+This is a println with fill no alignment %%%%%%%%%%%%%%%%%%46 end value
+This is a println with arg reference 46.02
+This is a println with arg reference &&&&&&&&&&&&&&&&&&&&
)");
std::pair compare_strings(const std::string& s1, const std::string& s2)
{
- if (s1.size() != s2.size())
- return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"};
+ const auto size = std::min(s1.size(), s2.size());
size_t index = 0;
- for (; index < s1.size(); ++index)
+ for (; index < size; ++index)
{
if (s1[index] != s2[index])
{
std::stringstream ss;
const auto i1 = std::max(static_cast(index) - 32, 0l);
- const auto l1 = std::min(static_cast(s1.size()) - i1, 65l);
+ const auto l1 = std::min(static_cast(size) - i1, 65l);
ss << "Strings differ at index " << index << "!\n";
ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl;
return {false, ss.str()};
}
}
+ if (s1.size() != s2.size())
+ return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"};
return {true, ""};
}
@@ -116,8 +121,12 @@ int main()
blt::logging::println(ss, "This is a println with boolean as octal {:o}", true);
blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64);
blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46);
- blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46);
+ blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46);
blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46);
+ blt::logging::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46);
+ blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46);
+ blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2);
+ blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20);
blt::logging::print(ss.str());
auto [passed, error_msg] = compare_strings(expected_str, ss.str());
BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str());