From acae7a76ad6ec2eca68902816639ed42c5f59762 Mon Sep 17 00:00:00 2001 From: Patrick Rotsaert Date: Sun, 16 Jun 2024 20:07:12 +0200 Subject: [PATCH] #1838 Add ANSI-colored non-rotating file sink. --- README.md | 58 ++++++++++------ example/example.cpp | 8 +++ include/spdlog/details/ansicolors-inl.h | 39 ++++++++++- .../spdlog/sinks/ansicolor_file_sink-inl.h | 52 ++++++++++++++ include/spdlog/sinks/ansicolor_file_sink.h | 68 +++++++++++++++++++ src/file_sinks.cpp | 4 ++ tests/includes.h | 1 + tests/test_file_logging.cpp | 27 ++++++++ 8 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 include/spdlog/sinks/ansicolor_file_sink-inl.h create mode 100644 include/spdlog/sinks/ansicolor_file_sink.h diff --git a/README.md b/README.md index e49a5e0d..e7fb2b90 100644 --- a/README.md +++ b/README.md @@ -61,23 +61,23 @@ see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/ ```c++ #include "spdlog/spdlog.h" -int main() +int main() { spdlog::info("Welcome to spdlog!"); spdlog::error("Some error message with arg: {}", 1); - + spdlog::warn("Easy padding in numbers like {:08d}", 12); spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); spdlog::info("Support for floats {:03.2f}", 1.23456); spdlog::info("Positional args are {1} {0}..", "too", "supported"); spdlog::info("{:<30}", "left aligned"); - + spdlog::set_level(spdlog::level::debug); // Set global log level to debug - spdlog::debug("This message should be displayed.."); - + spdlog::debug("This message should be displayed.."); + // change log pattern spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); - + // Compile time log levels // Note that this does not change the current log level, it will only // remove (depending on SPDLOG_ACTIVE_LEVEL) the call on the release code. @@ -94,8 +94,8 @@ int main() void stdout_example() { // create a color multi-threaded logger - auto console = spdlog::stdout_color_mt("console"); - auto err_logger = spdlog::stderr_color_mt("stderr"); + auto console = spdlog::stdout_color_mt("console"); + auto err_logger = spdlog::stderr_color_mt("stderr"); spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); } ``` @@ -106,7 +106,7 @@ void stdout_example() #include "spdlog/sinks/basic_file_sink.h" void basic_logfile_example() { - try + try { auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); } @@ -117,6 +117,22 @@ void basic_logfile_example() } ``` --- +#### ANSI-colored file logger +```c++ +#include "spdlog/sinks/ansicolor_file_sink.h" +void ansicolor_logfile_example() +{ + try + { + auto logger = spdlog::ansicolor_logger_mt("ansi_logger", "logs/ansicolor-log.txt"); + } + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + } +} +``` +--- #### Rotating files ```c++ #include "spdlog/sinks/rotating_file_sink.h" @@ -161,7 +177,7 @@ void daily_example() // This is useful to display debug logs only when needed (e.g. when an error happens). // When needed, call dump_backtrace() to dump them to your log. -spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. +spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. // or my_logger->enable_backtrace(32).. for(int i = 0; i < 100; i++) { @@ -188,9 +204,9 @@ spdlog::flush_every(std::chrono::seconds(3)); #include "spdlog/stopwatch.h" void stopwatch_example() { - spdlog::stopwatch sw; + spdlog::stopwatch sw; spdlog::debug("Elapsed {}", sw); - spdlog::debug("Elapsed {:.3}", sw); + spdlog::debug("Elapsed {:.3}", sw); } ``` @@ -277,7 +293,7 @@ void async_example() // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); // alternatively: - // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); + // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); } ``` @@ -299,7 +315,7 @@ void multi_sink_example2() spdlog::register_logger(logger); } ``` - + --- #### User-defined types ```c++ @@ -321,7 +337,7 @@ void user_defined_example() --- #### User-defined flags in the log pattern -```c++ +```c++ // Log patterns can contain custom flags. // the following example will add new flag '%*' - which will be bound to a instance. #include "spdlog/pattern_formatter.h" @@ -341,7 +357,7 @@ public: }; void custom_flags_example() -{ +{ auto formatter = std::make_unique(); formatter->add_flag('*').set_pattern("[%n] [%*] [%^%l%$] %v"); spdlog::set_formatter(std::move(formatter)); @@ -409,7 +425,7 @@ $ ./example --- #### Log file open/close event handlers ```c++ -// You can get callbacks from spdlog before/after a log file has been opened or closed. +// You can get callbacks from spdlog before/after a log file has been opened or closed. // This is useful for cleanup procedures or for adding something to the start/end of the log file. void file_events_example() { @@ -419,7 +435,7 @@ void file_events_example() handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("After opening\n", fstream); }; handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("Before closing\n", fstream); }; handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); }; - auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers); + auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers); } ``` @@ -500,16 +516,16 @@ Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/ben [info] Messages : 1,000,000 [info] Threads : 10 [info] Queue : 8,192 slots -[info] Queue memory : 8,192 x 272 = 2,176 KB +[info] Queue memory : 8,192 x 272 = 2,176 KB [info] ------------------------------------------------- -[info] +[info] [info] ********************************* [info] Queue Overflow Policy: block [info] ********************************* [info] Elapsed: 1.70784 secs 585,535/sec [info] Elapsed: 1.69805 secs 588,910/sec [info] Elapsed: 1.7026 secs 587,337/sec -[info] +[info] [info] ********************************* [info] Queue Overflow Policy: overrun [info] ********************************* diff --git a/example/example.cpp b/example/example.cpp index 3ac56105..3595ca38 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -10,6 +10,7 @@ void load_levels_example(); void stdout_logger_example(); void basic_example(); +void ansicolor_example(); void rotating_example(); void ansicolor_rotating_example(); void daily_example(); @@ -71,6 +72,7 @@ int main(int, char *[]) { try { stdout_logger_example(); basic_example(); + ansicolor_example(); rotating_example(); ansicolor_rotating_example(); daily_example(); @@ -123,6 +125,12 @@ void basic_example() { auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true); } +#include "spdlog/sinks/ansicolor_file_sink.h" +void ansicolor_example() { + // Create ANSI-colored file logger (not rotated). + auto my_logger = spdlog::ansicolor_logger_mt("ansi_logger", "logs/ansicolor-log.txt", true); +} + #include "spdlog/sinks/rotating_file_sink.h" void rotating_example() { // Create a file rotating logger with 5mb size max and 3 rotated files. diff --git a/include/spdlog/details/ansicolors-inl.h b/include/spdlog/details/ansicolors-inl.h index a583463b..33e88227 100644 --- a/include/spdlog/details/ansicolors-inl.h +++ b/include/spdlog/details/ansicolors-inl.h @@ -10,6 +10,41 @@ namespace spdlog { namespace details { +// Formatting codes +constexpr const char* ansicolors::reset; +constexpr const char* ansicolors::bold; +constexpr const char* ansicolors::dark; +constexpr const char* ansicolors::underline; +constexpr const char* ansicolors::blink; +constexpr const char* ansicolors::reverse; +constexpr const char* ansicolors::concealed; +constexpr const char* ansicolors::clear_line; + +// Foreground colors +constexpr const char* ansicolors::black; +constexpr const char* ansicolors::red; +constexpr const char* ansicolors::green; +constexpr const char* ansicolors::yellow; +constexpr const char* ansicolors::blue; +constexpr const char* ansicolors::magenta; +constexpr const char* ansicolors::cyan; +constexpr const char* ansicolors::white; + +/// Background colors +constexpr const char* ansicolors::on_black; +constexpr const char* ansicolors::on_red; +constexpr const char* ansicolors::on_green; +constexpr const char* ansicolors::on_yellow; +constexpr const char* ansicolors::on_blue; +constexpr const char* ansicolors::on_magenta; +constexpr const char* ansicolors::on_cyan; +constexpr const char* ansicolors::on_white; + +/// Bold colors +constexpr const char* ansicolors::yellow_bold; +constexpr const char* ansicolors::red_bold; +constexpr const char* ansicolors::bold_on_red; + SPDLOG_INLINE ansicolors::ansicolors() { colors_.at(level::trace) = to_string_(white); colors_.at(level::debug) = to_string_(cyan); @@ -25,7 +60,7 @@ SPDLOG_INLINE void ansicolors::set_color(level::level_enum color_level, string_v } SPDLOG_INLINE std::vector ansicolors::ranges( - const details::log_msg &msg, const memory_buf_t &formatted_msg) const { + const details::log_msg& msg, const memory_buf_t& formatted_msg) const { std::vector result{}; if (msg.color_range_end > msg.color_range_start) { // before color range @@ -49,7 +84,7 @@ SPDLOG_INLINE std::vector ansicolors::ranges( return result; } -SPDLOG_INLINE std::string ansicolors::to_string_(const string_view_t &sv) { +SPDLOG_INLINE std::string ansicolors::to_string_(const string_view_t& sv) { return std::string(sv.data(), sv.size()); } diff --git a/include/spdlog/sinks/ansicolor_file_sink-inl.h b/include/spdlog/sinks/ansicolor_file_sink-inl.h new file mode 100644 index 00000000..16dc964e --- /dev/null +++ b/include/spdlog/sinks/ansicolor_file_sink-inl.h @@ -0,0 +1,52 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY + #include +#endif + +#include +#include + +namespace spdlog { +namespace sinks { + +template +SPDLOG_INLINE ansicolor_file_sink::ansicolor_file_sink( + const filename_t &filename, bool truncate, const file_event_handlers &event_handlers) + : file_helper_{event_handlers} { + file_helper_.open(filename, truncate); +} + +template +SPDLOG_INLINE void ansicolor_file_sink::set_color(level::level_enum color_level, + string_view_t color) { + std::lock_guard lock(base_sink::mutex_); + colors_.set_color(color_level, color); +} + +template +SPDLOG_INLINE const filename_t &ansicolor_file_sink::filename() const { + return file_helper_.filename(); +} + +template +SPDLOG_INLINE void ansicolor_file_sink::sink_it_(const details::log_msg &msg) { + msg.color_range_start = 0; + msg.color_range_end = 0; + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + for (const auto &range : colors_.ranges(msg, formatted)) { + file_helper_.write(range); + } +} + +template +SPDLOG_INLINE void ansicolor_file_sink::flush_() { + file_helper_.flush(); +} + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/ansicolor_file_sink.h b/include/spdlog/sinks/ansicolor_file_sink.h new file mode 100644 index 00000000..7f362fc3 --- /dev/null +++ b/include/spdlog/sinks/ansicolor_file_sink.h @@ -0,0 +1,68 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace spdlog { +namespace sinks { +/* + * ANSI-colored file sink with single file as target + */ +template +class ansicolor_file_sink final : public base_sink { +public: + explicit ansicolor_file_sink(const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}); + void set_color(level::level_enum color_level, string_view_t color); + const filename_t &filename() const; + +protected: + void sink_it_(const details::log_msg &msg) override; + void flush_() override; + +private: + details::ansicolors colors_; + details::file_helper file_helper_; +}; + +using ansicolor_file_sink_mt = ansicolor_file_sink; +using ansicolor_file_sink_st = ansicolor_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr ansicolor_logger_mt(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + event_handlers); +} + +template +inline std::shared_ptr ansicolor_logger_st(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + event_handlers); +} + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY + #include "ansicolor_file_sink-inl.h" +#endif diff --git a/src/file_sinks.cpp b/src/file_sinks.cpp index 67675b67..5c775e2e 100644 --- a/src/file_sinks.cpp +++ b/src/file_sinks.cpp @@ -15,6 +15,10 @@ template class SPDLOG_API spdlog::sinks::basic_file_sink; template class SPDLOG_API spdlog::sinks::basic_file_sink; +#include +template class SPDLOG_API spdlog::sinks::ansicolor_file_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_file_sink; + #include template class SPDLOG_API spdlog::sinks::rotating_file_sink; template class SPDLOG_API spdlog::sinks::rotating_file_sink; diff --git a/tests/includes.h b/tests/includes.h index 00253439..0a340d7c 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -28,6 +28,7 @@ #include "spdlog/details/fmt_helper.h" #include "spdlog/mdc.h" #include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/ansicolor_file_sink.h" #include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/ostream_sink.h" diff --git a/tests/test_file_logging.cpp b/tests/test_file_logging.cpp index 14135178..86f95dda 100644 --- a/tests/test_file_logging.cpp +++ b/tests/test_file_logging.cpp @@ -45,6 +45,33 @@ TEST_CASE("flush_on", "[flush_on]") { default_eol, default_eol, default_eol)); } +TEST_CASE("ansicolor_file_logger", "[ansicolor_logger]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + // auto logger = spdlog::details::make_unique(filename); + auto logger = spdlog::create("logger", filename); + logger->set_pattern("[%^+++%$] %v"); + + auto sink = + std::dynamic_pointer_cast(logger->sinks().front()); + sink->set_color(spdlog::level::info, spdlog::details::ansicolors::blue); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + logger->flush(); + require_message_count(SIMPLE_LOG, 2); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == + spdlog::fmt_lib::format( + "[{ansi_blue}+++{ansi_reset}] Test message 1{eol}" + "[{ansi_blue}+++{ansi_reset}] Test message 2{eol}", + spdlog::fmt_lib::arg("ansi_blue", spdlog::details::ansicolors::blue), + spdlog::fmt_lib::arg("ansi_reset", spdlog::details::ansicolors::reset), + spdlog::fmt_lib::arg("eol", default_eol))); +} + TEST_CASE("rotating_file_logger1", "[rotating_logger]") { prepare_logdir(); size_t max_size = 1024 * 10;