diff --git a/README.md b/README.md index 3aeceb75..e49a5e0d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/ * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. * Multi/Single threaded loggers. * Various log targets: - * Rotating log files. + * Rotating log files (ANSI colors supported). * Daily log files. * Console logging (colors supported). * syslog. @@ -129,6 +129,18 @@ void rotating_example() } ``` +--- +#### ANSI-colored rotating files +```c++ +#include "spdlog/sinks/ansicolor_rotating_file_sink.h" +void ansicolor_rotating_example() { + // Create an ANSI-colored file rotating logger with 10 MB size max and 3 rotated files. + auto max_size = 1048576 * 10; + auto max_files = 3; + auto logger = spdlog::ansicolor_rotating_logger_mt("ansi_logger", "logs/ansicolor_rotating.txt", max_size, max_files); +} +``` + --- #### Daily files ```c++ diff --git a/example/example.cpp b/example/example.cpp index d4cfa1e2..3ac56105 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -11,6 +11,7 @@ void load_levels_example(); void stdout_logger_example(); void basic_example(); void rotating_example(); +void ansicolor_rotating_example(); void daily_example(); void callback_example(); void async_example(); @@ -71,6 +72,7 @@ int main(int, char *[]) { stdout_logger_example(); basic_example(); rotating_example(); + ansicolor_rotating_example(); daily_example(); callback_example(); async_example(); @@ -128,6 +130,13 @@ void rotating_example() { spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); } +#include "spdlog/sinks/ansicolor_rotating_file_sink.h" +void ansicolor_rotating_example() { + // Create an ANSI-colored file rotating logger with 10mb size max and 3 rotated files. + auto ansicolor_rotating_logger = spdlog::ansicolor_rotating_logger_mt( + "ansi_logger", "logs/ansicolor_rotating.txt", 1048576 * 10, 3); +} + #include "spdlog/sinks/daily_file_sink.h" void daily_example() { // Create a daily logger - a new file is created every day on 2:30am. diff --git a/include/spdlog/sinks/ansicolor_rotating_file_sink-inl.h b/include/spdlog/sinks/ansicolor_rotating_file_sink-inl.h new file mode 100644 index 00000000..4068fb3e --- /dev/null +++ b/include/spdlog/sinks/ansicolor_rotating_file_sink-inl.h @@ -0,0 +1,67 @@ +// 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 +#include +#include + +#include + +namespace spdlog { +namespace sinks { + +template +SPDLOG_INLINE ansicolor_rotating_file_sink::ansicolor_rotating_file_sink( + filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open, + const file_event_handlers &event_handlers) + : file_{base_filename, max_size, max_files, rotate_on_open, event_handlers} {} + +template +SPDLOG_INLINE void ansicolor_rotating_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); +} + +// calc filename according to index and file extension if exists. +// e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". +template +SPDLOG_INLINE filename_t +ansicolor_rotating_file_sink::calc_filename(const filename_t &filename, std::size_t index) { + return details::rotating_file::calc_filename(filename, index); +} + +template +SPDLOG_INLINE filename_t ansicolor_rotating_file_sink::filename() { + return file_.filename(); +} + +template +SPDLOG_INLINE void ansicolor_rotating_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_.write(range); + } +} + +template +SPDLOG_INLINE void ansicolor_rotating_file_sink::flush_() { + file_.flush(); +} + +} // namespace sinks +} // namespace spdlog diff --git a/include/spdlog/sinks/ansicolor_rotating_file_sink.h b/include/spdlog/sinks/ansicolor_rotating_file_sink.h new file mode 100644 index 00000000..07d5b79e --- /dev/null +++ b/include/spdlog/sinks/ansicolor_rotating_file_sink.h @@ -0,0 +1,81 @@ +// 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 +#include + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size, with ansi colors. +// +template +class ansicolor_rotating_file_sink final : public base_sink { +public: + ansicolor_rotating_file_sink(filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}); + + void set_color(level::level_enum color_level, string_view_t color); + + static filename_t calc_filename(const filename_t &filename, std::size_t index); + filename_t filename(); + +protected: + void sink_it_(const details::log_msg &msg) override; + void flush_() override; + +private: + details::ansicolors colors_; + details::rotating_file file_; +}; + +using ansicolor_rotating_file_sink_mt = ansicolor_rotating_file_sink; +using ansicolor_rotating_file_sink_st = ansicolor_rotating_file_sink; + +} // namespace sinks + +// +// factory functions +// + +template +inline std::shared_ptr ansicolor_rotating_logger_mt( + const std::string &logger_name, + const filename_t &filename, + size_t max_file_size, + size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); +} + +template +inline std::shared_ptr ansicolor_rotating_logger_st( + const std::string &logger_name, + const filename_t &filename, + size_t max_file_size, + size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); +} +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY + #include "ansicolor_rotating_file_sink-inl.h" +#endif diff --git a/src/file_sinks.cpp b/src/file_sinks.cpp index 04cb6c10..67675b67 100644 --- a/src/file_sinks.cpp +++ b/src/file_sinks.cpp @@ -18,3 +18,7 @@ template class SPDLOG_API spdlog::sinks::basic_file_sink template class SPDLOG_API spdlog::sinks::rotating_file_sink; template class SPDLOG_API spdlog::sinks::rotating_file_sink; + +#include +template class SPDLOG_API spdlog::sinks::ansicolor_rotating_file_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_rotating_file_sink; diff --git a/tests/includes.h b/tests/includes.h index 14e988b9..00253439 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -32,6 +32,7 @@ #include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/ostream_sink.h" #include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/ansicolor_rotating_file_sink.h" #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/msvc_sink.h" #include "spdlog/pattern_formatter.h" diff --git a/tests/test_file_logging.cpp b/tests/test_file_logging.cpp index ac378b5c..14135178 100644 --- a/tests/test_file_logging.cpp +++ b/tests/test_file_logging.cpp @@ -101,3 +101,60 @@ TEST_CASE("rotating_file_logger3", "[rotating_logger]") { REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), spdlog::spdlog_ex); } + +TEST_CASE("ansicolor_rotating_file_logger1", "[ansicolor_rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto logger = spdlog::ansicolor_rotating_logger_mt("logger", basename, max_size, 0); + + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + require_message_count(ROTATING_LOG, 10); +} + +TEST_CASE("ansicolor_rotating_file_logger2", "[ansicolor_rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + + { + // make an initial logger to create the first output file + auto logger = spdlog::ansicolor_rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + // drop causes the logger destructor to be called, which is required so the + // next logger can rename the first output file. + spdlog::drop(logger->name()); + } + + auto logger = spdlog::ansicolor_rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + + require_message_count(ROTATING_LOG, 10); + + for (int i = 0; i < 1000; i++) { + logger->info("Test message {}", i); + } + + logger->flush(); + REQUIRE(get_filesize(ROTATING_LOG) <= max_size); + REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size); +} + +// test that passing max_size=0 throws +TEST_CASE("ansicolor_rotating_file_logger3", "[ansicolor_rotating_logger]") { + prepare_logdir(); + size_t max_size = 0; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + REQUIRE_THROWS_AS(spdlog::ansicolor_rotating_logger_mt("logger", basename, max_size, 0), + spdlog::spdlog_ex); +}