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());