working on ansi
parent
1e30544cff
commit
fcbc6b947d
|
@ -1,6 +1,6 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
include(cmake/color.cmake)
|
include(cmake/color.cmake)
|
||||||
set(BLT_VERSION 5.1.9)
|
set(BLT_VERSION 5.1.10)
|
||||||
|
|
||||||
set(BLT_TARGET BLT)
|
set(BLT_TARGET BLT)
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ if (${BUILD_LOGGING})
|
||||||
message(STATUS "Building ${Yellow}logging${ColourReset} cxx files")
|
message(STATUS "Building ${Yellow}logging${ColourReset} cxx files")
|
||||||
file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp")
|
file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp")
|
||||||
else ()
|
else ()
|
||||||
set(PARSE_FILES "")
|
set(LOGGING_FILES "")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap)
|
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap)
|
||||||
|
|
|
@ -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
|
|
@ -23,6 +23,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <blt/std/types.h>
|
#include <blt/std/types.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace blt::logging
|
namespace blt::logging
|
||||||
{
|
{
|
||||||
|
@ -38,6 +39,8 @@ namespace blt::logging
|
||||||
POUND,
|
POUND,
|
||||||
LEFT_CHEVRON,
|
LEFT_CHEVRON,
|
||||||
RIGHT_CHEVRON,
|
RIGHT_CHEVRON,
|
||||||
|
OPEN_BRACKET,
|
||||||
|
CLOSE_BRACKET,
|
||||||
CARET
|
CARET
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,14 +105,16 @@ namespace blt::logging
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t m_pos = 0;
|
size_t m_pos = 0;
|
||||||
std::string_view m_fmt;
|
std::string_view m_fmt{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class fmt_parser_t
|
class fmt_parser_t
|
||||||
{
|
{
|
||||||
public:
|
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)
|
fmt_token_t& peek(const size_t offset)
|
||||||
{
|
{
|
||||||
|
@ -153,14 +158,17 @@ namespace blt::logging
|
||||||
|
|
||||||
void parse_fmt_field();
|
void parse_fmt_field();
|
||||||
void parse_arg_id();
|
void parse_arg_id();
|
||||||
|
std::string parse_arg_or_number();
|
||||||
|
|
||||||
void parse_fmt_spec();
|
void parse_fmt_spec();
|
||||||
|
void parse_fmt_spec_fill();
|
||||||
void parse_fmt_spec_align();
|
void parse_fmt_spec_align();
|
||||||
void parse_fmt_spec_sign();
|
void parse_fmt_spec_sign();
|
||||||
void parse_fmt_spec_form();
|
void parse_fmt_spec_form();
|
||||||
void parse_fmt_spec_width();
|
void parse_fmt_spec_width();
|
||||||
void parse_fmt_spec_precision();
|
void parse_fmt_spec_precision();
|
||||||
|
|
||||||
|
void parse_fill();
|
||||||
void parse_align();
|
void parse_align();
|
||||||
void parse_sign();
|
void parse_sign();
|
||||||
void parse_form();
|
void parse_form();
|
||||||
|
@ -172,6 +180,8 @@ namespace blt::logging
|
||||||
std::vector<fmt_token_t> m_tokens;
|
std::vector<fmt_token_t> m_tokens;
|
||||||
fmt_tokenizer_t m_tokenizer;
|
fmt_tokenizer_t m_tokenizer;
|
||||||
fmt_spec_t m_spec;
|
fmt_spec_t m_spec;
|
||||||
|
|
||||||
|
std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>>& m_handlers;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,11 +43,11 @@ namespace blt::logging
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
std::string log(std::string fmt, Args&&... args)
|
std::string log(std::string fmt, Args&&... args)
|
||||||
{
|
{
|
||||||
compile(std::move(fmt));
|
|
||||||
auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{};
|
auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{};
|
||||||
m_arg_print_funcs.clear();
|
m_arg_print_funcs.clear();
|
||||||
m_arg_print_funcs.resize(sizeof...(Args));
|
m_arg_print_funcs.resize(sizeof...(Args));
|
||||||
create_conv_funcs(sequence, std::forward<Args>(args)...);
|
create_conv_funcs(sequence, std::forward<Args>(args)...);
|
||||||
|
compile(std::move(fmt));
|
||||||
process_strings();
|
process_strings();
|
||||||
return to_string();
|
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 setup_stream(const fmt_spec_t& spec) const;
|
||||||
void process_strings();
|
void process_strings();
|
||||||
static void handle_type(std::ostream& stream, const fmt_spec_t& spec);
|
static void handle_type(std::ostream& stream, const fmt_spec_t& spec);
|
||||||
|
@ -150,7 +151,7 @@ namespace blt::logging
|
||||||
|
|
||||||
std::string m_fmt;
|
std::string m_fmt;
|
||||||
std::ostream& m_stream;
|
std::ostream& m_stream;
|
||||||
fmt_parser_t m_parser;
|
fmt_parser_t m_parser{m_arg_print_funcs};
|
||||||
// normal sections of string
|
// normal sections of string
|
||||||
std::vector<std::string_view> m_string_sections;
|
std::vector<std::string_view> m_string_sections;
|
||||||
// processed format specs
|
// processed format specs
|
||||||
|
|
|
@ -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
|
|
@ -344,494 +344,6 @@ namespace blt::logging
|
||||||
void setMaxFileSize(size_t fileSize);
|
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__)
|
#if defined(__clang__) || defined(__llvm__)
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||||
|
|
|
@ -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>
|
|
@ -55,6 +55,10 @@ namespace blt::logging
|
||||||
return fmt_token_type::RIGHT_CHEVRON;
|
return fmt_token_type::RIGHT_CHEVRON;
|
||||||
case '^':
|
case '^':
|
||||||
return fmt_token_type::CARET;
|
return fmt_token_type::CARET;
|
||||||
|
case '{':
|
||||||
|
return fmt_token_type::OPEN_BRACKET;
|
||||||
|
case '}':
|
||||||
|
return fmt_token_type::CLOSE_BRACKET;
|
||||||
default:
|
default:
|
||||||
return fmt_token_type::STRING;
|
return fmt_token_type::STRING;
|
||||||
}
|
}
|
||||||
|
@ -64,6 +68,12 @@ namespace blt::logging
|
||||||
{
|
{
|
||||||
if (m_pos >= m_fmt.size())
|
if (m_pos >= m_fmt.size())
|
||||||
return {};
|
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]))
|
switch (const auto base_type = get_type(m_fmt[m_pos]))
|
||||||
{
|
{
|
||||||
case fmt_token_type::SPACE:
|
case fmt_token_type::SPACE:
|
||||||
|
@ -75,6 +85,10 @@ namespace blt::logging
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
case fmt_token_type::CARET:
|
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}};
|
return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}};
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -164,6 +178,29 @@ namespace blt::logging
|
||||||
m_spec.arg_id = std::stoll(std::string(value));
|
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()
|
void fmt_parser_t::parse_fmt_spec()
|
||||||
{
|
{
|
||||||
// consume :
|
// consume :
|
||||||
|
@ -172,12 +209,9 @@ namespace blt::logging
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case fmt_token_type::STRING:
|
case fmt_token_type::STRING:
|
||||||
|
// if there is a token beyond this string, it is not a type string
|
||||||
if (has_next(1))
|
if (has_next(1))
|
||||||
{
|
parse_fmt_spec_fill();
|
||||||
const auto [next_type, next_value] = peek(1);
|
|
||||||
if (is_align_t(next_type))
|
|
||||||
parse_fmt_spec_align();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
|
@ -185,12 +219,14 @@ namespace blt::logging
|
||||||
parse_fmt_spec_align();
|
parse_fmt_spec_align();
|
||||||
break;
|
break;
|
||||||
case fmt_token_type::COLON:
|
case fmt_token_type::COLON:
|
||||||
|
case fmt_token_type::CLOSE_BRACKET:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "(Stage (Begin)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
ss << "(Stage (Begin)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
||||||
throw std::runtime_error(ss.str());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
case fmt_token_type::NUMBER:
|
case fmt_token_type::NUMBER:
|
||||||
|
case fmt_token_type::OPEN_BRACKET:
|
||||||
parse_fmt_spec_width();
|
parse_fmt_spec_width();
|
||||||
break;
|
break;
|
||||||
case fmt_token_type::DOT:
|
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()
|
void fmt_parser_t::parse_fmt_spec_align()
|
||||||
{
|
{
|
||||||
parse_align();
|
parse_align();
|
||||||
|
@ -218,6 +294,7 @@ namespace blt::logging
|
||||||
case fmt_token_type::STRING:
|
case fmt_token_type::STRING:
|
||||||
return;
|
return;
|
||||||
case fmt_token_type::NUMBER:
|
case fmt_token_type::NUMBER:
|
||||||
|
case fmt_token_type::OPEN_BRACKET:
|
||||||
parse_fmt_spec_width();
|
parse_fmt_spec_width();
|
||||||
break;
|
break;
|
||||||
case fmt_token_type::DOT:
|
case fmt_token_type::DOT:
|
||||||
|
@ -235,9 +312,10 @@ namespace blt::logging
|
||||||
case fmt_token_type::COLON:
|
case fmt_token_type::COLON:
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
|
case fmt_token_type::CLOSE_BRACKET:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
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());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,12 +339,14 @@ namespace blt::logging
|
||||||
case fmt_token_type::CARET:
|
case fmt_token_type::CARET:
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
|
case fmt_token_type::CLOSE_BRACKET:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "(Stage (Sign)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
ss << "(Stage (Sign)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
||||||
throw std::runtime_error(ss.str());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
case fmt_token_type::NUMBER:
|
case fmt_token_type::NUMBER:
|
||||||
|
case fmt_token_type::OPEN_BRACKET:
|
||||||
parse_fmt_spec_width();
|
parse_fmt_spec_width();
|
||||||
break;
|
break;
|
||||||
case fmt_token_type::DOT:
|
case fmt_token_type::DOT:
|
||||||
|
@ -296,12 +376,14 @@ namespace blt::logging
|
||||||
case fmt_token_type::CARET:
|
case fmt_token_type::CARET:
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
|
case fmt_token_type::CLOSE_BRACKET:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "(Stage (Form)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
ss << "(Stage (Form)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
||||||
throw std::runtime_error(ss.str());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
case fmt_token_type::NUMBER:
|
case fmt_token_type::NUMBER:
|
||||||
|
case fmt_token_type::OPEN_BRACKET:
|
||||||
parse_fmt_spec_width();
|
parse_fmt_spec_width();
|
||||||
break;
|
break;
|
||||||
case fmt_token_type::DOT:
|
case fmt_token_type::DOT:
|
||||||
|
@ -330,6 +412,8 @@ namespace blt::logging
|
||||||
case fmt_token_type::CARET:
|
case fmt_token_type::CARET:
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
case fmt_token_type::RIGHT_CHEVRON:
|
case fmt_token_type::RIGHT_CHEVRON:
|
||||||
|
case fmt_token_type::OPEN_BRACKET:
|
||||||
|
case fmt_token_type::CLOSE_BRACKET:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "(Stage (Width)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
ss << "(Stage (Width)) Invalid token type " << static_cast<u8>(type) << " value " << value;
|
||||||
|
@ -348,17 +432,22 @@ namespace blt::logging
|
||||||
parse_precision();
|
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()
|
void fmt_parser_t::parse_align()
|
||||||
{
|
{
|
||||||
auto [type, value] = next();
|
auto [type, value] = next();
|
||||||
fmt_token_type process_type = type;
|
switch (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)
|
|
||||||
{
|
{
|
||||||
case fmt_token_type::LEFT_CHEVRON:
|
case fmt_token_type::LEFT_CHEVRON:
|
||||||
m_spec.alignment = fmt_align_t::LEFT;
|
m_spec.alignment = fmt_align_t::LEFT;
|
||||||
|
@ -372,7 +461,7 @@ namespace blt::logging
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
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());
|
throw std::runtime_error(ss.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,19 +504,17 @@ namespace blt::logging
|
||||||
|
|
||||||
void fmt_parser_t::parse_width()
|
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())
|
if (value.front() == '0' && !m_spec.prefix_char.has_value())
|
||||||
m_spec.prefix_char = '0';
|
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()
|
void fmt_parser_t::parse_precision()
|
||||||
{
|
{
|
||||||
if (!has_next())
|
if (!has_next())
|
||||||
throw std::runtime_error("Missing token when parsing precision");
|
throw std::runtime_error("Missing token when parsing precision");
|
||||||
auto [type, value] = next();
|
auto value = parse_arg_or_number();
|
||||||
if (type != fmt_token_type::NUMBER)
|
|
||||||
throw std::runtime_error("Expected number when parsing precision");
|
|
||||||
m_spec.precision = std::stoll(std::string(value));
|
m_spec.precision = std::stoll(std::string(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,21 @@ namespace blt::logging
|
||||||
return dynamic_cast<std::stringstream&>(m_stream).str();
|
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
|
void logger_t::setup_stream(const fmt_spec_t& spec) const
|
||||||
{
|
{
|
||||||
if (spec.prefix_char)
|
if (spec.prefix_char)
|
||||||
|
@ -167,9 +182,8 @@ namespace blt::logging
|
||||||
const auto begin = m_fmt.find('{', m_last_fmt_pos);
|
const auto begin = m_fmt.find('{', m_last_fmt_pos);
|
||||||
if (begin == std::string::npos)
|
if (begin == std::string::npos)
|
||||||
return {};
|
return {};
|
||||||
const auto next_begin = m_fmt.find('{', begin + 1);
|
const auto end = find_ending_brace(begin);
|
||||||
const auto end = m_fmt.find('}', begin);
|
if (end == std::string::npos)
|
||||||
if (end == std::string::npos || (next_begin != std::string::npos && next_begin < end))
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast<i64>(begin) - 5, 0l));
|
ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast<i64>(begin) - 5, 0l));
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -3,5 +3,625 @@
|
||||||
* Licensed under GNU General Public License V3.0
|
* Licensed under GNU General Public License V3.0
|
||||||
* See LICENSE file for license detail
|
* See LICENSE file for license detail
|
||||||
*/
|
*/
|
||||||
#define BLT_LOGGING_IMPLEMENTATION
|
|
||||||
#include <blt/std/logging.h>
|
#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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 boolean as octal 1
|
||||||
This is a println with alignment left 64 end value
|
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 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) ********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)
|
std::pair<bool, std::string> compare_strings(const std::string& s1, const std::string& s2)
|
||||||
{
|
{
|
||||||
if (s1.size() != s2.size())
|
const auto size = std::min(s1.size(), s2.size());
|
||||||
return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"};
|
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for (; index < s1.size(); ++index)
|
for (; index < size; ++index)
|
||||||
{
|
{
|
||||||
if (s1[index] != s2[index])
|
if (s1[index] != s2[index])
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
const auto i1 = std::max(static_cast<blt::i64>(index) - 32, 0l);
|
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 << "Strings differ at index " << index << "!\n";
|
||||||
ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl;
|
ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl;
|
||||||
return {false, ss.str()};
|
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, ""};
|
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 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 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 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) {:*>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());
|
blt::logging::print(ss.str());
|
||||||
auto [passed, error_msg] = compare_strings(expected_str, 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());
|
BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str());
|
||||||
|
|
Loading…
Reference in New Issue