diff --git a/include/spdlog/details/file_helper-inl.h b/include/spdlog/details/file_helper-inl.h index d4528711..c50787dd 100644 --- a/include/spdlog/details/file_helper-inl.h +++ b/include/spdlog/details/file_helper-inl.h @@ -34,7 +34,7 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) close(); filename_ = fname; - auto *mode = SPDLOG_FILENAME_T("ab"); + auto *mode = SPDLOG_FILENAME_T("a+b"); auto *trunc_mode = SPDLOG_FILENAME_T("wb"); if (event_handlers_.before_open) @@ -133,6 +133,11 @@ SPDLOG_INLINE const filename_t &file_helper::filename() const return filename_; } +SPDLOG_INLINE int file_helper::fileno() const +{ + return ::fileno(fd_); +} + // // return file path and its extension: // diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index 0f5988b9..47f2d347 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -30,6 +30,7 @@ public: void write(const memory_buf_t &buf); size_t size() const; const filename_t &filename() const; + int fileno() const; // // return file path and its extension: diff --git a/include/spdlog/sinks/mapped_file_sink.h b/include/spdlog/sinks/mapped_file_sink.h new file mode 100644 index 00000000..e460ccc9 --- /dev/null +++ b/include/spdlog/sinks/mapped_file_sink.h @@ -0,0 +1,103 @@ +#include "spdlog/common.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +template +class mapped_file_sink final : public base_sink +{ +private: + static constexpr std::size_t mem_buffer_size = 2 * 1024 * 1024; + + struct mmap_deleter + { + void operator()(char *p) const + { + if (munmap(p, mem_buffer_size) != 0) + throw_spdlog_ex("munmap failed", errno); + } + }; + + std::string path; + std::size_t file_offset = 0; + std::size_t mem_offset = 0; + std::unique_ptr buffer; + details::file_helper file_helper_; + +public: + explicit mapped_file_sink(const filename_t &filename, bool truncate = false, const file_event_handlers &event_handlers = {}) + : file_helper_{event_handlers} + + { + file_helper_.open(filename, truncate); + map_buffer(); + } + + mapped_file_sink(const mapped_file_sink &) = delete; + mapped_file_sink(mapped_file_sink &&) noexcept = default; + mapped_file_sink &operator=(const mapped_file_sink &) = delete; + mapped_file_sink &operator=(mapped_file_sink &&) noexcept = default; + + ~mapped_file_sink() override + { + ftruncate(file_helper_.fileno(), file_offset + mem_offset); + } + +protected: + void map_buffer() + { + ftruncate(file_helper_.fileno(), file_offset + mem_buffer_size); + void *addr = mmap(nullptr, mem_buffer_size, PROT_WRITE, MAP_SHARED, file_helper_.fileno(), file_offset); + if (addr == MAP_FAILED) + throw_spdlog_ex("mmap failed", errno); + memset(addr, 0, mem_buffer_size); + buffer.reset(static_cast(addr)); + mem_offset = 0; + } + + void sink_it_(const details::log_msg &msg) override + { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + size_t written = 0; + while (written < formatted.size()) + { + auto to_write = std::min(formatted.size() - written, mem_buffer_size - mem_offset); + std::memcpy(buffer.get() + mem_offset, formatted.data() + written, to_write); + written += to_write; + mem_offset += to_write; + if (mem_offset == mem_buffer_size) + { + file_offset += mem_buffer_size; + map_buffer(); + } + } + } + void flush_() override + { + if (msync(buffer.get(), mem_buffer_size, MS_SYNC) != 0) + throw_spdlog_ex("msync failed", errno); + } +}; + +using mapped_file_sink_mt = mapped_file_sink; +using mapped_file_sink_st = mapped_file_sink; + +} // namespace sinks + +} // namespace spdlog \ No newline at end of file diff --git a/tests/includes.h b/tests/includes.h index 100984cc..9fc7541c 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -32,5 +32,6 @@ #include "spdlog/sinks/ostream_sink.h" #include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/mapped_file_sink.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 1c7a1853..63f9f562 100644 --- a/tests/test_file_logging.cpp +++ b/tests/test_file_logging.cpp @@ -2,9 +2,11 @@ * This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE */ #include "includes.h" +#include "spdlog/spdlog.h" #define SIMPLE_LOG "test_logs/simple_log" #define ROTATING_LOG "test_logs/rotating_log" +#define MAPPED_LOG "test_logs/mapped_log" TEST_CASE("simple_file_logger", "[simple_logger]]") { @@ -107,3 +109,22 @@ TEST_CASE("rotating_file_logger3", "[rotating_logger]]") spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), spdlog::spdlog_ex); } + +TEST_CASE("mapped_file_logger", "[mapped_logger]]") +{ + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(MAPPED_LOG); + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + logger->flush(); + spdlog::shutdown(); + logger.reset(); + require_message_count(MAPPED_LOG, 2); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(MAPPED_LOG) == spdlog::fmt_lib::format("Test message 1{}Test message 2{}", default_eol, default_eol)); +}