From a5fe961baee484d6e4803bb7cbe5b81416fe3dc4 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Wed, 17 Jul 2024 17:24:03 +0800 Subject: [PATCH] 27 add logmanager for open source version (#41) * add LogManager --- BUILD | 22 +- CMakeLists.txt | 6 +- MODULE.bazel | 2 +- copts.bzl | 4 +- src/babylon/BUILD | 2 +- src/babylon/anyflow/BUILD | 2 +- src/babylon/anyflow/closure.hpp | 2 +- src/babylon/application_context.cpp | 2 +- src/babylon/application_context.h | 4 +- src/babylon/logging/BUILD | 82 +++++- src/babylon/logging/async_file_appender.cpp | 251 ++++------------ src/babylon/logging/async_file_appender.h | 156 ++-------- src/babylon/logging/async_log_stream.cpp | 58 ++++ src/babylon/logging/async_log_stream.h | 56 ++++ src/babylon/logging/file_object.cpp | 33 +++ src/babylon/logging/file_object.h | 57 ++++ src/babylon/logging/interface.cpp | 38 +-- src/babylon/logging/interface.h | 73 +---- src/babylon/logging/log_entry.cpp | 116 ++++++++ src/babylon/logging/log_entry.h | 104 +++++++ src/babylon/logging/log_severity.cpp | 12 + src/babylon/logging/log_severity.h | 60 ++++ src/babylon/logging/log_stream.cpp | 67 +++++ src/babylon/logging/log_stream.h | 245 +++++++++++++++- src/babylon/logging/log_stream.hpp | 119 -------- src/babylon/logging/logger.cpp | 202 +++++++++++++ src/babylon/logging/logger.h | 142 +++++++++ src/babylon/logging/rolling_file_object.cpp | 179 ++++++++++++ src/babylon/logging/rolling_file_object.h | 63 ++++ src/babylon/mlock.cpp | 2 +- src/babylon/serialization/BUILD | 2 +- src/babylon/type_traits.h | 2 +- test/concurrent/test_bounded_queue.cpp | 2 +- test/logging/BUILD | 40 +++ test/logging/test_async_file_appender.cpp | 49 ++-- test/logging/test_async_log_stream.cpp | 71 +++++ test/logging/test_custom_default_provider.cpp | 1 + test/logging/test_interface.cpp | 8 +- test/logging/test_log_entry.cpp | 88 ++++++ test/logging/test_log_stream.cpp | 10 + test/logging/test_logger.cpp | 272 ++++++++++++++++++ test/logging/test_rolling_file_object.cpp | 82 ++++++ test/serialization/test_aggregate.cpp | 2 +- test/serialization/test_array.cpp | 2 +- test/serialization/test_list.cpp | 2 +- test/serialization/test_map.cpp | 2 +- test/serialization/test_message.cpp | 2 +- test/serialization/test_set.cpp | 2 +- test/serialization/test_shared_ptr.cpp | 2 +- test/serialization/test_unique_ptr.cpp | 2 +- test/serialization/test_vector.cpp | 2 +- 51 files changed, 2196 insertions(+), 610 deletions(-) create mode 100644 src/babylon/logging/async_log_stream.cpp create mode 100644 src/babylon/logging/async_log_stream.h create mode 100644 src/babylon/logging/file_object.cpp create mode 100644 src/babylon/logging/file_object.h create mode 100644 src/babylon/logging/log_entry.cpp create mode 100644 src/babylon/logging/log_entry.h create mode 100644 src/babylon/logging/log_severity.cpp create mode 100644 src/babylon/logging/log_severity.h delete mode 100644 src/babylon/logging/log_stream.hpp create mode 100644 src/babylon/logging/logger.cpp create mode 100644 src/babylon/logging/logger.h create mode 100644 src/babylon/logging/rolling_file_object.cpp create mode 100644 src/babylon/logging/rolling_file_object.h create mode 100644 test/logging/test_async_log_stream.cpp create mode 100644 test/logging/test_log_entry.cpp create mode 100644 test/logging/test_logger.cpp create mode 100644 test/logging/test_rolling_file_object.cpp diff --git a/BUILD b/BUILD index 016d61c1..2fdafab0 100644 --- a/BUILD +++ b/BUILD @@ -130,9 +130,19 @@ alias( actual = '//src/babylon/logging:async_file_appender', ) +alias( + name = 'logging_async_log_stream', + actual = '//src/babylon/logging:async_log_stream', +) + alias( name = 'logging_interface', - actual = '//src/babylon/logging:interface', + actual = '//src/babylon/logging:logger', +) + +alias( + name = 'logging_log_entry', + actual = '//src/babylon/logging:log_entry', ) alias( @@ -140,6 +150,16 @@ alias( actual = '//src/babylon/logging:log_stream', ) +alias( + name = 'logging_logger', + actual = '//src/babylon/logging:logger', +) + +alias( + name = 'logging_rolling_file_object', + actual = '//src/babylon/logging:rolling_file_object', +) + alias( name = 'move_only_function', actual = '//src/babylon:move_only_function', diff --git a/CMakeLists.txt b/CMakeLists.txt index e240bc9a..bb2cb36d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(babylon VERSION 1.2.3) +project(babylon VERSION 1.3.0) include(CTest) # for BUILD_TESTING option include(CMakePackageConfigHelpers) # for write_basic_package_version_file @@ -99,6 +99,10 @@ if(BUILD_TESTING AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto" PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(babylon_test_proto PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + set_source_files_properties( + "${CMAKE_CURRENT_SOURCE_DIR}/test/logging/test_logger.cpp" + PROPERTIES COMPILE_FLAGS "-fno-access-control") + foreach(SRC ${BABYLON_TEST_SRCS}) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}" "" TARGET_NAME ${SRC}) string(REPLACE "/" "_" TARGET_NAME ${TARGET_NAME}) diff --git a/MODULE.bazel b/MODULE.bazel index 58f503c4..8a59e9bb 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = 'babylon', - version = '1.2.3', + version = '1.3.0', compatibility_level = 1, ) diff --git a/copts.bzl b/copts.bzl index 542e8b5f..a7392423 100644 --- a/copts.bzl +++ b/copts.bzl @@ -1,7 +1,7 @@ BABYLON_GCC_COPTS = ['-Wall', '-Wextra'] BABYLON_CLANG_COPTS = ['-faligned-new', '-Weverything', '-Wno-unknown-warning-option', # 不保持老版本c++语法兼容 - '-Wno-c++98-compat', '-Wno-c++98-compat-pedantic', + '-Wno-c++98-compat-pedantic', '-Wno-c99-designator', # Boost Preprocessor中大量使用 '-Wno-disabled-macro-expansion', # 未定义宏默认为0作为一个惯例特性保留使用 @@ -21,7 +21,7 @@ BABYLON_CLANG_COPTS = ['-faligned-new', '-Weverything', '-Wno-unknown-warning-op # TODO(lijiang01): 逐步梳理清除 '-Wno-old-style-cast', '-Wno-shadow-field', '-Wno-exit-time-destructors', '-Wno-sign-conversion', - '-Wno-c++20-designator', '-Wno-shadow-field-in-constructor', '-Wno-gnu-anonymous-struct', '-Wno-nested-anon-types', + '-Wno-shadow-field-in-constructor', '-Wno-gnu-anonymous-struct', '-Wno-nested-anon-types', '-Wno-shadow-uncaptured-local', '-Wno-weak-vtables', '-Wno-float-conversion', '-Wno-switch-enum', '-Wno-shadow', '-Wno-array-bounds-pointer-arithmetic', '-Wno-cast-align', '-Wno-vla-extension', '-Wno-unneeded-member-function', '-Wno-deprecated-declarations'] diff --git a/src/babylon/BUILD b/src/babylon/BUILD index 1547477b..346a25ee 100644 --- a/src/babylon/BUILD +++ b/src/babylon/BUILD @@ -112,7 +112,7 @@ cc_library( strip_include_prefix = '//src', deps = [ ':string_view', - '//src/babylon/logging:interface', + '//src/babylon/logging:logger', '@com_google_absl//absl/strings', ], ) diff --git a/src/babylon/anyflow/BUILD b/src/babylon/anyflow/BUILD index 307a68fe..82051ac6 100644 --- a/src/babylon/anyflow/BUILD +++ b/src/babylon/anyflow/BUILD @@ -28,7 +28,7 @@ cc_library( '//src/babylon:any', '//src/babylon/concurrent:transient_topic', '//src/babylon:executor', - '//src/babylon/logging:interface', + '//src/babylon/logging:logger', '//src/babylon:move_only_function', '//src/babylon/reusable:manager', '//:boost.preprocessor', diff --git a/src/babylon/anyflow/closure.hpp b/src/babylon/anyflow/closure.hpp index f4e5efba..5beba449 100644 --- a/src/babylon/anyflow/closure.hpp +++ b/src/babylon/anyflow/closure.hpp @@ -3,7 +3,7 @@ #include "babylon/anyflow/closure.h" #include "babylon/anyflow/executor.h" #include "babylon/future.h" -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" BABYLON_NAMESPACE_BEGIN namespace anyflow { diff --git a/src/babylon/application_context.cpp b/src/babylon/application_context.cpp index a23f1c09..ed7129ae 100644 --- a/src/babylon/application_context.cpp +++ b/src/babylon/application_context.cpp @@ -1,6 +1,6 @@ #include "babylon/application_context.h" -#include "babylon/logging/interface.h" // BABYLON_LOG +#include "babylon/logging/logger.h" // BABYLON_LOG BABYLON_NAMESPACE_BEGIN diff --git a/src/babylon/application_context.h b/src/babylon/application_context.h index ce61b8e3..0f578dc6 100644 --- a/src/babylon/application_context.h +++ b/src/babylon/application_context.h @@ -1,7 +1,7 @@ #pragma once -#include "babylon/any.h" // babylon::Any -#include "babylon/logging/interface.h" // BABYLON_LOG +#include "babylon/any.h" // babylon::Any +#include "babylon/logging/logger.h" // BABYLON_LOG // clang-format off #include BABYLON_EXTERNAL(absl/container/flat_hash_map.h) // absl::flat_hash_map diff --git a/src/babylon/logging/BUILD b/src/babylon/logging/BUILD index ea60bdf4..8546eeb1 100644 --- a/src/babylon/logging/BUILD +++ b/src/babylon/logging/BUILD @@ -7,9 +7,9 @@ load('//:copts.bzl', 'BABYLON_COPTS') cc_library( name = 'logging', deps = [ - 'async_file_appender', - 'interface', - 'log_stream', + 'async_log_stream', + 'logger', + 'rolling_file_object', ] ) @@ -21,31 +21,97 @@ cc_library( includes = ['//src'], strip_include_prefix = '//src', deps = [ + ':file_object', + ':log_entry', '//src/babylon/reusable:page_allocator', ], ) cc_library( - name = 'interface', - srcs = ['interface.cpp'], - hdrs = ['interface.h'], + name = 'async_log_stream', + srcs = ['async_log_stream.cpp'], + hdrs = ['async_log_stream.h'], copts = BABYLON_COPTS, includes = ['//src'], strip_include_prefix = '//src', deps = [ - ':log_stream', + ':async_file_appender', + ':logger', + ], +) + +cc_library( + name = 'file_object', + srcs = ['file_object.cpp'], + hdrs = ['file_object.h'], + copts = BABYLON_COPTS, + strip_include_prefix = '//src', + deps = [ + '//src/babylon:environment', + ], +) + +cc_library( + name = 'log_entry', + srcs = ['log_entry.cpp'], + hdrs = ['log_entry.h'], + copts = BABYLON_COPTS, + strip_include_prefix = '//src', + deps = [ + '//src/babylon/reusable:page_allocator', + ], +) + +cc_library( + name = 'log_severity', + srcs = ['log_severity.cpp'], + hdrs = ['log_severity.h'], + copts = BABYLON_COPTS, + strip_include_prefix = '//src', + deps = [ + '//src/babylon:string_view', ], ) cc_library( name = 'log_stream', srcs = ['log_stream.cpp'], - hdrs = ['log_stream.h', 'log_stream.hpp'], + hdrs = ['log_stream.h'], copts = BABYLON_COPTS, includes = ['//src'], strip_include_prefix = '//src', deps = [ + ':log_severity', + '//src/babylon:time', '//src/babylon:type_traits', '@com_google_absl//absl/strings:str_format', ], ) + +cc_library( + name = 'logger', + srcs = ['logger.cpp', 'interface.cpp'], + hdrs = ['logger.h', 'interface.h'], + copts = BABYLON_COPTS, + strip_include_prefix = '//src', + deps = [ + ':log_stream', + '//src/babylon/concurrent:thread_local', + '//src/babylon/concurrent:transient_hash_table', + '//src/babylon/reusable:page_allocator', + ], +) + +cc_library( + name = 'rolling_file_object', + srcs = ['rolling_file_object.cpp'], + hdrs = ['rolling_file_object.h'], + copts = BABYLON_COPTS, + strip_include_prefix = '//src', + deps = [ + ':file_object', + '//src/babylon:string_view', + '//src/babylon:time', + '@com_google_absl//absl/time', + ], +) diff --git a/src/babylon/logging/async_file_appender.cpp b/src/babylon/logging/async_file_appender.cpp index 63313828..ca41bcb5 100644 --- a/src/babylon/logging/async_file_appender.cpp +++ b/src/babylon/logging/async_file_appender.cpp @@ -1,79 +1,10 @@ #include "babylon/logging/async_file_appender.h" -BABYLON_NAMESPACE_BEGIN - -//////////////////////////////////////////////////////////////////////////////// -// FileObject begin -FileObject::~FileObject() noexcept {} -// FileObject end -//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include -//////////////////////////////////////////////////////////////////////////////// -// StderrFileObject begin -StderrFileObject& StderrFileObject::instance() noexcept { - static StderrFileObject object; - return object; -} - -int StderrFileObject::check_and_get_file_descriptor() noexcept { - return STDERR_FILENO; -} -// StderrFileObject end -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// LogStreamBuffer begin -int LogStreamBuffer::overflow(int ch) noexcept { - // 先同步字节数 - sync(); - // 分配一个新页,设置到缓冲区 - auto page = reinterpret_cast(_page_allocator->allocate()); - if (ABSL_PREDICT_FALSE(_pages == _pages_end)) { - overflow_page_table(); - } - *_pages++ = page; - _sync_point = page; - setp(page, page + _page_allocator->page_size()); - return sputc(ch); -} - -int LogStreamBuffer::sync() noexcept { - auto ptr = pptr(); - if (ptr > _sync_point) { - _log.size += ptr - _sync_point; - _sync_point = ptr; - } - return 0; -} - -void LogStreamBuffer::overflow_page_table() noexcept { - // 当前页表用尽,需要分配一个新的页表 - auto page_table = - reinterpret_cast(_page_allocator->allocate()); - page_table->next = nullptr; - // 计算新页表的起止点 - auto pages = page_table->pages; - auto pages_end = reinterpret_cast( - reinterpret_cast(page_table) + _page_allocator->page_size()); - // 如果当前页表是日志对象内联页表 - if (_pages == _log.pages + Log::INLINE_PAGE_CAPACITY) { - // 转存最后一个页指针到新页表内,腾出页表的单链表头 - // 并将新页表挂载到头上 - *pages++ = reinterpret_cast(_log.head); - _log.head = page_table; - } else { - // 从最后一个页表项推算出对应页表地址 - // 将新页表接在后面 - auto last_page_table = reinterpret_cast( - reinterpret_cast(_pages_end) - _page_allocator->page_size()); - last_page_table->next = page_table; - } - // 更新当前页表起止点 - _pages = pages; - _pages_end = pages_end; -} -// LogStreamBuffer end -//////////////////////////////////////////////////////////////////////////////// +BABYLON_NAMESPACE_BEGIN //////////////////////////////////////////////////////////////////////////////// // AsyncFileAppender begin @@ -81,10 +12,6 @@ AsyncFileAppender::~AsyncFileAppender() noexcept { close(); } -void AsyncFileAppender::set_file_object(FileObject& file_object) noexcept { - _file_object = &file_object; -} - void AsyncFileAppender::set_page_allocator( PageAllocator& page_allocator) noexcept { _page_allocator = &page_allocator; @@ -95,32 +22,36 @@ void AsyncFileAppender::set_queue_capacity(size_t queue_capacity) noexcept { } int AsyncFileAppender::initialize() noexcept { - // 根据页大小,计算出页表容量 - _extend_page_capacity = - (_page_allocator->page_size() - sizeof(Log::PageTable)) / sizeof(char*); _write_thread = ::std::thread(&AsyncFileAppender::keep_writing, this); return 0; } -void AsyncFileAppender::write(Log& log) noexcept { - _queue.push([&](Log& target) { - target = log; +void AsyncFileAppender::write(LogEntry& entry, FileObject* file) noexcept { + _queue.push([&](Item& target) { + target.entry = entry; + target.file = file; }); } -void AsyncFileAppender::discard(Log& log) noexcept { - thread_local ::std::vector<::iovec> writev_payloads; - thread_local ::std::vector pages_to_release; - writev_payloads.clear(); - pages_to_release.clear(); - collect_log_pages(log, writev_payloads, pages_to_release); - _page_allocator->deallocate(pages_to_release.data(), pages_to_release.size()); +void AsyncFileAppender::discard(LogEntry& entry) noexcept { + static thread_local ::std::vector iov; + static thread_local ::std::vector pages; + + entry.append_to_iovec(_page_allocator->page_size(), iov); + for (auto& one_iov : iov) { + pages.push_back(one_iov.iov_base); + } + + _page_allocator->deallocate(pages.data(), pages.size()); + pages.clear(); + iov.clear(); } int AsyncFileAppender::close() noexcept { if (_write_thread.joinable()) { - _queue.push([](Log& log) { - log.size = 0; + _queue.push([](Item& target) { + target.entry.size = 0; + target.file = nullptr; }); _write_thread.join(); } @@ -128,33 +59,38 @@ int AsyncFileAppender::close() noexcept { } void AsyncFileAppender::keep_writing() noexcept { - ::std::vector<::iovec> writev_payloads; - ::std::vector pages_to_release; auto stop = false; // 由于writev限制了单次最大长度到UIO_MAXIOV,更大的batch并无意义 // 另一方面ConcurrentBoundedQueue也限制了最大batch,合并取最严的限制 auto batch = ::std::min(UIO_MAXIOV, _queue.capacity()); do { - writev_payloads.clear(); - pages_to_release.clear(); - // 无论本轮是否有日志要写出,都检测一次文件描述符 // 便于长期无日志输出时依然保持日志滚动 - auto fd = _file_object->check_and_get_file_descriptor(); auto poped = _queue.try_pop_n( [&](Queue::Iterator iter, Queue::Iterator end) { while (iter < end) { - stop = - collect_log_pages(*iter++, writev_payloads, pages_to_release); + auto& item = *iter++; + if (ABSL_PREDICT_FALSE(item.entry.size == 0)) { + stop = true; + break; + } + auto& dest = destination(item.file); + item.entry.append_to_iovec(_page_allocator->page_size(), dest.iov); } }, batch); - if (writev_payloads.size() > 0) { - write_to_fd(writev_payloads, fd); - _page_allocator->deallocate(pages_to_release.data(), - pages_to_release.size()); + for (auto& dest : _destinations) { + auto result = dest.file->check_and_get_file_descriptor(); + auto fd = ::std::get<0>(result); + auto old_fd = ::std::get<1>(result); + if (old_fd >= 0) { + ::close(old_fd); + } + if (!dest.iov.empty()) { + write_use_plain_writev(dest, fd); + } } // 当一轮日志量过低时,拉长下一轮写入的周期 @@ -171,97 +107,36 @@ void AsyncFileAppender::keep_writing() noexcept { } while (!stop); } -bool AsyncFileAppender::collect_log_pages( - Log& log, ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept { - if (ABSL_PREDICT_FALSE(log.size == 0)) { - return true; +AsyncFileAppender::Destination& AsyncFileAppender::destination( + FileObject* file) noexcept { + auto index = file->index(); + if (index != SIZE_MAX) { + return _destinations[index]; } - auto page_size = _page_allocator->page_size(); - size_t data_page_num = (log.size + page_size - 1) / page_size; - // 页面数量超过了可内联规模,需要考虑扩展页表 - if (data_page_num > Log::INLINE_PAGE_CAPACITY) { - // 先收集内联的整页 - collect_full_pages(log.pages, Log::INLINE_PAGE_CAPACITY - 1, - writev_payloads, pages_to_release); - // 再收集扩展页单链表中的内容 - auto remain = log.size - ((Log::INLINE_PAGE_CAPACITY - 1) * page_size); - collect_log_pages(*log.head, remain, writev_payloads, pages_to_release); - return false; - } - - collect_pages(log.pages, log.size, writev_payloads, pages_to_release); - return false; -} - -void AsyncFileAppender::collect_log_pages( - Log::PageTable& extend_log, size_t size, ::std::vector<::iovec>& payloads, - ::std::vector& pages) noexcept { - auto extend_page_capacity = _extend_page_capacity; - auto extend_capacity = extend_page_capacity * _page_allocator->page_size(); - - auto remain = size; - auto extend_log_ptr = &extend_log; - // 剩余内容足够填满一个页表内的所有页,完整收集 - while (remain >= extend_capacity) { - collect_full_pages(extend_log_ptr->pages, extend_page_capacity, payloads, - pages); - pages.emplace_back(extend_log_ptr); - extend_log_ptr = extend_log_ptr->next; - remain -= extend_capacity; - } - // 收集尾部可能不完整的页表 - if (remain > 0) { - collect_pages(extend_log_ptr->pages, remain, payloads, pages); - pages.emplace_back(extend_log_ptr); - } + file->set_index(_destinations.size()); + _destinations.emplace_back(Destination {.file = file}); + return _destinations.back(); } -void AsyncFileAppender::collect_full_pages( - char** pages, size_t num, ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept { - auto page_size = _page_allocator->page_size(); - for (size_t i = 0; i < num; ++i) { - auto page = pages[i]; - writev_payloads.emplace_back( - ::iovec {.iov_base = page, .iov_len = page_size}); - pages_to_release.emplace_back(page); - } -} +void AsyncFileAppender::write_use_plain_writev(Destination& dest, + int fd) noexcept { + static thread_local ::std::vector pages; -void AsyncFileAppender::collect_pages( - char** pages, size_t byte_size, ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept { - auto page_size = _page_allocator->page_size(); - auto page_ptr = pages; - auto remain = byte_size; - // 先收集靠前的整页 - while (remain >= page_size) { - auto page = *page_ptr++; - writev_payloads.emplace_back( - ::iovec {.iov_base = page, .iov_len = page_size}); - pages_to_release.emplace_back(page); - remain -= page_size; - } - // 收集位于尾部的最后一个非整页 - if (remain > 0) { - auto page = *page_ptr; - writev_payloads.emplace_back(::iovec {.iov_base = page, .iov_len = remain}); - pages_to_release.emplace_back(page); + auto& iov = dest.iov; + for (auto iter = iov.begin(); iter < iov.end();) { + auto piov = &*iter; + auto size = ::std::min(IOV_MAX, iov.end() - iter); + for (size_t i = 0; i < size; ++i) { + pages.emplace_back(piov[i].iov_base); + } + ::writev(fd, piov, size); + iter += size; } -} -void AsyncFileAppender::write_to_fd(::std::vector<::iovec>& writev_payloads, - int fd) noexcept { - size_t begin_index = 0; - do { - auto size = - ::std::min(UIO_MAXIOV, writev_payloads.size() - begin_index); - auto written = ::writev(fd, &writev_payloads[begin_index], size); - (void)written; - begin_index += size; - } while (begin_index < writev_payloads.size()); + _page_allocator->deallocate(pages.data(), pages.size()); + pages.clear(); + iov.clear(); } // AsyncFileAppender end //////////////////////////////////////////////////////////////////////////////// diff --git a/src/babylon/logging/async_file_appender.h b/src/babylon/logging/async_file_appender.h index eb6349d0..b8567800 100644 --- a/src/babylon/logging/async_file_appender.h +++ b/src/babylon/logging/async_file_appender.h @@ -1,6 +1,8 @@ #pragma once #include "babylon/concurrent/bounded_queue.h" // babylon::ConcurrentBoundedQueue +#include "babylon/logging/file_object.h" // babylon::PageAllocator +#include "babylon/logging/log_entry.h" // babylon::LogEntry #include "babylon/reusable/page_allocator.h" // babylon::PageAllocator #include // ::iovec @@ -11,91 +13,6 @@ BABYLON_NAMESPACE_BEGIN -// 解耦异步日志机制中的文件操作和异步传输 -// 对接具体日志框架时,文件操作层需要适配对应框架机制 -// 内部完成截断/滚动/磁盘容量控制等功能 -// 将这部分独立出来有助于实现日志框架无关的异步传输层 -class FileObject { - public: - virtual ~FileObject() noexcept = 0; - - // 核心功能函数,异步传输层在每次需要写出日志时都会调用此函数 - // 并需要接口返回一个可用的文件描述符,最终日志会通过这个文件来输出 - // 如果需要进行文件滚动等操作,内部完成后返回最终可用的描述符 - virtual int check_and_get_file_descriptor() noexcept = 0; -}; - -// 指向标准错误流文件的包装 -class StderrFileObject : public FileObject { - public: - static StderrFileObject& instance() noexcept; - - virtual int check_and_get_file_descriptor() noexcept override; -}; - -// 表达一条日志的结构,实际内容分页存储管理 -// 由于需要做缓存行隔离,因此结构本身可以内联存储少量页指针 -// 当日志页超过可内联容量时,展开单链表组织的独立页表进行管理 -struct Log { - // 先计算缓存行对齐模式下日志结构的最大尺寸 - constexpr static size_t MAX_INLINE_SIZE = - BABYLON_CACHELINE_SIZE - - ((sizeof(Futex) + alignof(void*) - 1) & ~alignof(void*)); - // 根据尺寸算出可以支持多少内联页 - constexpr static size_t INLINE_PAGE_CAPACITY = - (MAX_INLINE_SIZE - sizeof(size_t)) / sizeof(void*); - - // 通过单链表组织的页表结构 - // 页表结构自身也采用页大小来分配,因此实际的页指针数组容量 - // 和页大小相关,需要动态确定,这里设置一个假的零容量 - struct PageTable { - PageTable* next; - char* pages[0]; - }; - - // 元信息只记录一个总长度,总长度确定后 - // 实际的页数目和组织方式也可以唯一确定 - size_t size; - - // 为了提升内联容量,页指针数组最后一个元素和页表头指针是复用的 - // 容量减一为了方便形成结构 - char* pages[INLINE_PAGE_CAPACITY - 1]; - PageTable* head; -}; - -// 支持渐进构造一行日志 -// 采用std::streambuf接口实现,便于集成到std::ostream格式化机制 -// -// 极简用法 -// LogStreamBuffer streambuf; -// streambuf.set_page_allocator(allocator); -// loop: -// streambuf.begin(); -// streambuf.sputn(..., sizeof(...)); -// streambuf.sputn(..., sizeof(...)); -// appender.write(streambuf.end()); -class LogStreamBuffer : public ::std::streambuf { - public: - inline void set_page_allocator(PageAllocator& page_allocator) noexcept; - - inline void begin() noexcept; - inline Log& end() noexcept; - - inline void begin(PageAllocator& page_allocator) noexcept; - - private: - virtual int overflow(int ch) noexcept override; - virtual int sync() noexcept override; - - void overflow_page_table() noexcept; - - Log _log; - PageAllocator* _page_allocator; - char** _pages; - char** _pages_end; - char* _sync_point; -}; - // 实现日志异步追加写入文件的组件 // 对外提供的写入接口将日志存入队列中即返回 // 之后由独立的异步线程完成到文件的写入动作 @@ -105,9 +22,9 @@ class AsyncFileAppender { ~AsyncFileAppender() noexcept; // 设置日志文件对象,内存池,以及队列容量等参数 - void set_file_object(FileObject& file_object) noexcept; void set_page_allocator(PageAllocator& page_allocator) noexcept; void set_queue_capacity(size_t queue_capacity) noexcept; + void set_direct_buffer_size(size_t size) noexcept; // 启动异步线程,进入工作状态 int initialize() noexcept; @@ -115,9 +32,9 @@ class AsyncFileAppender { // 获取内存池,日志对象需要在这个内存池上分配 inline PageAllocator& page_allocator() noexcept; // 提交一个构造好的日志对象 - void write(Log& log) noexcept; + void write(LogEntry& log, FileObject* file_object) noexcept; // 放弃一个构造好的日志对象 - void discard(Log& log) noexcept; + void discard(LogEntry& log) noexcept; // 返回当前待写入的日志对象数目 inline size_t pending_size() const noexcept; @@ -125,57 +42,34 @@ class AsyncFileAppender { int close() noexcept; private: - using Queue = ConcurrentBoundedQueue; + struct Item { + LogEntry entry; + FileObject* file; + }; + using Queue = ConcurrentBoundedQueue; - constexpr static size_t INLINE_PAGE_CAPACITY = Log::INLINE_PAGE_CAPACITY; + struct Destination { + FileObject* file {nullptr}; + ::std::vector iov {}; + }; + + static void writev_all(int fd, const ::std::vector& iov, + size_t bytes) noexcept; void keep_writing() noexcept; - bool collect_log_pages(Log& log, ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept; - void collect_log_pages(Log::PageTable& page_table, size_t size, - ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept; - void collect_pages(char** pages, size_t byte_size, - ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept; - void collect_full_pages(char** pages, size_t num, - ::std::vector<::iovec>& writev_payloads, - ::std::vector& pages_to_release) noexcept; - void write_to_fd(::std::vector<::iovec>& writev_payloads, int fd) noexcept; + Destination& destination(FileObject* object) noexcept; + + void write_use_plain_writev(Destination& dest, int fd) noexcept; Queue _queue {1024}; PageAllocator* _page_allocator {&SystemPageAllocator::instance()}; - FileObject* _file_object {&StderrFileObject::instance()}; - size_t _extend_page_capacity {0}; - size_t _backoff_us {0}; ::std::thread _write_thread; + size_t _backoff_us {0}; + ::std::vector _destinations; }; -inline void LogStreamBuffer::set_page_allocator( - PageAllocator& page_allocator) noexcept { - _page_allocator = &page_allocator; -} - -inline void LogStreamBuffer::begin() noexcept { - auto page = reinterpret_cast(_page_allocator->allocate()); - _log.size = 0; - _log.pages[0] = page; - _pages = _log.pages + 1; - _pages_end = _log.pages + Log::INLINE_PAGE_CAPACITY; - _sync_point = page; - setp(page, page + _page_allocator->page_size()); -} - -inline void LogStreamBuffer::begin(PageAllocator& page_allocator) noexcept { - set_page_allocator(page_allocator); - begin(); -} - -inline Log& LogStreamBuffer::end() noexcept { - sync(); - return _log; -} - +//////////////////////////////////////////////////////////////////////////////// +// AsyncFileAppender begin inline PageAllocator& AsyncFileAppender::page_allocator() noexcept { return *_page_allocator; } @@ -183,5 +77,7 @@ inline PageAllocator& AsyncFileAppender::page_allocator() noexcept { inline size_t AsyncFileAppender::pending_size() const noexcept { return _queue.size(); } +// AsyncFileAppender end +//////////////////////////////////////////////////////////////////////////////// BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/async_log_stream.cpp b/src/babylon/logging/async_log_stream.cpp new file mode 100644 index 00000000..cfe39a68 --- /dev/null +++ b/src/babylon/logging/async_log_stream.cpp @@ -0,0 +1,58 @@ +#include "babylon/logging/async_log_stream.h" + +#include "babylon/time.h" // localtime + +#include // __NR_gettid +#include // ::syscall + +BABYLON_NAMESPACE_BEGIN + +LoggerBuilder::Creator AsyncLogStream::creator( + AsyncFileAppender& appender, FileObject& file_object) noexcept { + return creator(appender, file_object, default_header_formatter); +} + +LoggerBuilder::Creator AsyncLogStream::creator( + AsyncFileAppender& appender, FileObject& file_object, + HeaderFormatter formatter) noexcept { + return [&, formatter]() { + return ::std::unique_ptr { + new AsyncLogStream {&appender, &file_object, formatter}, {}}; + }; +} + +void AsyncLogStream::default_header_formatter(AsyncLogStream& ls) noexcept { + auto now = ::absl::ToTimeval(::absl::Now()); + struct ::tm time_struct; + StringView severity_name = ls.severity(); + ::babylon::localtime(&now.tv_sec, &time_struct); + thread_local int tid = ::syscall(__NR_gettid); + ls.format("%.*s %d-%02d-%02d %02d:%02d:%02d.%06d %d %.*s:%d] ", + severity_name.size(), severity_name.data(), + time_struct.tm_year + 1900, time_struct.tm_mon + 1, + time_struct.tm_mday, time_struct.tm_hour, time_struct.tm_min, + time_struct.tm_sec, now.tv_usec, tid, ls.file().size(), + ls.file().data(), ls.line()); +} + +AsyncLogStream::AsyncLogStream(AsyncFileAppender* appender, + FileObject* file_object, + HeaderFormatter formatter) noexcept + : LogStream {_buffer}, + _appender {appender}, + _file_object {file_object}, + _formatter {formatter} {} + +void AsyncLogStream::do_begin() noexcept { + _buffer.set_page_allocator(_appender->page_allocator()); + _buffer.begin(); + _formatter(*this); +} + +void AsyncLogStream::do_end() noexcept { + _buffer.sputc('\n'); + auto& log_entry = _buffer.end(); + _appender->write(log_entry, _file_object); +} + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/async_log_stream.h b/src/babylon/logging/async_log_stream.h new file mode 100644 index 00000000..01e6fbc8 --- /dev/null +++ b/src/babylon/logging/async_log_stream.h @@ -0,0 +1,56 @@ +#pragma once + +#include "babylon/logging/async_file_appender.h" // AsyncFileAppender +#include "babylon/logging/file_object.h" // FileObject +#include "babylon/logging/log_stream.h" // LogStream +#include "babylon/logging/logger.h" // LoggerBuilder + +BABYLON_NAMESPACE_BEGIN + +// 使用AsyncFileAppender作为后端,实现异步刷写的LogStream +// 提供插件点实现对通用日志头格式的定制 +// 同时提供对接到LoggerBuilder的构建方法封装 +// +// 典型使用方法是 +// ... // 准备好AsyncFileAppender写入器 +// ... // 以及FileObject文件目标 +// ... // 准备好HeaderFormatter定制日志头格式 +// LoggerBuilder builder; +// builder.set_log_stream_creator(AsyncLogStream::creator(appender, file_object, +// formatter)); LoggerManager::instance().set_builder(::std::move(builder)); +class AsyncLogStream : public LogStream { + public: + using HeaderFormatter = ::std::function; + + // 封装对接LoggerBuilder的构造方法 + // 本身不持有appender和file_object的生命周期,需要在外部单独管理 + // formatter被拷贝构造后持有在构造方法内部 + static LoggerBuilder::Creator creator(AsyncFileAppender& appender, + FileObject& file_object) noexcept; + static LoggerBuilder::Creator creator(AsyncFileAppender& appender, + FileObject& file_object, + HeaderFormatter formatter) noexcept; + + AsyncLogStream() = delete; + AsyncLogStream(AsyncLogStream&&) = delete; + AsyncLogStream(const AsyncLogStream&) = delete; + AsyncLogStream& operator=(AsyncLogStream&&) = delete; + AsyncLogStream& operator=(const AsyncLogStream&) = delete; + virtual ~AsyncLogStream() noexcept override = default; + + private: + static void default_header_formatter(AsyncLogStream& ls) noexcept; + + AsyncLogStream(AsyncFileAppender* appender, FileObject* file_object, + HeaderFormatter formatter) noexcept; + + virtual void do_begin() noexcept override; + virtual void do_end() noexcept override; + + AsyncFileAppender* _appender {nullptr}; + FileObject* _file_object {nullptr}; + LogStreamBuffer _buffer; + HeaderFormatter _formatter; +}; + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/file_object.cpp b/src/babylon/logging/file_object.cpp new file mode 100644 index 00000000..6aa1b986 --- /dev/null +++ b/src/babylon/logging/file_object.cpp @@ -0,0 +1,33 @@ +#include "babylon/logging/file_object.h" + +#include // STDERR_FILENO + +BABYLON_NAMESPACE_BEGIN + +//////////////////////////////////////////////////////////////////////////////// +// FileObject::begin +void FileObject::set_index(size_t index) noexcept { + _index = index; +} + +size_t FileObject::index() const noexcept { + return _index; +} +// FileObject::end +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// StderrFileObject begin +StderrFileObject& StderrFileObject::instance() noexcept { + static StderrFileObject object; + return object; +} + +::std::tuple +StderrFileObject::check_and_get_file_descriptor() noexcept { + return ::std::tuple(STDERR_FILENO, -1); +} +// StderrFileObject end +//////////////////////////////////////////////////////////////////////////////// + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/file_object.h b/src/babylon/logging/file_object.h new file mode 100644 index 00000000..54585c8d --- /dev/null +++ b/src/babylon/logging/file_object.h @@ -0,0 +1,57 @@ +#pragma once + +#include "babylon/environment.h" + +#include // SIZE_MAX +#include // std::tuple + +BABYLON_NAMESPACE_BEGIN + +// 日志文件实体,用于解耦文件滚动管理和实际的读写动作 +// 实现内部封装截断/滚动/磁盘容量控制等功能 +// 对上层提供提供最终准备好的文件描述符作为分界面 +class AsyncFileAppender; +class FileObject { + public: + // 可以默认构造和移动,不可拷贝 + FileObject() noexcept = default; + FileObject(FileObject&&) noexcept = default; + FileObject(const FileObject&) = delete; + FileObject& operator=(FileObject&&) noexcept = default; + FileObject& operator=(const FileObject&) = delete; + virtual ~FileObject() noexcept = default; + + // 核心功能函数,上层在每次写出前需要调用此函数来获得文件操作符 + // 函数内部完成文件滚动检测等操作,返回最终准备好的描述符 + // 由于可能发生文件的滚动,返回值为新旧描述符(fd, old_fd)二元组 + // fd: + // >=0: 当前文件描述符,调用者后续写入通过此描述符发起 + // < 0: 发生异常无法打开文件 + // old_fd: + // >=0: 发生文件切换,返回之前的文件描述符 + // 一般由文件滚动引起,需要调用者执行关闭动作 + // 关闭前调用者可以进行最后的收尾写入等操作 + // < 0: 未发生文件切换 + virtual ::std::tuple check_and_get_file_descriptor() noexcept = 0; + + private: + // 由AsyncFileAppender内部使用,用于记录一些附加信息 + void set_index(size_t index) noexcept; + size_t index() const noexcept; + + size_t _index {SIZE_MAX}; + + friend class AsyncFileAppender; +}; + +// 指向标准错误流文件的包装 +class StderrFileObject : public FileObject { + public: + static StderrFileObject& instance() noexcept; + + private: + virtual ::std::tuple check_and_get_file_descriptor() noexcept + override; +}; + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/interface.cpp b/src/babylon/logging/interface.cpp index 72226390..0014f6d2 100644 --- a/src/babylon/logging/interface.cpp +++ b/src/babylon/logging/interface.cpp @@ -1,5 +1,7 @@ #include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" + #include // std::cerr #include // std::mutex @@ -25,35 +27,11 @@ class DefaultLogStreamProvider : public LogStreamProvider { public: virtual LogStream& stream(int severity, StringView file, int line) noexcept override { - struct S : public LogStream { -#if __clang__ || BABYLON_GCC_VERSION >= 50000 - inline S() noexcept : LogStream {*::std::cerr.rdbuf()} {} -#else // !__clang__ && BABYLON_GCC_VERSION < 50000 - inline S() noexcept : LogStream(*::std::cerr.rdbuf()) {} -#endif // !__clang__ && BABYLON_GCC_VERSION < 50000 - virtual void do_begin() noexcept override { - mutex().lock(); - operator<<(SEVERITY_NAME[severity]) - << '[' << file << ':' << line << "] "; - } - virtual void do_end() noexcept override { - operator<<('\n'); - mutex().unlock(); - } - static ::std::mutex& mutex() noexcept { - static ::std::mutex instance; - return instance; - } - - int severity; - StringView file; - int line; - }; - static thread_local S s_stream; - s_stream.severity = severity; - s_stream.file = file; - s_stream.line = line; - return s_stream; + static thread_local DefaultLogStream stream; + stream.set_severity(static_cast(severity)); + stream.set_file(file); + stream.set_line(line); + return stream; } }; #if __cplusplus < 201703L @@ -67,6 +45,8 @@ static DefaultLogStreamProvider s_default_provider; // LogInterface begin void LogInterface::set_min_severity(int severity) noexcept { _s_min_severity = severity; + LoggerManager::instance().get_root_logger().set_min_severity( + static_cast(severity)); } void LogInterface::set_provider( diff --git a/src/babylon/logging/interface.h b/src/babylon/logging/interface.h index e45e3d5d..806889aa 100644 --- a/src/babylon/logging/interface.h +++ b/src/babylon/logging/interface.h @@ -1,7 +1,8 @@ #pragma once -#include "babylon/logging/log_stream.h" // LogStream -#include "babylon/string_view.h" // StringView +#include "babylon/logging/log_severity.h" // LogSeverity +#include "babylon/logging/log_stream.h" // LogStream +#include "babylon/string_view.h" // StringView BABYLON_NAMESPACE_BEGIN @@ -30,11 +31,12 @@ class LogStreamProvider { // 用于设置和访问日志系统的接口层 class LogInterface { public: - static constexpr int SEVERITY_DEBUG = 0; - static constexpr int SEVERITY_INFO = 1; - static constexpr int SEVERITY_WARNING = 2; - static constexpr int SEVERITY_FATAL = 3; - static constexpr int SEVERITY_NUM = 4; + static constexpr int SEVERITY_DEBUG = static_cast(LogSeverity::DEBUG); + static constexpr int SEVERITY_INFO = static_cast(LogSeverity::INFO); + static constexpr int SEVERITY_WARNING = + static_cast(LogSeverity::WARNING); + static constexpr int SEVERITY_FATAL = static_cast(LogSeverity::FATAL); + static constexpr int SEVERITY_NUM = static_cast(LogSeverity::NUM); // 设置最低日志等级,默认为>=INFO级别 static void set_min_severity(int severity) noexcept; @@ -51,44 +53,6 @@ class LogInterface { static LogStreamProvider* _s_provider; }; -// 便于实现BABYLON_LOG宏的RAII控制器 -// 封装LogStreamProvider的使用 -class ScopedLogStream { - public: - ScopedLogStream(ScopedLogStream&&) = delete; - ScopedLogStream(const ScopedLogStream&) = delete; - inline ~ScopedLogStream() noexcept; - - template - inline ScopedLogStream(int severity, StringView file, int line, - Args&&... args) noexcept; - - inline LogStream& stream() noexcept; - - private: - LogStream& _stream; -}; - -// 通过&操作符将返回值转为void的工具类 -// 辅助BABYLON_LOG宏实现条件日志 -class Voidify { - public: - template - inline void operator&(T&&) noexcept {} -}; - -// 供内部使用的日志宏 -// 可以通过设置LogStreamProvider对接到不同的日志系统 -#define BABYLON_LOG(severity) \ - ::babylon::LogInterface::min_severity() > \ - ::babylon::LogInterface::SEVERITY_##severity \ - ? (void)0 \ - : ::babylon::Voidify() & \ - ::babylon::ScopedLogStream( \ - ::babylon::LogInterface::SEVERITY_##severity, __FILE__, \ - __LINE__) \ - .stream() - //////////////////////////////////////////////////////////////////////////////// // LogInterface begin inline int LogInterface::min_severity() noexcept { @@ -101,23 +65,4 @@ inline LogStreamProvider& LogInterface::provider() noexcept { // LogInterface end //////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// ScopedLogStream begin -inline ScopedLogStream::~ScopedLogStream() noexcept { - _stream.end(); -} - -template -inline ScopedLogStream::ScopedLogStream(int severity, StringView file, int line, - Args&&... args) noexcept - : _stream {LogInterface::provider().stream(severity, file, line)} { - _stream.begin(::std::forward(args)...); -} - -inline LogStream& ScopedLogStream::stream() noexcept { - return _stream; -} -// ScopedLogStream end -//////////////////////////////////////////////////////////////////////////////// - BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_entry.cpp b/src/babylon/logging/log_entry.cpp new file mode 100644 index 00000000..24e6852f --- /dev/null +++ b/src/babylon/logging/log_entry.cpp @@ -0,0 +1,116 @@ +#include "babylon/logging/log_entry.h" + +BABYLON_NAMESPACE_BEGIN + +//////////////////////////////////////////////////////////////////////////////// +// LogStreamBuffer begin +void LogEntry::append_to_iovec(size_t page_size, + ::std::vector& iov) noexcept { + auto full_inline_size = INLINE_PAGE_CAPACITY * page_size; + // 页面数量超过了可内联规模,需要考虑扩展页表 + if (size > full_inline_size) { + // 先收集内联的整页 + pages_append_to_iovec(pages, full_inline_size - page_size, page_size, iov); + // 再收集扩展页单链表中的内容 + page_table_append_to_iovec(*head, size - full_inline_size + page_size, + page_size, iov); + } else { + // 收集内联的非整页 + pages_append_to_iovec(pages, size, page_size, iov); + } +} + +void LogEntry::pages_append_to_iovec( + char** pages, size_t size, size_t page_size, + ::std::vector& iov) noexcept { + auto num = size / page_size; + // 先收集靠前的整页 + for (size_t i = 0; i < num; ++i) { + iov.emplace_back(::iovec {.iov_base = pages[i], .iov_len = page_size}); + size -= page_size; + } + // 收集位于尾部的最后一个非整页 + if (size > 0) { + iov.emplace_back(::iovec {.iov_base = pages[num], .iov_len = size}); + } +} + +void LogEntry::page_table_append_to_iovec( + PageTable& table, size_t size, size_t page_size, + ::std::vector& iov) noexcept { + auto full_table_size = + (page_size - sizeof(PageTable)) / sizeof(char*) * page_size; + + auto table_ptr = &table; + // 剩余内容足够填满一个页表内的所有页,完整收集 + while (size > full_table_size) { + pages_append_to_iovec(table_ptr->pages, full_table_size, page_size, iov); + iov.emplace_back(::iovec {.iov_base = table_ptr, .iov_len = 0}); + table_ptr = table_ptr->next; + size -= full_table_size; + } + + // 收集尾部可能不完整的页表 + if (size > 0) { + pages_append_to_iovec(table_ptr->pages, size, page_size, iov); + iov.emplace_back(::iovec {.iov_base = table_ptr, .iov_len = 0}); + } +} +// LogStreamBuffer end +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// LogStreamBuffer begin +int LogStreamBuffer::overflow(int ch) noexcept { + // 先同步字节数 + sync(); + // 分配一个新页,设置到缓冲区 + auto page = reinterpret_cast(_page_allocator->allocate()); + if (ABSL_PREDICT_FALSE(_pages == _pages_end)) { + overflow_page_table(); + } + *_pages++ = page; + _sync_point = page; + setp(page, page + _page_allocator->page_size()); + return sputc(ch); +} + +int LogStreamBuffer::sync() noexcept { + auto ptr = pptr(); + if (ptr > _sync_point) { + _log.size += ptr - _sync_point; + _sync_point = ptr; + } + return 0; +} + +void LogStreamBuffer::overflow_page_table() noexcept { + // 当前页表用尽,需要分配一个新的页表 + auto page_table = + reinterpret_cast(_page_allocator->allocate()); + page_table->next = nullptr; + // 计算新页表的起止点 + auto pages = page_table->pages; + auto pages_end = reinterpret_cast( + reinterpret_cast(page_table) + _page_allocator->page_size()); + // 如果当前页表是日志对象内联页表 + if (_pages == _log.pages + LogEntry::INLINE_PAGE_CAPACITY) { + // 转存最后一个页指针到新页表内,腾出页表的单链表头 + // 并将新页表挂载到头上 + *pages++ = reinterpret_cast(_log.head); + _log.head = page_table; + } else { + // 从最后一个页表项推算出对应页表地址 + // 将新页表接在后面 + auto last_page_table = reinterpret_cast( + reinterpret_cast(_pages_end) - _page_allocator->page_size()); + last_page_table->next = page_table; + } + // 更新当前页表起止点 + _pages = pages; + _pages_end = pages_end; +} +// LogStreamBuffer end +//////////////////////////////////////////////////////////////////////////////// + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_entry.h b/src/babylon/logging/log_entry.h new file mode 100644 index 00000000..3576b948 --- /dev/null +++ b/src/babylon/logging/log_entry.h @@ -0,0 +1,104 @@ +#pragma once + +#include "babylon/reusable/page_allocator.h" // babylon::PageAllocator + +#include // ::iovec + +BABYLON_NAMESPACE_BEGIN + +// 表达一条日志的结构,实际内容分页存储管理 +// 由于需要做缓存行隔离,因此结构本身可以内联存储少量页指针 +// 当日志页超过可内联容量时,展开单链表组织的独立页表进行管理 +class LogEntry { + public: + // 先计算缓存行对齐模式下日志结构的最大尺寸 + constexpr static size_t MAX_INLINE_SIZE = + BABYLON_CACHELINE_SIZE - + ((sizeof(Futex) + alignof(void*) - 1) & ~alignof(void*)); + // 根据尺寸算出可以支持多少内联页 + constexpr static size_t INLINE_PAGE_CAPACITY = + (MAX_INLINE_SIZE - sizeof(size_t)) / sizeof(void*); + + // 通过单链表组织的页表结构 + // 页表结构自身也采用页大小来分配,因此实际的页指针数组容量 + // 和页大小相关,需要动态确定,这里设置一个假的零容量 + struct PageTable { + PageTable* next; + char* pages[0]; + }; + + void append_to_iovec(size_t page_size, + ::std::vector& iov) noexcept; + + private: + void pages_append_to_iovec(char** pages, size_t size, size_t page_size, + ::std::vector& iov) noexcept; + void page_table_append_to_iovec(PageTable& page_table, size_t size, + size_t page_size, + ::std::vector& iov) noexcept; + + public: + // 元信息只记录一个总长度,总长度确定后 + // 实际的页数目和组织方式也可以唯一确定 + size_t size; + + // 为了提升内联容量,页指针数组最后一个元素和页表头指针是复用的 + // 容量减一为了方便形成结构 + char* pages[INLINE_PAGE_CAPACITY - 1]; + PageTable* head; +}; + +// 支持渐进式构造一条日志 +// 采用std::streambuf接口实现,便于集成到std::ostream格式化机制 +// +// 极简用法 +// LogStreamBuffer streambuf; +// streambuf.set_page_allocator(allocator); +// loop: +// streambuf.begin(); +// streambuf.sputn(..., sizeof(...)); +// streambuf.sputn(..., sizeof(...)); +// appender.write(streambuf.end(), file_object); +class LogStreamBuffer : public ::std::streambuf { + public: + inline void set_page_allocator(PageAllocator& page_allocator) noexcept; + + inline void begin() noexcept; + inline LogEntry& end() noexcept; + + private: + virtual int overflow(int ch) noexcept override; + virtual int sync() noexcept override; + + void overflow_page_table() noexcept; + + LogEntry _log; + PageAllocator* _page_allocator; + char** _pages; + char** _pages_end; + char* _sync_point; +}; + +//////////////////////////////////////////////////////////////////////////////// +// LogStreamBuffer begin +inline void LogStreamBuffer::set_page_allocator( + PageAllocator& page_allocator) noexcept { + _page_allocator = &page_allocator; +} + +inline void LogStreamBuffer::begin() noexcept { + _log.size = 0; + _pages = _log.pages; + _pages_end = _log.pages + LogEntry::INLINE_PAGE_CAPACITY; + _sync_point = nullptr; + setp(nullptr, nullptr); +} + +inline LogEntry& LogStreamBuffer::end() noexcept { + sync(); + return _log; +} +// LogStreamBuffer end +//////////////////////////////////////////////////////////////////////////////// + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_severity.cpp b/src/babylon/logging/log_severity.cpp new file mode 100644 index 00000000..ce7fde9c --- /dev/null +++ b/src/babylon/logging/log_severity.cpp @@ -0,0 +1,12 @@ +#include "babylon/logging/log_severity.h" + +BABYLON_NAMESPACE_BEGIN + +// 当库本身用-std=c++14编译时,强制产出的符号为常量符号 +// 会和使用者采用-std=c++17编译时产生的唯一符号发生重定义冲突 +// 这里对-std=c++14的情况改写为弱符号 +#if __cplusplus < 201703L +ABSL_ATTRIBUTE_WEAK constexpr StringView LogSeverity::names[LogSeverity::NUM]; +#endif // __cplusplus < 201703L + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_severity.h b/src/babylon/logging/log_severity.h new file mode 100644 index 00000000..5ae7f0cd --- /dev/null +++ b/src/babylon/logging/log_severity.h @@ -0,0 +1,60 @@ +#pragma once + +#include "babylon/string_view.h" // StringView + +BABYLON_NAMESPACE_BEGIN + +class LogSeverity { + public: + enum E { + DEBUG = 0, + INFO = 1, + WARNING = 2, + FATAL = 3, + + NUM = 4, + }; + + inline constexpr LogSeverity() noexcept = default; + inline constexpr LogSeverity(LogSeverity&&) noexcept = default; + inline constexpr LogSeverity(const LogSeverity&) noexcept = default; + inline constexpr LogSeverity& operator=(LogSeverity&&) noexcept = default; + inline constexpr LogSeverity& operator=(const LogSeverity&) noexcept = + default; + inline ~LogSeverity() noexcept = default; + + inline constexpr LogSeverity(int8_t value) noexcept; + + inline constexpr operator int8_t() const noexcept; + + inline constexpr operator StringView() const noexcept; + + private: + int8_t _value {DEBUG}; + + static constexpr StringView names[NUM] = { + [DEBUG] = "DEBUG", + [INFO] = "INFO", + [WARNING] = "WARNING", + [FATAL] = "FATAL", + }; +}; + +inline constexpr LogSeverity::LogSeverity(int8_t value) noexcept + : _value {value} {} + +inline constexpr LogSeverity::operator int8_t() const noexcept { + return _value; +} + +inline constexpr LogSeverity::operator StringView() const noexcept { + return names[_value]; +} + +template +inline ::std::basic_ostream& operator<<(::std::basic_ostream& os, + LogSeverity severity) noexcept { + return os << static_cast(severity); +} + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_stream.cpp b/src/babylon/logging/log_stream.cpp index 217f113c..8c33fcd9 100644 --- a/src/babylon/logging/log_stream.cpp +++ b/src/babylon/logging/log_stream.cpp @@ -1,8 +1,75 @@ #include "babylon/logging/log_stream.h" +#include "babylon/time.h" + +#include "absl/time/clock.h" // absl::Now + +#include // std::cerr + BABYLON_NAMESPACE_BEGIN +class NullLogStream::Buffer : public ::std::streambuf { + private: + virtual int overflow(int ch) noexcept override { + return ch; + } + virtual ::std::streamsize xsputn(const char*, + ::std::streamsize count) noexcept override { + return count; + } +}; + void LogStream::do_begin() noexcept {} void LogStream::do_end() noexcept {} +DefaultLogStream::DefaultLogStream() noexcept + : +#if __clang__ || BABYLON_GCC_VERSION >= 50000 + LogStream {*::std::cerr.rdbuf()} { +} +#else // !__clang__ && BABYLON_GCC_VERSION < 50000 + LogStream(*::std::cerr.rdbuf()) { +} +#endif // !__clang__ && BABYLON_GCC_VERSION < 50000 + +::std::mutex& DefaultLogStream::mutex() noexcept { + static ::std::mutex mutex; + return mutex; +} + +void DefaultLogStream::do_begin() noexcept { + auto now_us = ::absl::ToUnixMicros(::absl::Now()); + time_t now_s = now_us / 1000 / 1000; + time_t us = now_us % (1000 * 1000); + struct ::tm time_struct; + ::babylon::localtime(&now_s, &time_struct); + mutex().lock(); + (*this) << severity(); + format(" %d-%02d-%02d %02d:%02d:%02d.%06d %.*s:%d] ", + time_struct.tm_year + 1900, time_struct.tm_mon + 1, + time_struct.tm_mday, time_struct.tm_hour, time_struct.tm_min, + time_struct.tm_sec, us, file().size(), file().data(), line()); +} + +void DefaultLogStream::do_end() noexcept { + (*this) << '\n'; + mutex().unlock(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NullLogStream begin +NullLogStream::NullLogStream() noexcept + : +#if __clang__ || BABYLON_GCC_VERSION >= 50000 + LogStream {s_buffer} { +} +#else // !__clang__ && BABYLON_GCC_VERSION < 50000 + LogStream(s_buffer) { +} +#endif // !__clang__ && BABYLON_GCC_VERSION < 50000 + +NullLogStream::Buffer NullLogStream::s_buffer; +// NullLogStream end +//////////////////////////////////////////////////////////////////////////////// + BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_stream.h b/src/babylon/logging/log_stream.h index bb804987..3b571f6a 100644 --- a/src/babylon/logging/log_stream.h +++ b/src/babylon/logging/log_stream.h @@ -1,6 +1,7 @@ #pragma once -#include "babylon/type_traits.h" // BABYLON_DECLARE_MEMBER_INVOCABLE +#include "babylon/logging/log_severity.h" // LogSeverity +#include "babylon/type_traits.h" // BABYLON_DECLARE_MEMBER_INVOCABLE // clang-format off #include BABYLON_EXTERNAL(absl/strings/str_format.h) // absl::Format @@ -8,21 +9,23 @@ #include // ::*int*_t +#include // std::mutex + BABYLON_NAMESPACE_BEGIN // 典型的日志前端接口,用于流式格式化一行日志,并发送给日志框架 // 通过如下方式使用 -// ls.begin(args...); // 开启一个日志行,args作为日志头依次预先写入 -// ls << ...; // 增量写入其他内容 -// ls.end(); // 整体发送给日志框架 +// ls.begin(args...); // 开启一个日志行,args作为日志头依次预先写入 +// ls << ...; // 增量写入其他内容 +// ls.end(); // 整体发送给日志框架 // // 中途可以使用noflush挂起即 // ls.begin(args...); -// ls << ... << noflush; // 挂起 -// ls.end(); // 此时不发送给日志框架 -// ls.begin(args...); // 恢复挂起,且不再次打印日志头 -// ls << ...; // 继续增量写入其他内容 -// ls.end(); // 作为一条日志发送给日志框架 +// ls << ... << ::babylon::noflush; // 挂起 +// ls.end(); // 此时不发送给日志框架 +// ls.begin(args...); // 恢复挂起,且不再次打印日志头 +// ls << ...; // 继续增量写入其他内容 +// ls.end(); // 作为一条日志发送给日志框架 // // 除流式输出,也通过集成absl::Format,支持printf语法 // ls.begin(args...); @@ -39,6 +42,15 @@ class LogStream : protected ::std::ostream { inline LogStream(::std::streambuf& streambuf) noexcept; + inline void set_severity(LogSeverity severity) noexcept; + inline LogSeverity severity() const noexcept; + + inline void set_file(StringView file) noexcept; + inline StringView file() const noexcept; + + inline void set_line(int line) noexcept; + inline int line() const noexcept; + template inline LogStream& begin(const Args&... args) noexcept; inline LogStream& noflush() noexcept; @@ -68,7 +80,8 @@ class LogStream : protected ::std::ostream { // 支持基于std::ostream的流操作符 inline LogStream& operator<<( - ::std::ostream& (*function)(::std::ostream&)) noexcept; + ::std::ostream& (&function)(::std::ostream&)) noexcept; + inline LogStream& operator<<(LogStream& (&function)(LogStream&)) noexcept; private: // 支持absl::Format @@ -121,8 +134,216 @@ class LogStream : protected ::std::ostream { size_t _depth {0}; bool _noflush {false}; + LogSeverity _severity {LogSeverity::DEBUG}; + int _line {-1}; + StringView _file; }; -BABYLON_NAMESPACE_END +// 便于实现LOG宏的RAII控制器 +class ScopedLogStream { + public: + ScopedLogStream() = delete; + ScopedLogStream(ScopedLogStream&&) = delete; + ScopedLogStream(const ScopedLogStream&) = delete; + ScopedLogStream& operator=(ScopedLogStream&&) = delete; + ScopedLogStream& operator=(const ScopedLogStream&) = delete; + inline ~ScopedLogStream() noexcept; + + template + inline ScopedLogStream(LogStream& stream, Args&&... args) noexcept; + + inline LogStream& stream() noexcept; + + private: + LogStream& _stream; +}; + +class DefaultLogStream : public LogStream { + public: + DefaultLogStream() noexcept; + + private: + static ::std::mutex& mutex() noexcept; + + virtual void do_begin() noexcept override; + virtual void do_end() noexcept override; +}; + +class NullLogStream : public LogStream { + public: + NullLogStream() noexcept; + + private: + class Buffer; + static Buffer s_buffer; +}; + +//////////////////////////////////////////////////////////////////////////////// +// LogStream begin +inline void LogStream::set_severity(LogSeverity severity) noexcept { + _severity = severity; +} + +inline LogSeverity LogStream::severity() const noexcept { + return _severity; +} + +inline void LogStream::set_file(StringView file) noexcept { + _file = file; +} + +inline StringView LogStream::file() const noexcept { + return _file; +} + +inline void LogStream::set_line(int line) noexcept { + _line = line; +} -#include "babylon/logging/log_stream.hpp" +inline int LogStream::line() const noexcept { + return _line; +} + +template +inline LogStream& LogStream::begin(const Args&... args) noexcept { + if (ABSL_PREDICT_FALSE(++_depth != 1)) { + return *this; + } + if (!_noflush) { + do_begin(); + write_objects(args...); + } else { + _noflush = false; + } + return *this; +} + +inline LogStream& LogStream::noflush() noexcept { + if (ABSL_PREDICT_FALSE(_depth != 1)) { + return *this; + } + _noflush = true; + return *this; +} + +inline LogStream& LogStream::end() noexcept { + if (ABSL_PREDICT_FALSE(--_depth > 0)) { + return *this; + } + if (!_noflush) { + do_end(); + clear(); + } + return *this; +} + +inline LogStream& LogStream::write(const char* data, size_t size) noexcept { + rdbuf()->sputn(data, size); + return *this; +} + +inline LogStream& LogStream::write(char c) noexcept { + rdbuf()->sputc(c); + return *this; +} + +template +inline LogStream& LogStream::format(const ::absl::FormatSpec& format, + const Args&... args) noexcept { + return do_absl_format(this, format, args...); +} + +template < + typename T, typename... Args, + typename ::std::enable_if< + ::std::is_convertible::value, int>::type> +inline LogStream& LogStream::do_absl_format( + T* ls, const ::absl::FormatSpec& format, + const Args&... args) noexcept { + ::absl::Format(ls, format, args...); + return *ls; +} + +template < + typename T, typename... Args, + typename ::std::enable_if< + !::std::is_convertible::value, int>::type> +inline LogStream& LogStream::do_absl_format( + T* ls, const ::absl::FormatSpec& format, + const Args&... args) noexcept { + ::absl::Format(static_cast<::std::ostream*>(ls), format, args...); + return *ls; +} + +template ::value, int>::type> +inline LogStream& LogStream::operator<<(const T& object) noexcept { + return write_object(object); +} + +template ::value, int>::type> +inline LogStream& LogStream::operator<<(const T& object) noexcept { + *static_cast<::std::ostream*>(this) << object; + return *this; +} + +inline LogStream& LogStream::operator<<( + ::std::ostream& (&function)(::std::ostream&)) noexcept { + function(*this); + return *this; +} + +inline LogStream& LogStream::operator<<( + LogStream& (&function)(LogStream&)) noexcept { + return function(*this); +} + +inline LogStream::LogStream(::std::streambuf& streambuf) noexcept + : ::std::ostream(&streambuf) {} + +template +inline void LogStream::write_objects(const T& object, + const Args&... args) noexcept { + *this << object; + write_objects(args...); +} + +inline void LogStream::write_objects() noexcept {} + +inline LogStream& LogStream::write_object(StringView sv) noexcept { + return write(sv.data(), sv.size()); +} + +inline LogStream& LogStream::write_object(char c) noexcept { + return write(c); +} +// LogStream end +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// ScopedLogStream begin +inline ScopedLogStream::~ScopedLogStream() noexcept { + _stream.end(); +} + +template +inline ScopedLogStream::ScopedLogStream(LogStream& stream, + Args&&... args) noexcept + : _stream {stream} { + _stream.begin(::std::forward(args)...); +} + +inline LogStream& ScopedLogStream::stream() noexcept { + return _stream; +} +// ScopedLogStream end +//////////////////////////////////////////////////////////////////////////////// + +inline LogStream& noflush(LogStream& stream) { + return stream.noflush(); +} + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/log_stream.hpp b/src/babylon/logging/log_stream.hpp deleted file mode 100644 index 36a1346c..00000000 --- a/src/babylon/logging/log_stream.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include "babylon/logging/log_stream.h" - -BABYLON_NAMESPACE_BEGIN - -template -inline LogStream& LogStream::begin(const Args&... args) noexcept { - if (ABSL_PREDICT_FALSE(++_depth != 1)) { - return *this; - } - if (!_noflush) { - do_begin(); - write_objects(args...); - } else { - _noflush = false; - } - return *this; -} - -inline LogStream& LogStream::noflush() noexcept { - if (ABSL_PREDICT_FALSE(_depth != 1)) { - return *this; - } - _noflush = true; - return *this; -} - -inline LogStream& LogStream::end() noexcept { - if (ABSL_PREDICT_FALSE(--_depth > 0)) { - return *this; - } - if (!_noflush) { - do_end(); - clear(); - } - return *this; -} - -inline LogStream& LogStream::write(const char* data, size_t size) noexcept { - rdbuf()->sputn(data, size); - return *this; -} - -inline LogStream& LogStream::write(char c) noexcept { - rdbuf()->sputc(c); - return *this; -} - -template -inline LogStream& LogStream::format(const ::absl::FormatSpec& format, - const Args&... args) noexcept { - return do_absl_format(this, format, args...); -} - -template < - typename T, typename... Args, - typename ::std::enable_if< - ::std::is_convertible::value, int>::type> -inline LogStream& LogStream::do_absl_format( - T* ls, const ::absl::FormatSpec& format, - const Args&... args) noexcept { - ::absl::Format(ls, format, args...); - return *ls; -} - -template < - typename T, typename... Args, - typename ::std::enable_if< - !::std::is_convertible::value, int>::type> -inline LogStream& LogStream::do_absl_format( - T* ls, const ::absl::FormatSpec& format, - const Args&... args) noexcept { - ::absl::Format(static_cast<::std::ostream*>(ls), format, args...); - return *ls; -} - -template ::value, int>::type> -inline LogStream& LogStream::operator<<(const T& object) noexcept { - return write_object(object); -} - -template ::value, int>::type> -inline LogStream& LogStream::operator<<(const T& object) noexcept { - *static_cast<::std::ostream*>(this) << object; - return *this; -} - -inline LogStream& LogStream::operator<<( - ::std::ostream& (*function)(::std::ostream&)) noexcept { - function(*this); - return *this; -} - -inline LogStream::LogStream(::std::streambuf& streambuf) noexcept - : ::std::ostream(&streambuf) {} - -template -inline void LogStream::write_objects(const T& object, - const Args&... args) noexcept { - *this << object; - write_objects(args...); -} - -inline void LogStream::write_objects() noexcept {} - -inline LogStream& LogStream::write_object(StringView sv) noexcept { - return write(sv.data(), sv.size()); -} - -inline LogStream& LogStream::write_object(char c) noexcept { - return write(c); -} - -BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/logger.cpp b/src/babylon/logging/logger.cpp new file mode 100644 index 00000000..54f1e0c1 --- /dev/null +++ b/src/babylon/logging/logger.cpp @@ -0,0 +1,202 @@ +#include "babylon/logging/logger.h" + +#include "babylon/logging/interface.h" + +BABYLON_NAMESPACE_BEGIN + +Logger::Logger() noexcept + : _min_severity {LogSeverity::DEBUG}, _initialized {false} { + static ThreadLocalLogStream default_log_stream { + [](::std::unique_ptr* ptr) { + new (ptr)::std::unique_ptr {new DefaultLogStream}; + }}; + for (size_t i = 0; i < static_cast(LogSeverity::NUM); ++i) { + _log_streams[i].store(&default_log_stream, ::std::memory_order_relaxed); + } +} + +Logger::Logger(const Logger& other) noexcept + : _min_severity {other._min_severity}, _initialized {other._initialized} { + for (size_t i = 0; i < static_cast(LogSeverity::NUM); ++i) { + _log_streams[i].store( + other._log_streams[i].load(::std::memory_order_relaxed), + ::std::memory_order_relaxed); + } +} + +Logger& Logger::operator=(const Logger& other) noexcept { + for (size_t i = 0; i < static_cast(LogSeverity::NUM); ++i) { + _log_streams[i].store( + other._log_streams[i].load(::std::memory_order_relaxed), + ::std::memory_order_relaxed); + } + _min_severity = other._min_severity; + _initialized = other._initialized; + return *this; +} + +LogStream& Logger::stream(LogSeverity severity, StringView file, + int line) noexcept { + static NullLogStream nls; + + // TODO(oathdruid): remove this after remove LogStreamProvider in interface.h + if (ABSL_PREDICT_FALSE(!_initialized)) { + if (static_cast(severity) >= LogInterface::min_severity()) { + return LogInterface::provider().stream(static_cast(severity), file, + line); + } + return nls; + } + + if (ABSL_PREDICT_FALSE(severity < min_severity())) { + return nls; + } + + auto& stream = *_log_streams[static_cast(severity)] + .load(::std::memory_order_acquire) + ->local(); + stream.set_severity(severity); + stream.set_file(file); + stream.set_line(line); + return stream; +} + +void Logger::set_initialized(bool initialized) noexcept { + _initialized = initialized; +} + +void Logger::set_min_severity(LogSeverity min_severity) noexcept { + _min_severity = min_severity; +} + +void Logger::set_log_stream(LogSeverity severity, + ThreadLocalLogStream& log_stream) noexcept { + _log_streams[static_cast(severity)].store(&log_stream, + ::std::memory_order_release); +} + +//////////////////////////////////////////////////////////////////////////////// +// LoggerBuilder begin +LoggerBuilder::LoggerBuilder() noexcept : _min_severity {LogSeverity::INFO} { + for (int i = 0; i < static_cast(LogSeverity::NUM); ++i) { + auto severity = static_cast(i); + _log_streams[i].first = severity; + _log_streams[i].second.set_constructor( + [severity](::std::unique_ptr* ptr) { + new (ptr)::std::unique_ptr {new DefaultLogStream}; + (*ptr)->set_severity(severity); + }); + } +} + +Logger LoggerBuilder::build() noexcept { + Logger logger; + for (auto& pair : _log_streams) { + logger.set_log_stream(pair.first, pair.second); + } + logger.set_min_severity(_min_severity); + logger.set_initialized(true); + return logger; +} + +void LoggerBuilder::set_log_stream_creator( + LoggerBuilder::Creator creator) noexcept { + for (int i = 0; i < static_cast(LogSeverity::NUM); ++i) { + auto severity = static_cast(i); + auto& stream = _log_streams[i].second; + stream.set_constructor( + [severity, creator](::std::unique_ptr* ptr) { + new (ptr)::std::unique_ptr {creator()}; + (*ptr)->set_severity(severity); + }); + } +} + +void LoggerBuilder::set_log_stream_creator( + LogSeverity severity, LoggerBuilder::Creator creator) noexcept { + auto& stream = _log_streams[static_cast(severity)].second; + stream.set_constructor( + [severity, creator](::std::unique_ptr* ptr) { + new (ptr)::std::unique_ptr {creator()}; + (*ptr)->set_severity(severity); + }); +} + +void LoggerBuilder::set_min_severity(LogSeverity min_severity) noexcept { + _min_severity = min_severity; +} +// LoggerBuilder end +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// LoggerManager begin +LoggerManager& LoggerManager::instance() noexcept { + static LoggerManager object; + return object; +} + +void LoggerManager::set_root_builder(LoggerBuilder&& builder) noexcept { + ::std::lock_guard<::std::mutex> lock {_builder_mutex}; + _root_builder.reset(new LoggerBuilder {::std::move(builder)}); +} + +void LoggerManager::set_builder(StringView name, + LoggerBuilder&& builder) noexcept { + ::std::lock_guard<::std::mutex> lock {_builder_mutex}; + _builders.emplace(name, ::std::move(builder)); +} + +void LoggerManager::apply() noexcept { + ::std::lock_guard<::std::mutex> lock {_builder_mutex}; + if (_root_builder == nullptr) { + _root_builder.reset(new LoggerBuilder); + } + _root_logger = _root_builder->build(); + for (auto& pair : _loggers) { + apply_to(pair.first, pair.second); + } +} + +Logger& LoggerManager::get_logger(StringView name) noexcept { + if (name.empty()) { + return _root_logger; + } + + auto result = _loggers.emplace(name); + if (!result.second) { + return result.first->second; + } + + ::std::lock_guard<::std::mutex> lock {_builder_mutex}; + apply_to(name, result.first->second); + return result.first->second; +} + +void LoggerManager::apply_to(StringView name, Logger& logger) noexcept { + auto builder = find_nearest_builder(name); + if (builder != nullptr) { + logger = builder->build(); + } +} + +LoggerBuilder* LoggerManager::find_nearest_builder(StringView name) noexcept { + ::std::string needle {name.data(), name.size()}; + while (!needle.empty()) { + auto iter = _builders.find(needle); + if (iter != _builders.end()) { + return &iter->second; + } + auto colon_pos = needle.rfind("::"); + auto dot_pos = needle.rfind('.'); + auto pos = colon_pos != needle.npos ? colon_pos : 0; + if (dot_pos != needle.npos && dot_pos > pos) { + pos = dot_pos; + } + needle.resize(pos); + } + return _root_builder.get(); +} +// LoggerManager end +//////////////////////////////////////////////////////////////////////////////// + +BABYLON_NAMESPACE_END diff --git a/src/babylon/logging/logger.h b/src/babylon/logging/logger.h new file mode 100644 index 00000000..0ce846b7 --- /dev/null +++ b/src/babylon/logging/logger.h @@ -0,0 +1,142 @@ +#pragma once + +#include "babylon/concurrent/thread_local.h" // babylon::EnumerableThreadLocal +#include "babylon/concurrent/transient_hash_table.h" // babylon::ConcurrentTransientHashMap +#include "babylon/logging/interface.h" // babylon::LogInterface +#include "babylon/logging/log_severity.h" // babylon::LogSeverity +#include "babylon/logging/log_stream.h" // babylon::LogStream + +#include + +BABYLON_NAMESPACE_BEGIN + +class LoggerBuilder; +class LoggerManager; +class Logger final { + public: + Logger() noexcept; + ~Logger() noexcept = default; + + inline bool initialized() const noexcept; + inline LogSeverity min_severity() const noexcept; + LogStream& stream(LogSeverity severity, StringView file, int line) noexcept; + + private: + using ThreadLocalLogStream = + EnumerableThreadLocal<::std::unique_ptr>; + using Storage = ::std::array(LogSeverity::NUM)>; + using PointerStorage = ::std::array<::std::atomic, + static_cast(LogSeverity::NUM)>; + + Logger(const Logger& other) noexcept; + Logger& operator=(const Logger& other) noexcept; + + void set_initialized(bool initialized) noexcept; + void set_min_severity(LogSeverity min_severity) noexcept; + void set_log_stream(LogSeverity severity, + ThreadLocalLogStream& log_stream) noexcept; + + PointerStorage _log_streams; + LogSeverity _min_severity; + bool _initialized; + + friend class LogInterface; + friend class LoggerBuilder; + friend class LoggerManager; +}; + +class LoggerBuilder final { + public: + using Creator = ::std::function<::std::unique_ptr()>; + + LoggerBuilder() noexcept; + + Logger build() noexcept; + + void set_log_stream_creator(Creator creator) noexcept; + void set_log_stream_creator(LogSeverity severity, Creator creator) noexcept; + + void set_min_severity(LogSeverity min_severity) noexcept; + + private: + using Storage = + ::std::array<::std::pair, + static_cast(LogSeverity::NUM)>; + Storage _log_streams; + LogSeverity _min_severity; +}; + +class LoggerManager final { + public: + LoggerManager(LoggerManager&&) = delete; + LoggerManager(const LoggerManager&) = delete; + LoggerManager& operator=(LoggerManager&&) = delete; + LoggerManager& operator=(const LoggerManager&) = delete; + + static LoggerManager& instance() noexcept; + + void set_root_builder(LoggerBuilder&& logger) noexcept; + void set_builder(StringView name, LoggerBuilder&& logger) noexcept; + void apply() noexcept; + void clear() noexcept; + + inline Logger& get_root_logger() noexcept; + Logger& get_logger(StringView name) noexcept; + + private: + LoggerManager() = default; + ~LoggerManager() noexcept = default; + + void apply_to(StringView name, Logger& logger) noexcept; + LoggerBuilder* find_nearest_builder(StringView name) noexcept; + + Logger _root_logger; + ConcurrentTransientHashMap<::std::string, Logger, ::std::hash> + _loggers; + + ::std::mutex _builder_mutex; + ::std::unique_ptr _root_builder; + ConcurrentTransientHashMap<::std::string, LoggerBuilder, + ::std::hash> + _builders; +}; + +// 通过&操作符将返回值转为void的工具类 +// 辅助BABYLON_LOG宏实现条件日志 +class Voidify { + public: + template + inline void operator&(T&&) noexcept {} +}; + +inline LogSeverity Logger::min_severity() const noexcept { + return _min_severity; +} + +inline bool Logger::initialized() const noexcept { + return _initialized; +} + +//////////////////////////////////////////////////////////////////////////////// +// LoggerManager begin +inline Logger& LoggerManager::get_root_logger() noexcept { + return _root_logger; +} +// LoggerManager end +//////////////////////////////////////////////////////////////////////////////// +BABYLON_NAMESPACE_END + +#define BABYLON_LOG_STREAM(logger, severity, ...) \ + (logger).min_severity() > ::babylon::LogSeverity::severity \ + ? (void)0 \ + : ::babylon::Voidify() & \ + ::babylon::ScopedLogStream( \ + (logger).stream(::babylon::LogSeverity::severity, __FILE__, \ + __LINE__), \ + ##__VA_ARGS__) \ + .stream() + +#define BABYLON_LOG(severity) \ + BABYLON_LOG_STREAM(::babylon::LoggerManager::instance().get_root_logger(), \ + severity) diff --git a/src/babylon/logging/rolling_file_object.cpp b/src/babylon/logging/rolling_file_object.cpp new file mode 100644 index 00000000..614c7f9f --- /dev/null +++ b/src/babylon/logging/rolling_file_object.cpp @@ -0,0 +1,179 @@ +#include "babylon/logging/rolling_file_object.h" + +#if __cplusplus >= 201703L + +#include "babylon/time.h" + +// clang-format off +#include BABYLON_EXTERNAL(absl/base/optimization.h) // ABSL_PREDICT_FALSE +#include BABYLON_EXTERNAL(absl/time/clock.h) // absl::Now +// clang-format on + +#include +#include +#include +#include +#include + +#include +#include + +BABYLON_NAMESPACE_BEGIN + +RollingFileObject::~RollingFileObject() noexcept { + if (_fd >= 0) { + ::close(_fd); + } +} + +void RollingFileObject::set_directory(StringView directory) noexcept { + _directory.assign(directory.data(), directory.size()); +} + +void RollingFileObject::set_file_pattern(StringView pattern) noexcept { + _file_pattern.assign(pattern.data(), pattern.size()); +} + +void RollingFileObject::set_max_file_number(size_t number) noexcept { + _max_file_number = number; +} + +void RollingFileObject::delete_expire_files() noexcept { + if (_max_file_number == SIZE_MAX) { + return; + } + + ::std::vector<::std::string> to_be_deleted_files; + { + ::std::lock_guard<::std::mutex> {_tracking_files_mutex}; + while (_tracking_files.size() > _max_file_number) { + to_be_deleted_files.emplace_back(::std::move(_tracking_files.front())); + _tracking_files.pop_front(); + } + } + + for (auto& file : to_be_deleted_files) { + ::unlink(file.c_str()); + } +} + +void RollingFileObject::scan_and_tracking_existing_files() noexcept { + if (_max_file_number == SIZE_MAX) { + return; + } + + ::std::string file_re; + file_re.reserve(_file_pattern.size() * 2); + bool in_escape = false; + bool in_repeat = false; + for (auto c : _file_pattern) { + if (in_escape) { + if (c == '%') { + file_re.push_back(c); + in_repeat = false; + } else if (!in_repeat) { + file_re.append(".+"); + in_repeat = true; + } + in_escape = false; + continue; + } else if (c == '%') { + in_escape = true; + continue; + } else if (c == '.') { + file_re.push_back('\\'); + } + file_re.push_back(c); + in_repeat = false; + } + ::std::regex file_matcher(file_re); + + auto dir = ::opendir(_directory.c_str()); + if (dir == nullptr) { + return; + } + + for (struct ::dirent* entry = ::readdir(dir); entry != nullptr; + entry = ::readdir(dir)) { + if (::std::regex_match(entry->d_name, file_matcher)) { + ::std::lock_guard<::std::mutex> lock {_tracking_files_mutex}; + _tracking_files.emplace_back(_directory); + _tracking_files.back().push_back('/'); + _tracking_files.back().append(entry->d_name); + } + } + ::closedir(dir); + + _tracking_files.sort(); + return; +} + +::std::tuple +RollingFileObject::check_and_get_file_descriptor() noexcept { + // 文件未打开 + if (ABSL_PREDICT_FALSE(_fd < 0)) { + open(); + return ::std::make_tuple(_fd, -1); + } + + // 最频繁一秒检查一次足以 + auto now_time = ::absl::GetCurrentTimeNanos() / 1000 / 1000 / 1000; + if (now_time == _last_check_time) { + return ::std::make_tuple(_fd, -1); + } + _last_check_time = now_time; + + // 文件名未发生变更,继续使用 + char file_name[_file_pattern.size() + 64]; + format_file_name(file_name, sizeof(file_name)); + if (_file_name == file_name) { + return ::std::make_tuple(_fd, -1); + } + + // 文件切换 + auto old_fd = open(); + return ::std::make_tuple(_fd, old_fd); +} + +int RollingFileObject::format_file_name(char* buffer, size_t size) noexcept { + auto now_time = ::absl::ToTimeT(::absl::Now()); + struct ::tm time_struct; + ::babylon::localtime(&now_time, &time_struct); + return ::strftime(buffer, size, _file_pattern.c_str(), &time_struct); +} + +int RollingFileObject::open() noexcept { + ::mkdir(_directory.c_str(), 0755); + + char full_file_name[_directory.size() + _file_pattern.size() + 64]; + ::memcpy(full_file_name, _directory.c_str(), _directory.size()); + full_file_name[_directory.size()] = '/'; + auto bytes = format_file_name(full_file_name + _directory.size() + 1, + _file_pattern.size() + 63); + if (bytes == 0) { + return -1; + } + + auto new_fd = ::open(full_file_name, O_CREAT | O_APPEND | O_WRONLY, 0644); + if (new_fd < 0) { + return -1; + } + + if (0 != ::fstat(new_fd, &_stat)) { + ::close(new_fd); + return -1; + } + + if (_max_file_number != SIZE_MAX) { + ::std::lock_guard<::std::mutex> {_tracking_files_mutex}; + _tracking_files.push_back(full_file_name); + } + auto old_fd = _fd; + _fd = new_fd; + _file_name = full_file_name + _directory.size() + 1; + return old_fd; +} + +BABYLON_NAMESPACE_END + +#endif // __cplusplus >= 201703L diff --git a/src/babylon/logging/rolling_file_object.h b/src/babylon/logging/rolling_file_object.h new file mode 100644 index 00000000..ef37f909 --- /dev/null +++ b/src/babylon/logging/rolling_file_object.h @@ -0,0 +1,63 @@ +#pragma once + +#include "babylon/logging/file_object.h" // FileObject +#include "babylon/string_view.h" // StringView + +#if __cplusplus >= 201703L + +#include // ::stat + +#include // std::atomic +#include // std::list +#include // std::mutex +#include // std::thread + +BABYLON_NAMESPACE_BEGIN + +// 定时滚动的日志文件实体 +// - 文件名通过pattern设定,根据strftime语法根据时间生成 +// - 根据文件名变化切换打开滚动到新文件 +// - 跟踪滚动文件总数,执行定量清理 +class RollingFileObject : public FileObject { + public: + virtual ~RollingFileObject() noexcept override; + + // 设置文件所属目录 + void set_directory(StringView directory) noexcept; + // 设置文件pattern,接受strftime语法,例如典型的%Y%m%d%H + void set_file_pattern(StringView pattern) noexcept; + // 设置最多保留文件数,默认无限保留 + void set_max_file_number(size_t number) noexcept; + + // 在限定了保留文件数的情况下,会对创建的文件加入跟踪列表用于定量保留 + // 但是对于已经存在的文件无法跟踪 + // 启动期间调用此接口可以扫描目录并记录其中符合pattern的已有文件 + // 并加入到跟踪列表,来支持重启场景下继续跟进正确的文件定量保留 + void scan_and_tracking_existing_files() noexcept; + // 检查目前跟踪列表中是否超出了保留数目,超出则进行清理 + void delete_expire_files() noexcept; + + private: + virtual ::std::tuple check_and_get_file_descriptor() noexcept + override; + + int format_file_name(char* buffer, size_t size) noexcept; + int open() noexcept; + + ::std::string _directory; + ::std::string _file_pattern; + size_t _max_file_number {SIZE_MAX}; + + time_t _last_check_time {0}; + + int _fd {-1}; + ::std::string _file_name; + struct ::stat _stat; + + ::std::mutex _tracking_files_mutex; + ::std::list<::std::string> _tracking_files; +}; + +BABYLON_NAMESPACE_END + +#endif // __cplusplus >= 201703L diff --git a/src/babylon/mlock.cpp b/src/babylon/mlock.cpp index 34f9349c..b8db37fa 100644 --- a/src/babylon/mlock.cpp +++ b/src/babylon/mlock.cpp @@ -1,6 +1,6 @@ #include "babylon/mlock.h" -#include "babylon/logging/interface.h" // BABYLON_LOG +#include "babylon/logging/logger.h" // BABYLON_LOG // clang-format off #include BABYLON_EXTERNAL(absl/strings/str_split.h) // absl::StrSplit diff --git a/src/babylon/serialization/BUILD b/src/babylon/serialization/BUILD index 4deca934..b3115036 100644 --- a/src/babylon/serialization/BUILD +++ b/src/babylon/serialization/BUILD @@ -12,7 +12,7 @@ cc_library( strip_include_prefix = '//src', deps = [ '//src/babylon:any', - '//src/babylon/logging:interface', + '//src/babylon/logging:logger', '@com_google_protobuf//:protobuf', '//:boost.preprocessor', '@com_google_absl//absl/container:flat_hash_map', diff --git a/src/babylon/type_traits.h b/src/babylon/type_traits.h index 54c9661d..1d6a1a7b 100644 --- a/src/babylon/type_traits.h +++ b/src/babylon/type_traits.h @@ -178,7 +178,7 @@ struct IsInvocable { // 例如,如下检测可以通过 // IsHashable, const std::string&>::value == true // IsHashable, const char*>::value == true -BABYLON_DECLARE_MEMBER_INVOCABLE(operator(), IsHashable); +BABYLON_DECLARE_MEMBER_INVOCABLE(operator(), IsHashable) // 检测是否可以进行==和!=运算 template diff --git a/test/concurrent/test_bounded_queue.cpp b/test/concurrent/test_bounded_queue.cpp index a2995d55..3acbf4a7 100644 --- a/test/concurrent/test_bounded_queue.cpp +++ b/test/concurrent/test_bounded_queue.cpp @@ -1,5 +1,5 @@ #include "babylon/concurrent/bounded_queue.h" -#include "babylon/logging/interface.h" // BABYLON_LOG +#include "babylon/logging/logger.h" // BABYLON_LOG #include "gtest/gtest.h" diff --git a/test/logging/BUILD b/test/logging/BUILD index bbe32abd..5c1d95c1 100644 --- a/test/logging/BUILD +++ b/test/logging/BUILD @@ -14,6 +14,16 @@ cc_test( ] ) +cc_test( + name = 'test_async_log_stream', + srcs = ['test_async_log_stream.cpp'], + copts = BABYLON_COPTS, + deps = [ + '//:logging_async_log_stream', + '@com_google_googletest//:gtest_main', + ] +) + cc_test( name = 'test_custom_default_provider', srcs = ['test_custom_default_provider.cpp'], @@ -34,6 +44,16 @@ cc_test( ] ) +cc_test( + name = 'test_log_entry', + srcs = ['test_log_entry.cpp'], + copts = BABYLON_COPTS, + deps = [ + '//:logging_log_entry', + '@com_google_googletest//:gtest_main', + ] +) + cc_test( name = 'test_log_stream', srcs = ['test_log_stream.cpp'], @@ -43,3 +63,23 @@ cc_test( '@com_google_googletest//:gtest_main', ] ) + +cc_test( + name = 'test_logger', + srcs = ['test_logger.cpp'], + copts = BABYLON_COPTS + ['-fno-access-control'], + deps = [ + '//:logging_logger', + '@com_google_googletest//:gtest_main', + ] +) + +cc_test( + name = 'test_rolling_file_object', + srcs = ['test_rolling_file_object.cpp'], + copts = BABYLON_COPTS, + deps = [ + '//:logging_rolling_file_object', + '@com_google_googletest//:gtest_main', + ] +) diff --git a/test/logging/test_async_file_appender.cpp b/test/logging/test_async_file_appender.cpp index e1ed4a8c..275409e9 100644 --- a/test/logging/test_async_file_appender.cpp +++ b/test/logging/test_async_file_appender.cpp @@ -7,24 +7,27 @@ using ::babylon::AsyncFileAppender; using ::babylon::FileObject; -using ::babylon::Log; +using ::babylon::LogEntry; using ::babylon::LogStreamBuffer; using ::babylon::PageAllocator; -using ::babylon::PageHeap; +namespace { struct StaticFileObject : public FileObject { - virtual int check_and_get_file_descriptor() noexcept override { - return fd; + virtual ::std::tuple check_and_get_file_descriptor() noexcept + override { + return ::std::tuple {fd, -1}; } int fd; }; +} // namespace struct LogStream : public ::std::ostream { LogStream(PageAllocator& page_allocator) noexcept : ::std::ostream(&buffer) { - buffer.begin(page_allocator); + buffer.set_page_allocator(page_allocator); + buffer.begin(); } - inline Log& end() noexcept { + inline LogEntry& end() noexcept { return buffer.end(); } @@ -34,16 +37,13 @@ struct LogStream : public ::std::ostream { struct AsyncFileAppenderTest : public ::testing::Test { virtual void SetUp() override { ASSERT_EQ(0, ::pipe(pipefd)); - int ret = ::fcntl(pipefd[1], F_GETPIPE_SZ); - fprintf(stderr, "pipe buffer size %d\n", ret); - ret = ::fcntl(pipefd[1], F_SETPIPE_SZ, 16384); - fprintf(stderr, "set pipe buffer size ret %d\n", ret); + ASSERT_GT(65536, ::fcntl(pipefd[1], F_SETPIPE_SZ, 16384)); file_object.fd = pipefd[1]; } virtual void TearDown() override { - close(pipefd[1]); - close(pipefd[0]); + ASSERT_EQ(0, close(pipefd[1])); + ASSERT_EQ(0, close(pipefd[0])); } void read_pipe(char* data, size_t size) { @@ -56,23 +56,14 @@ struct AsyncFileAppenderTest : public ::testing::Test { int pipefd[2]; StaticFileObject file_object; - PageHeap page_heap; AsyncFileAppender appender; }; -TEST_F(AsyncFileAppenderTest, default_write_to_stderr) { - ASSERT_EQ(0, appender.initialize()); - LogStream ls(appender.page_allocator()); - ls << "this line should appear in stderr with num " << 10086 << ::std::endl; - appender.write(ls.end()); -} - TEST_F(AsyncFileAppenderTest, write_to_file_object) { - appender.set_file_object(file_object); ASSERT_EQ(0, appender.initialize()); LogStream ls(appender.page_allocator()); ls << "this line should appear in pipe with num " << 10010 << ::std::endl; - appender.write(ls.end()); + appender.write(ls.end(), &file_object); ::std::string expected = "this line should appear in pipe with num 10010\n"; ::std::string s; @@ -82,12 +73,11 @@ TEST_F(AsyncFileAppenderTest, write_to_file_object) { } TEST_F(AsyncFileAppenderTest, write_happen_async) { - appender.set_file_object(file_object); ASSERT_EQ(0, appender.initialize()); for (size_t i = 0; i < 1000; ++i) { LogStream ls(appender.page_allocator()); ls << "this line should appear in pipe with num " << i << ::std::endl; - appender.write(ls.end()); + appender.write(ls.end(), &file_object); } ASSERT_LT(0, appender.pending_size()); for (size_t i = 0; i < 1000; ++i) { @@ -99,10 +89,10 @@ TEST_F(AsyncFileAppenderTest, write_happen_async) { ASSERT_EQ(expected, s); } ASSERT_EQ(0, appender.pending_size()); + appender.close(); } TEST_F(AsyncFileAppenderTest, can_discard_log) { - appender.set_file_object(file_object); ASSERT_EQ(0, appender.initialize()); for (size_t i = 0; i < 100; ++i) { LogStream ls(appender.page_allocator()); @@ -122,20 +112,21 @@ TEST_F(AsyncFileAppenderTest, write_different_size_level_correct) { s.push_back(gen()); } - page_heap.set_page_size(page_size); - appender.set_page_allocator(page_heap); - appender.set_file_object(file_object); + ::babylon::NewDeletePageAllocator page_allocator; + page_allocator.set_page_size(page_size); + appender.set_page_allocator(page_allocator); appender.set_queue_capacity(64); ASSERT_EQ(0, appender.initialize()); for (size_t i = page_size / 2; i < max_log_size; i += page_size / 2) { LogStream ls(appender.page_allocator()); ls << s.substr(0, i); - appender.write(ls.end()); + appender.write(ls.end(), &file_object); ::std::string rs; rs.resize(i); read_pipe(&rs[0], rs.size()); ASSERT_EQ(s.substr(0, i), rs); } + appender.close(); } diff --git a/test/logging/test_async_log_stream.cpp b/test/logging/test_async_log_stream.cpp new file mode 100644 index 00000000..46c53caf --- /dev/null +++ b/test/logging/test_async_log_stream.cpp @@ -0,0 +1,71 @@ +#include "babylon/logging/async_file_appender.h" +#include "babylon/logging/async_log_stream.h" + +#include "gtest/gtest.h" + +#include + +using ::babylon::AsyncFileAppender; +using ::babylon::AsyncLogStream; +using ::babylon::FileObject; +using ::babylon::LoggerBuilder; + +namespace { +struct StaticFileObject : public FileObject { + virtual ::std::tuple check_and_get_file_descriptor() noexcept + override { + return ::std::tuple {fd, -1}; + } + int fd; +}; +} // namespace + +struct AsyncLogStreamTest : public ::testing::Test { + virtual void SetUp() override { + file_object.fd = STDERR_FILENO; + ASSERT_EQ(0, appender.initialize()); + } + + StaticFileObject file_object; + AsyncFileAppender appender; +}; + +TEST_F(AsyncLogStreamTest, write_to_file_object) { + LoggerBuilder builder; + builder.set_log_stream_creator( + AsyncLogStream::creator(appender, file_object)); + ::babylon::LoggerManager::instance().set_root_builder(::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + + ::testing::internal::CaptureStderr(); + BABYLON_LOG(INFO) << "this line should appear in stderr with num " << 10010; + appender.close(); + auto text = ::testing::internal::GetCapturedStderr(); + + ::std::cerr << text; + ASSERT_NE(text.npos, + text.find("this line should appear in stderr with num 10010")); +} + +TEST_F(AsyncLogStreamTest, write_header_before_message) { + LoggerBuilder builder; + builder.set_log_stream_creator( + AsyncLogStream::creator(appender, file_object, [](AsyncLogStream& ls) { + ls << "[this header appear before message]"; + })); + ::babylon::LoggerManager::instance().set_root_builder(::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + + ::testing::internal::CaptureStderr(); + BABYLON_LOG(INFO) << "this line should appear in stderr with num" + << ::babylon::noflush; + BABYLON_LOG(INFO) << " " << 10010; + appender.close(); + auto text = ::testing::internal::GetCapturedStderr(); + + ::std::cerr << text; + ASSERT_NE(text.npos, + text.find("this line should appear in stderr with num 10010")); + text.resize(text.find("this line should appear in stderr with num 10010")); + ASSERT_EQ("[this header appear before message]", text); +} diff --git a/test/logging/test_custom_default_provider.cpp b/test/logging/test_custom_default_provider.cpp index 8e97f92a..bf01f21a 100644 --- a/test/logging/test_custom_default_provider.cpp +++ b/test/logging/test_custom_default_provider.cpp @@ -1,4 +1,5 @@ #include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include diff --git a/test/logging/test_interface.cpp b/test/logging/test_interface.cpp index 27abb5c2..e842ab9b 100644 --- a/test/logging/test_interface.cpp +++ b/test/logging/test_interface.cpp @@ -1,4 +1,5 @@ #include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include @@ -30,13 +31,6 @@ struct LogInterfaceTest : public ::testing::Test { } }; -TEST_F(LogInterfaceTest, default_log_to_stderr) { - ::testing::internal::CaptureStderr(); - BABYLON_LOG(INFO) << "this line should appear in stderr"; - auto text = ::testing::internal::GetCapturedStderr(); - ASSERT_NE(text.npos, text.find("this line should appear in stderr")); -} - TEST_F(LogInterfaceTest, ignore_log_less_than_min_severity) { LogInterface::set_min_severity(LogInterface::SEVERITY_WARNING); ::testing::internal::CaptureStderr(); diff --git a/test/logging/test_log_entry.cpp b/test/logging/test_log_entry.cpp new file mode 100644 index 00000000..5a95ea41 --- /dev/null +++ b/test/logging/test_log_entry.cpp @@ -0,0 +1,88 @@ +#include "babylon/logging/log_entry.h" +#include "babylon/reusable/page_allocator.h" + +#include "gtest/gtest.h" + +#include + +struct LogEntryTest : public ::testing::Test { + virtual void SetUp() override { + new_delete_page_allocator.set_page_size(128); + page_allocator.set_upstream(new_delete_page_allocator); + buffer.set_page_allocator(page_allocator); + } + + virtual void TearDown() override {} + + ::std::string random_string(size_t size) { + ::std::string s; + s.reserve(size); + for (size_t i = 0; i < size; ++i) { + s.push_back(gen()); + } + return s; + } + + ::std::string to_string(::std::vector& iov) { + ::std::string s; + for (auto one_iov : iov) { + s.append(static_cast(one_iov.iov_base), one_iov.iov_len); + } + return s; + } + + void release(::std::vector& iov) { + for (auto one_iov : iov) { + page_allocator.deallocate(one_iov.iov_base); + } + } + + ::std::mt19937_64 gen {::std::random_device {}()}; + + ::babylon::LogStreamBuffer buffer; + ::babylon::NewDeletePageAllocator new_delete_page_allocator; + ::babylon::CountingPageAllocator page_allocator; +}; + +TEST_F(LogEntryTest, get_empty_log_entry_when_no_input) { + buffer.begin(); + auto& entry = buffer.end(); + ASSERT_EQ(0, entry.size); + ASSERT_EQ(0, page_allocator.allocated_page_num()); + + ::std::vector iov; + entry.append_to_iovec(page_allocator.page_size(), iov); + ASSERT_EQ(0, iov.size()); +} + +TEST_F(LogEntryTest, read_and_write_correct) { + ::std::vector iov; + auto s = random_string(128 * 128); + for (size_t i = 0; i < 128 * 128; ++i) { + ::std::string_view sv {s.c_str(), i}; + iov.clear(); + buffer.begin(); + buffer.sputn(sv.data(), sv.size()); + auto& entry = buffer.end(); + entry.append_to_iovec(page_allocator.page_size(), iov); + auto ss = to_string(iov); + ASSERT_EQ(sv, ss); + release(iov); + ASSERT_EQ(0, page_allocator.allocated_page_num()); + } +} + +TEST_F(LogEntryTest, work_with_std_ostream) { + auto s = random_string(gen() % (128 * 128)); + ::std::ostream os {&buffer}; + buffer.begin(); + os << s; + auto& entry = buffer.end(); + + ::std::vector iov; + entry.append_to_iovec(page_allocator.page_size(), iov); + auto ss = to_string(iov); + ASSERT_EQ(s, ss); + release(iov); + ASSERT_EQ(0, page_allocator.allocated_page_num()); +} diff --git a/test/logging/test_log_stream.cpp b/test/logging/test_log_stream.cpp index 1dbef75a..5bbc1215 100644 --- a/test/logging/test_log_stream.cpp +++ b/test/logging/test_log_stream.cpp @@ -204,3 +204,13 @@ TEST_F(LogStreamTest, noflushable_after_reenter) { ASSERT_EQ(1, ss.end_times); ASSERT_EQ("hello +10086 world 0.66 10010", ss.stringbuf.str()); } + +TEST_F(LogStreamTest, default_log_stream_log_to_stderr) { + ::babylon::DefaultLogStream dls; + ::testing::internal::CaptureStderr(); + dls.begin(); + dls << "this should apper in stderr"; + dls.end(); + auto text = ::testing::internal::GetCapturedStderr(); + ASSERT_NE(text.npos, text.find("this should apper in stderr")); +} diff --git a/test/logging/test_logger.cpp b/test/logging/test_logger.cpp new file mode 100644 index 00000000..a0dc318d --- /dev/null +++ b/test/logging/test_logger.cpp @@ -0,0 +1,272 @@ +#include "babylon/logging/logger.h" + +#include "gtest/gtest.h" + +struct LoggerTest : public ::testing::Test { + virtual void SetUp() override { + ::babylon::LoggerManager::instance().~LoggerManager(); + new (&::babylon::LoggerManager::instance())::babylon::LoggerManager; + } + + virtual void TearDown() override {} + + ::std::stringbuf buffer; + ::babylon::LoggerBuilder builder; + + ::std::stringbuf buffer2; +}; + +TEST_F(LoggerTest, default_logger_do_minimal_job) { + ::babylon::Logger logger; + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in stderr"; + ASSERT_FALSE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, logger.min_severity()); +} + +TEST_F(LoggerTest, default_builder_build_default_logger_in_production_mode) { + auto logger = builder.build(); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); +} + +TEST_F(LoggerTest, uninitialized_manager_get_uninitialized_root_logger) { + auto& logger = ::babylon::LoggerManager::instance().get_root_logger(); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + ASSERT_FALSE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, logger.min_severity()); +} + +TEST_F(LoggerTest, uninitialized_manager_get_uninitialized_named_logger) { + auto& logger = ::babylon::LoggerManager::instance().get_logger("name"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + ASSERT_FALSE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, logger.min_severity()); +} + +TEST_F(LoggerTest, assign_stream_for_severity) { + builder.set_log_stream_creator(::babylon::LogSeverity::INFO, [this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + auto logger = builder.build(); + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in string"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text appear in stderr"; + ASSERT_EQ("this text appear in string", buffer.str()); +} + +TEST_F(LoggerTest, assign_stream_for_all_severity) { + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + auto logger = builder.build(); + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + BABYLON_LOG_STREAM(logger, DEBUG) << "this text is ignore"; + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in string"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text also appear in string"; + ASSERT_EQ( + "this text appear in string" + "this text also appear in string", + buffer.str()); +} + +TEST_F(LoggerTest, stream_has_correct_basic_info) { + builder.set_log_stream_creator(::babylon::LogSeverity::INFO, [this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::DEBUG); + auto logger = builder.build(); + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, logger.min_severity()); + for (auto i = 0; i < static_cast(::babylon::LogSeverity::NUM); ++i) { + auto severity = static_cast<::babylon::LogSeverity>(i); + ASSERT_EQ(severity, logger.stream(severity, __FILE__, __LINE__).severity()); + ASSERT_EQ(__FILE__, logger.stream(severity, __FILE__, __LINE__).file()); + ASSERT_EQ(__LINE__, logger.stream(severity, __FILE__, __LINE__).line()); + } +} + +TEST_F(LoggerTest, apply_empty_manager_get_default_production_logger) { + ::babylon::LoggerManager::instance().apply(); + { + auto& logger = ::babylon::LoggerManager::instance().get_root_logger(); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("name"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + } +} + +TEST_F(LoggerTest, builder_set_to_manager_cover_a_sub_tree) { + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::WARNING); + ::babylon::LoggerManager::instance().set_builder("a.b", ::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + { + auto& logger = ::babylon::LoggerManager::instance().get_root_logger(); + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in stderr"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a.bc"); + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in stderr"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::INFO, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a.b"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a.b.c"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text also appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a.b::c"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text still appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + ASSERT_EQ( + "this text appear in string" + "this text also appear in string" + "this text still appear in string", + buffer.str()); +} + +TEST_F(LoggerTest, builder_set_to_root_cover_all) { + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::WARNING); + ::babylon::LoggerManager::instance().set_root_builder(::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + { + auto& logger = ::babylon::LoggerManager::instance().get_root_logger(); + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a.b"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text also appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = ::babylon::LoggerManager::instance().get_logger("a"); + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text still appear in string"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + ASSERT_EQ( + "this text appear in string" + "this text also appear in string" + "this text still appear in string", + buffer.str()); +} + +TEST_F(LoggerTest, builder_set_cover_logger_get_before) { + auto root_logger = &::babylon::LoggerManager::instance().get_root_logger(); + auto a_logger = &::babylon::LoggerManager::instance().get_logger("a"); + auto a_b_logger = &::babylon::LoggerManager::instance().get_logger("a.b"); + auto a_b_c_logger = &::babylon::LoggerManager::instance().get_logger("a.b.c"); + BABYLON_LOG_STREAM(*root_logger, INFO) << "this text appear in stderr"; + BABYLON_LOG_STREAM(*a_logger, INFO) << "this text appear in stderr"; + BABYLON_LOG_STREAM(*a_b_logger, INFO) << "this text appear in stderr"; + BABYLON_LOG_STREAM(*a_b_c_logger, INFO) << "this text appear in stderr"; + ASSERT_FALSE(root_logger->initialized()); + ASSERT_FALSE(a_logger->initialized()); + ASSERT_FALSE(a_b_logger->initialized()); + ASSERT_FALSE(a_b_c_logger->initialized()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, root_logger->min_severity()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, a_logger->min_severity()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, a_b_logger->min_severity()); + ASSERT_EQ(::babylon::LogSeverity::DEBUG, a_b_c_logger->min_severity()); + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::WARNING); + ::babylon::LoggerManager::instance().set_root_builder(::std::move(builder)); + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer2); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::WARNING); + ::babylon::LoggerManager::instance().set_builder("a.b", ::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + { + auto& logger = *root_logger; + BABYLON_LOG_STREAM(logger, INFO) << "this text appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text appear in root"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = *a_logger; + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text also appear in root"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = *a_b_logger; + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text appear in a.b"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + { + auto& logger = *a_b_c_logger; + BABYLON_LOG_STREAM(logger, INFO) << "this text also appear in stderr"; + BABYLON_LOG_STREAM(logger, WARNING) << "this text also appear in a.b"; + ASSERT_TRUE(logger.initialized()); + ASSERT_EQ(::babylon::LogSeverity::WARNING, logger.min_severity()); + } + ASSERT_EQ( + "this text appear in root" + "this text also appear in root", + buffer.str()); + ASSERT_EQ( + "this text appear in a.b" + "this text also appear in a.b", + buffer2.str()); +} + +TEST_F(LoggerTest, concise_log_macro_use_root_logger) { + builder.set_log_stream_creator([this] { + auto ptr = new ::babylon::LogStream(buffer); + return ::std::unique_ptr<::babylon::LogStream>(ptr); + }); + builder.set_min_severity(::babylon::LogSeverity::INFO); + ::babylon::LoggerManager::instance().set_root_builder(::std::move(builder)); + ::babylon::LoggerManager::instance().apply(); + BABYLON_LOG(INFO) << "this text appear in root"; + ASSERT_EQ("this text appear in root", buffer.str()); +} diff --git a/test/logging/test_rolling_file_object.cpp b/test/logging/test_rolling_file_object.cpp new file mode 100644 index 00000000..950069c5 --- /dev/null +++ b/test/logging/test_rolling_file_object.cpp @@ -0,0 +1,82 @@ +#include "babylon/logging/rolling_file_object.h" + +#if __cplusplus >= 201703L +#include "gtest/gtest.h" + +#include +#include + +using ::babylon::FileObject; +using ::babylon::RollingFileObject; + +struct RollingFileObjectTest : public ::testing::Test { + virtual void SetUp() override { + ::std::filesystem::remove_all("log"); + rolling_object.set_directory("log"); + rolling_object.set_file_pattern("name.%Y%m%d-%H%M%S"); + object = &rolling_object; + } + + RollingFileObject rolling_object; + FileObject* object; +}; + +TEST_F(RollingFileObjectTest, file_first_create_when_get_trigger) { + object->check_and_get_file_descriptor(); + { + ::std::filesystem::directory_iterator dir {"log"}; + ASSERT_EQ(1, ::std::distance(begin(dir), end(dir))); + } +} + +TEST_F(RollingFileObjectTest, keep_file_dont_exceed_num) { + ::std::filesystem::create_directory("log"); + ::std::ofstream {"log/name.00000000-000000"}; + + ::std::vector old_fds; + rolling_object.set_max_file_number(3); + rolling_object.scan_and_tracking_existing_files(); + for (size_t i = 0; i < 50; ++i) { + auto [fd, old_fd] = object->check_and_get_file_descriptor(); + if (old_fd >= 0) { + old_fds.emplace_back(old_fd); + } + rolling_object.delete_expire_files(); + ::usleep(100 * 1000); + } + + { + ::std::filesystem::directory_iterator dir {"log"}; + ASSERT_EQ(3, ::std::distance(begin(dir), end(dir))); + } + + ASSERT_LE(4, old_fds.size()); + for (auto fd : old_fds) { + ::close(fd); + } +} + +TEST_F(RollingFileObjectTest, fd_refer_to_latest_file) { + int last_fd = -1; + ::std::vector old_fds; + rolling_object.set_max_file_number(1); + for (size_t i = 0; i < 30; ++i) { + auto [fd, old_fd] = object->check_and_get_file_descriptor(); + if (old_fd >= 0) { + old_fds.emplace_back(old_fd); + } + rolling_object.delete_expire_files(); + ::usleep(100 * 1000); + last_fd = fd; + } + + ::dprintf(last_fd, "this should appear in file\n"); + ::std::filesystem::directory_iterator dir {"log"}; + auto entry = *begin(dir); + ::std::ifstream ifs {entry.path()}; + ASSERT_TRUE(ifs); + ::std::string line; + ASSERT_TRUE(::std::getline(ifs, line)); + ASSERT_EQ("this should appear in file", line); +} +#endif // __cplusplus >= 201703L diff --git a/test/serialization/test_aggregate.cpp b/test/serialization/test_aggregate.cpp index e767f3da..b83b63cf 100644 --- a/test/serialization/test_aggregate.cpp +++ b/test/serialization/test_aggregate.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_array.cpp b/test/serialization/test_array.cpp index d3ff9b5f..f1d35b77 100644 --- a/test/serialization/test_array.cpp +++ b/test/serialization/test_array.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include diff --git a/test/serialization/test_list.cpp b/test/serialization/test_list.cpp index 9fb899a0..d703d04e 100644 --- a/test/serialization/test_list.cpp +++ b/test/serialization/test_list.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_map.cpp b/test/serialization/test_map.cpp index 5dd321c7..bca67727 100644 --- a/test/serialization/test_map.cpp +++ b/test/serialization/test_map.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_message.cpp b/test/serialization/test_message.cpp index 9fbf9b0a..e1609c4f 100644 --- a/test/serialization/test_message.cpp +++ b/test/serialization/test_message.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/text_format.h" diff --git a/test/serialization/test_set.cpp b/test/serialization/test_set.cpp index ba410ff2..12831d2f 100644 --- a/test/serialization/test_set.cpp +++ b/test/serialization/test_set.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_shared_ptr.cpp b/test/serialization/test_shared_ptr.cpp index f38e42ad..00298a07 100644 --- a/test/serialization/test_shared_ptr.cpp +++ b/test/serialization/test_shared_ptr.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_unique_ptr.cpp b/test/serialization/test_unique_ptr.cpp index c117fd1b..25e193be 100644 --- a/test/serialization/test_unique_ptr.cpp +++ b/test/serialization/test_unique_ptr.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h" diff --git a/test/serialization/test_vector.cpp b/test/serialization/test_vector.cpp index e5ff82ed..03aab58b 100644 --- a/test/serialization/test_vector.cpp +++ b/test/serialization/test_vector.cpp @@ -2,7 +2,7 @@ #if BABYLON_USE_PROTOBUF -#include "babylon/logging/interface.h" +#include "babylon/logging/logger.h" #include "gtest/gtest.h"