working on ansi

main
Brett 2025-03-04 22:02:51 -05:00
parent 1e30544cff
commit fcbc6b947d
12 changed files with 1060 additions and 524 deletions

View File

@ -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)

184
include/blt/logging/ansi.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef BLT_LOGGING_COLORS_H
#define BLT_LOGGING_COLORS_H
#include <string>
#include <blt/std/types.h>
#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 <typename T>
struct color_converter
{
};
template <>
struct color_converter<color8>
{
static std::string to_string(color8 color, const bool background)
{
return (background ? "4" : "3") + std::to_string(static_cast<i32>(color));
}
};
template <>
struct color_converter<color8_bright>
{
static std::string to_string(color8_bright color, const bool background)
{
return (background ? "10" : "9") + std::to_string(static_cast<i32>(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 <bool UseH>
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

View File

@ -23,6 +23,7 @@
#include <string_view>
#include <vector>
#include <blt/std/types.h>
#include <functional>
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<std::function<void(std::ostream&, const fmt_spec_t&)>>& 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<fmt_token_t> m_tokens;
fmt_tokenizer_t m_tokenizer;
fmt_spec_t m_spec;
std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>>& m_handlers;
};
}

View File

@ -43,11 +43,11 @@ namespace blt::logging
template <typename... Args>
std::string log(std::string fmt, Args&&... args)
{
compile(std::move(fmt));
auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{};
m_arg_print_funcs.clear();
m_arg_print_funcs.resize(sizeof...(Args));
create_conv_funcs(sequence, std::forward<Args>(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<std::string_view> m_string_sections;
// processed format specs

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef BLT_LOGGING_LOGGING_CONFIG_H
#define BLT_LOGGING_LOGGING_CONFIG_H
#include <optional>
#include <string>
#include <blt/std/types.h>
#include <blt/logging/ansi.h>
#include <array>
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<std::string> log_file_path;
std::string log_format = "";
std::array<std::string, LOG_LEVEL_COUNT> 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

View File

@ -344,494 +344,6 @@ namespace blt::logging
void setMaxFileSize(size_t fileSize);
}
//#define BLT_LOGGING_IMPLEMENTATION
#ifdef BLT_LOGGING_IMPLEMENTATION
#include <iostream>
#include <chrono>
#include <ctime>
#include <unordered_map>
#include <thread>
#include <cstdarg>
#include <iostream>
#include <vector>
#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS)
#include <filesystem>
#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS)
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
#include <ios>
#include <fstream>
template<typename K, typename V>
using hashmap = std::unordered_map<K, V>;
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<tag> 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<std::thread::id, std::string> loggingThreadNames;
hashmap<std::thread::id, hashmap<blt::logging::log_level, std::string>> loggingStreamLines;
LogFileWriter writer;
const std::unique_ptr<tag_map> tagMap = std::make_unique<tag_map>(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::milliseconds>(
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::nanoseconds>(
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<std::string> split(std::string s, const std::string& delim) {
size_t pos = 0;
std::vector<std::string> 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<unsigned long>(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"

18
src/blt/logging/ansi.cpp Normal file
View File

@ -0,0 +1,18 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include <blt/logging/ansi.h>

View File

@ -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<u8>(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<u8>(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<u8>(type) << " value " << value;
ss << "(Stage (Align)) Invalid token type " << static_cast<u8>(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<u8>(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<u8>(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<u8>(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<u8>(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<u8>(process_type) << " value " << value;
ss << "Invalid align type " << static_cast<u8>(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));
}

View File

@ -35,6 +35,21 @@ namespace blt::logging
return dynamic_cast<std::stringstream&>(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<i64>(begin) - 5, 0l));

View File

@ -0,0 +1,23 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include <blt/logging/logging_config.h>
namespace blt::logging
{
}

View File

@ -3,5 +3,625 @@
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#define BLT_LOGGING_IMPLEMENTATION
#include <blt/std/logging.h>
#include <iostream>
#include <chrono>
#include <ctime>
#include <unordered_map>
#include <thread>
#include <cstdarg>
#include <iostream>
#include <vector>
#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS)
#include <filesystem>
#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS)
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
#include <ios>
#include <fstream>
template <typename K, typename V>
using hashmap = std::unordered_map<K, V>;
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<tag> 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<std::thread::id, std::string> loggingThreadNames;
hashmap<std::thread::id, hashmap<blt::logging::log_level, std::string>> loggingStreamLines;
LogFileWriter writer;
const std::unique_ptr<tag_map> tagMap = std::make_unique<tag_map>(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::milliseconds>(
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::nanoseconds>(
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<std::string> split(std::string s, const std::string& delim)
{
size_t pos = 0;
std::vector<std::string> 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<unsigned long>(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();
}
}

View File

@ -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<bool, std::string> 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<blt::i64>(index) - 32, 0l);
const auto l1 = std::min(static_cast<blt::i64>(s1.size()) - i1, 65l);
const auto l1 = std::min(static_cast<blt::i64>(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());