├── tools ├── CMakeLists.txt ├── README.md └── logroller │ ├── CMakeLists.txt │ ├── logroller_getopt.ggo │ ├── README.md │ ├── logroller.cc │ └── cmdline.h ├── .clang-format ├── doc ├── figures │ └── example.png └── api_usage_overview.md ├── tests ├── mpsc_benchmarks │ ├── CMakeLists.txt │ ├── mpsc_benchmarks_result_512kb_buffer.png │ ├── mpsc_benchmarks_result_64kb_buffer.png │ ├── readme.md │ └── mpsc_benchmarks.cc ├── CMakeLists.txt ├── file_test.cc ├── power_of_2_test.cc ├── spsc_ring_test.cc ├── ulog_test.c ├── ulog_test.cc └── mpsc_ring_test.cc ├── examples ├── CMakeLists.txt └── unix │ ├── ulog_example_asyn.cc │ └── ulog_example_rotate_file.cc ├── include └── ulog │ ├── file │ ├── rotation_strategy_base.h │ ├── sink_base.h │ ├── sink_limit_size_file.h │ ├── file_writer_base.h │ ├── rotation_strategy_rename.h │ ├── file_writer_buffered_io.h │ ├── file_writer_unbuffered_io.h │ ├── rotation_strategy_incremental.h │ ├── sink_rotating_file.h │ ├── file.h │ ├── file_writer_zstd.h │ └── sink_async_wrapper.h │ ├── error.h │ ├── queue │ ├── intrusive_struct.h │ ├── memory_logger.h │ ├── lite_notifier.h │ ├── power_of_two.h │ ├── spsc_ring.h │ ├── fifo_power_of_two.h │ └── mpsc_ring.h │ ├── status.h │ ├── ulog.h │ └── ulog_private.h ├── cmake ├── zstd.cmake ├── policy.cmake ├── googletest.cmake └── git_version.cmake ├── LICENSE ├── CMakeLists.txt ├── .github └── workflows │ ├── tests.yml │ └── release.yml ├── README.md ├── CHANGELOG.md ├── src └── ulog.c └── .gitignore /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(logroller) 2 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Command-line tool made using ulog 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 120 3 | -------------------------------------------------------------------------------- /doc/figures/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnfeng0/ulog/HEAD/doc/figures/example.png -------------------------------------------------------------------------------- /tools/logroller/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(logroller logroller.cc cmdline.c) 2 | target_link_libraries(logroller ulog pthread zstd::libzstd_shared) 3 | -------------------------------------------------------------------------------- /tests/mpsc_benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ulog_mpsc_benchmarks mpsc_benchmarks.cc) 2 | target_link_libraries(ulog_mpsc_benchmarks GTest::gtest_main ulog) 3 | 4 | -------------------------------------------------------------------------------- /tests/mpsc_benchmarks/mpsc_benchmarks_result_512kb_buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnfeng0/ulog/HEAD/tests/mpsc_benchmarks/mpsc_benchmarks_result_512kb_buffer.png -------------------------------------------------------------------------------- /tests/mpsc_benchmarks/mpsc_benchmarks_result_64kb_buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnfeng0/ulog/HEAD/tests/mpsc_benchmarks/mpsc_benchmarks_result_64kb_buffer.png -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ulog example: Asynchronous output 2 | add_executable(ulog_example_asyn unix/ulog_example_asyn.cc) 3 | target_link_libraries(ulog_example_asyn ulog pthread) 4 | 5 | # ulog example: Asynchronous output to file 6 | add_executable(ulog_example_rotate_file unix/ulog_example_rotate_file.cc) 7 | target_link_libraries(ulog_example_rotate_file ulog pthread) 8 | -------------------------------------------------------------------------------- /include/ulog/file/rotation_strategy_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ulog/status.h" 4 | 5 | namespace ulog::file { 6 | 7 | class RotationStrategyBase { 8 | public: 9 | RotationStrategyBase() = default; 10 | virtual ~RotationStrategyBase() = default; 11 | 12 | virtual Status Rotate() = 0; 13 | virtual std::string LatestFilename() = 0; 14 | }; 15 | 16 | } // namespace ulog::file -------------------------------------------------------------------------------- /include/ulog/error.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawn on 25-1-19. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | // Precompiler define to get only filename; 10 | #if defined(__FILE_NAME__) 11 | #define ULOG_ERROR(fmt, ...) fprintf(stderr, "%s:%d " fmt "\n", __FILE_NAME__, __LINE__, ##__VA_ARGS__) 12 | #else 13 | #define ULOG_ERROR(fmt, ...) fprintf(stderr, "%s:%d " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__) 14 | #endif 15 | -------------------------------------------------------------------------------- /cmake/zstd.cmake: -------------------------------------------------------------------------------- 1 | find_package(zstd) 2 | if (NOT zstd_FOUND) 3 | include(FetchContent) 4 | FetchContent_Declare( 5 | zstd 6 | URL https://github.com/facebook/zstd/archive/refs/tags/v1.5.6.zip 7 | SOURCE_SUBDIR build/cmake 8 | ) 9 | FetchContent_MakeAvailable(zstd) 10 | add_library(zstd::libzstd_shared ALIAS libzstd_shared) 11 | add_library(zstd::libzstd_static ALIAS libzstd_static) 12 | endif () 13 | -------------------------------------------------------------------------------- /cmake/policy.cmake: -------------------------------------------------------------------------------- 1 | # copy from https://github.com/sotatek-dev/solidity/blob/develop/cmake/EthPolicy.cmake 2 | 3 | # it must be a macro cause policies have scopes 4 | # http://www.cmake.org/cmake/help/v3.0/command/cmake_policy.html 5 | macro (use_cmake_policy) 6 | # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: 7 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") 8 | cmake_policy(SET CMP0135 NEW) 9 | endif() 10 | endmacro() 11 | -------------------------------------------------------------------------------- /cmake/googletest.cmake: -------------------------------------------------------------------------------- 1 | # Google Test 2 | find_package(GTest) 3 | if (NOT GTest_FOUND) 4 | include(FetchContent) 5 | FetchContent_Declare( 6 | googletest 7 | URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip 8 | ) 9 | # For Windows: Prevent overriding the parent project's compiler/linker settings 10 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 11 | FetchContent_MakeAvailable(googletest) 12 | endif () 13 | -------------------------------------------------------------------------------- /cmake/git_version.cmake: -------------------------------------------------------------------------------- 1 | macro(set_git_version version) 2 | # Generate git version info 3 | if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git) 4 | execute_process( 5 | COMMAND git describe --always --tags --long --dirty=+ 6 | OUTPUT_VARIABLE ${version} 7 | OUTPUT_STRIP_TRAILING_WHITESPACE 8 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 9 | ) 10 | else () 11 | set(${version} "v0.0.0-0-not_git_repo") 12 | endif () 13 | endmacro() 14 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(mpsc_benchmarks) 2 | 3 | # ulog build test 4 | add_executable(ulog_test ulog_test.c) 5 | target_link_libraries(ulog_test ulog) 6 | add_test(test_c_compile ulog_test) 7 | 8 | add_executable(ulog_test_cpp ulog_test.cc) 9 | target_link_libraries(ulog_test_cpp ulog) 10 | add_test(test_cpp_compile ulog_test_cpp) 11 | 12 | add_executable(ulog_unit_test file_test.cc mpsc_ring_test.cc spsc_ring_test.cc power_of_2_test.cc) 13 | target_link_libraries(ulog_unit_test GTest::gtest_main ulog) 14 | add_test(ulog_unit_test ulog_unit_test) 15 | -------------------------------------------------------------------------------- /include/ulog/file/sink_base.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 25-1-27. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "ulog/status.h" 8 | 9 | namespace ulog::file { 10 | 11 | class SinkBase { 12 | public: 13 | SinkBase() = default; 14 | virtual ~SinkBase() = default; 15 | 16 | // Disable copy and assign. 17 | SinkBase(const SinkBase&) = delete; 18 | SinkBase& operator=(const SinkBase&) = delete; 19 | 20 | /** 21 | * Sink data to next sinker 22 | * @param data The data to write. 23 | * @param len The length of the data. 24 | * @return Status::OK() if success 25 | * @return Status::Full() if the file is full 26 | */ 27 | virtual Status SinkIt(const void* data, size_t len) = 0; 28 | virtual Status SinkIt(const void* data, size_t len, std::chrono::milliseconds timeout) = 0; 29 | 30 | /** 31 | * Flush the file. 32 | * @return 0 if success 33 | * @return Negative numbers are errors, use Status to judge 34 | */ 35 | virtual Status Flush() = 0; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /include/ulog/queue/intrusive_struct.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copy from 3 | // https://github.com/shawnfeng0/intrusive_list/blob/3cc9512eee33c72a3011a0baa5b351cbd8d65ad9/include/intrusive_list/common.h 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace intrusive { 12 | 13 | template 14 | static constexpr ptrdiff_t offset_of(const Member Type::*member) { 15 | return reinterpret_cast(&(reinterpret_cast(0)->*member)); 16 | } 17 | 18 | template 19 | static constexpr Type *owner_of(const Member *ptr, const Member Type::*member) { 20 | return reinterpret_cast(reinterpret_cast(ptr) - offset_of(member)); 21 | } 22 | 23 | template 24 | static constexpr Type *owner_of(const void *ptr, const Member Type::*member) { 25 | return reinterpret_cast(reinterpret_cast(ptr) - offset_of(member)); 26 | } 27 | 28 | } // namespace intrusive 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Shawn Feng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/ulog/queue/memory_logger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawn on 24-12-6. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "intrusive_struct.h" 8 | #include "power_of_two.h" 9 | 10 | template 11 | class MemoryLogger { 12 | static_assert(ulog::queue::is_power_of_2(size)); 13 | 14 | struct Item { 15 | T data; 16 | uint32_t seq{0}; 17 | std::atomic_bool is_writing{false}; 18 | }; 19 | 20 | public: 21 | T *TryReserve() { 22 | const auto head = head_.fetch_add(1, std::memory_order_relaxed); 23 | auto *item = &items_[head & (size - 1)]; 24 | 25 | bool expected = false; 26 | const bool result = item->is_writing.compare_exchange_strong(expected, true, std::memory_order_acquire); 27 | 28 | if (!result) return nullptr; 29 | 30 | item->seq = head; 31 | return &item->data; 32 | } 33 | 34 | void Commit(T *ptr) { intrusive::owner_of(ptr, &Item::data)->is_writing.store(false, std::memory_order_release); } 35 | 36 | const T *Get(const size_t index) const { return &items_[index & (size - 1)].data; } 37 | 38 | private: 39 | Item items_[size]; 40 | std::atomic_uint32_t head_{0}; 41 | }; 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5...3.31) 2 | 3 | # Set cmake_policies 4 | include(cmake/policy.cmake) 5 | use_cmake_policy() 6 | 7 | project(ulog) 8 | 9 | set(CMAKE_C_STANDARD 11) 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | 13 | option(ULOG_BUILD_EXAMPLES "Build examples" OFF) 14 | option(ULOG_BUILD_TESTS "Build tests" OFF) 15 | option(ULOG_BUILD_TOOLS "Build tools" ON) 16 | 17 | # Generate git version info 18 | include(cmake/git_version.cmake) 19 | set_git_version(ULOG_GIT_TAG) 20 | message(STATUS "ulog version: ${ULOG_GIT_TAG}") 21 | 22 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 23 | add_compile_options(-Wextra -Wall) 24 | 25 | # ulog library 26 | add_library(ulog src/ulog.c) 27 | target_include_directories(ulog PUBLIC include) 28 | 29 | if (ULOG_BUILD_EXAMPLES OR ULOG_BUILD_TOOLS) 30 | include(cmake/zstd.cmake) 31 | endif () 32 | 33 | if (ULOG_BUILD_EXAMPLES) 34 | add_subdirectory(examples) 35 | endif () 36 | 37 | if (ULOG_BUILD_TESTS) 38 | include(cmake/googletest.cmake) 39 | enable_testing() 40 | add_subdirectory(tests) 41 | endif () 42 | 43 | if (ULOG_BUILD_TOOLS) 44 | add_subdirectory(tools) 45 | install(TARGETS logroller 46 | RUNTIME DESTINATION bin) 47 | endif () 48 | 49 | # install 50 | install(TARGETS ulog 51 | ARCHIVE DESTINATION lib) 52 | install(DIRECTORY include/ulog DESTINATION include) 53 | -------------------------------------------------------------------------------- /tests/file_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-6-27. 3 | // 4 | #include "ulog/file/file.h" 5 | 6 | #include 7 | 8 | using namespace ulog::file; 9 | 10 | TEST(file, SplitByExtension) { 11 | ASSERT_EQ(SplitByExtension("mylog.txt"), std::make_tuple("mylog", ".txt")); 12 | ASSERT_EQ(SplitByExtension("mylog"), std::make_tuple("mylog", "")); 13 | ASSERT_EQ(SplitByExtension("mylog."), std::make_tuple("mylog.", "")); 14 | ASSERT_EQ(SplitByExtension("/dir1/dir2/mylog.txt"), std::make_tuple("/dir1/dir2/mylog", ".txt")); 15 | 16 | ASSERT_EQ(SplitByExtension(".mylog"), std::make_tuple(".mylog", "")); 17 | ASSERT_EQ(SplitByExtension(".mylog.zst.tar"), std::make_tuple(".mylog", ".zst.tar")); 18 | ASSERT_EQ(SplitByExtension("a.zst.tar"), std::make_tuple("a", ".zst.tar")); 19 | ASSERT_EQ(SplitByExtension("/my_folder/a.zst.tar"), std::make_tuple("/my_folder/a", ".zst.tar")); 20 | ASSERT_EQ(SplitByExtension("/my_folder/a"), std::make_tuple("/my_folder/a", "")); 21 | ASSERT_EQ(SplitByExtension("my_folder/.mylog"), std::make_tuple("my_folder/.mylog", "")); 22 | ASSERT_EQ(SplitByExtension("my_folder/.mylog.txt"), std::make_tuple("my_folder/.mylog", ".txt")); 23 | 24 | ASSERT_EQ(SplitByExtension("my_folder/.mylog.txt.zst"), std::make_tuple("my_folder/.mylog", ".txt.zst")); 25 | ASSERT_EQ(SplitByExtension("/etc/rc.d/somelogfile"), std::make_tuple("/etc/rc.d/somelogfile", "")); 26 | } 27 | -------------------------------------------------------------------------------- /include/ulog/file/sink_limit_size_file.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2020-05-25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "file_writer_buffered_io.h" 13 | #include "rotation_strategy_incremental.h" 14 | #include "rotation_strategy_rename.h" 15 | #include "sink_base.h" 16 | 17 | namespace ulog::file { 18 | 19 | class SinkLimitSizeFile final : public SinkBase { 20 | public: 21 | /** 22 | * Outputs data to a file and rotates the file 23 | * @param writer File writer 24 | * @param filename File name 25 | * @param file_size File write data size limit 26 | */ 27 | SinkLimitSizeFile(std::unique_ptr &&writer, std::string filename, const std::size_t file_size) 28 | : file_size_(file_size), 29 | writer_(std::move(writer)), 30 | filename_(std::move(filename)) { 31 | writer_->Open(filename_, true, file_size_); 32 | } 33 | 34 | Status SinkIt(const void *data, const size_t len, [[maybe_unused]] std::chrono::milliseconds timeout) override { 35 | return SinkIt(data, len); 36 | } 37 | 38 | Status SinkIt(const void *data, const size_t len) override { 39 | return writer_->Write(data, len); 40 | } 41 | 42 | [[nodiscard]] Status Flush() override { return writer_->Flush(); } 43 | 44 | private: 45 | const std::size_t file_size_; 46 | std::unique_ptr writer_; 47 | std::string filename_; 48 | }; 49 | 50 | } // namespace ulog::file 51 | -------------------------------------------------------------------------------- /tests/mpsc_benchmarks/readme.md: -------------------------------------------------------------------------------- 1 | # MPSC Benchmarks 2 | 3 | MPSC is a lock-free queue implementation. Its internal logic is more complex than a traditional lock-based queue, and it only demonstrates its maximum performance advantage when there is no waiting. If waiting occurs, its performance can actually be lower than kfifo+mutex. This explains why, in the later stages of the 64KB test, MPSC consistently underperforms compared to the traditional ring buffer implementation. 4 | 5 | In contrast, with a larger buffer size (e.g., 512KB), the queue is less likely to be full, so threads spend less time waiting. This allows MPSC to fully demonstrate its performance advantage, reaching up to 1.5x the performance of the traditional ring buffer (limited by CPU cache coherence, so performance cannot increase linearly). 6 | 7 | ## Benchmark Description 8 | - Benchmark file: `mpsc_benchmarks.cc` 9 | - Compares UMQ (multi-producer queue) and kfifo+mutex (ring buffer + mutex) under varying thread counts. 10 | - Each thread publishes 10*1024 packets, each packet is 8~256 bytes. 11 | - The `ratio` column uses color gradients to indicate performance difference: 12 | - Negative: white-to-red gradient (UMQ is slower than kfifo+mutex) 13 | - Positive: white-to-green gradient (UMQ is faster than kfifo+mutex) 14 | 15 | ## Results 16 | 17 | ### Buffer Size = 64KB 18 | ![64KB Benchmark Result](mpsc_benchmarks_result_64kb_buffer.png) 19 | 20 | ### Buffer Size = 512KB 21 | ![512KB Benchmark Result](mpsc_benchmarks_result_512kb_buffer.png) 22 | -------------------------------------------------------------------------------- /tests/power_of_2_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-6-27. 3 | // 4 | #include 5 | 6 | #include "ulog/queue/power_of_two.h" 7 | 8 | TEST(PowOfTwo, IsInRange) { 9 | ASSERT_EQ(ulog::queue::IsInRange(10, 9, 20), false); 10 | ASSERT_EQ(ulog::queue::IsInRange(10, 10, 20), true); 11 | ASSERT_EQ(ulog::queue::IsInRange(10, 11, 20), true); 12 | 13 | ASSERT_EQ(ulog::queue::IsInRange(10, 19, 20), true); 14 | ASSERT_EQ(ulog::queue::IsInRange(10, 20, 20), true); 15 | ASSERT_EQ(ulog::queue::IsInRange(10, 21, 20), false); 16 | 17 | ASSERT_EQ(ulog::queue::IsInRange(10, 9, 10), false); 18 | ASSERT_EQ(ulog::queue::IsInRange(10, 10, 10), true); 19 | ASSERT_EQ(ulog::queue::IsInRange(10, 11, 10), false); 20 | 21 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, UINT32_MAX - 2, 1), false); 22 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, UINT32_MAX - 1, 1), true); 23 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, UINT32_MAX, 1), true); 24 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, 0, 1), true); 25 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, 1, 1), true); 26 | ASSERT_EQ(ulog::queue::IsInRange(UINT32_MAX - 1, 2, 1), false); 27 | } 28 | 29 | TEST(PowOfTwo, IsPassed) { 30 | ASSERT_EQ(ulog::queue::IsPassed(1, 1), true); 31 | 32 | ASSERT_EQ(ulog::queue::IsPassed(2, 1), false); 33 | ASSERT_EQ(ulog::queue::IsPassed(1, 2), true); 34 | 35 | ASSERT_EQ(ulog::queue::IsPassed(1, 0xFF000000), false); 36 | ASSERT_EQ(ulog::queue::IsPassed(0xFF000000, 1), true); 37 | 38 | ASSERT_EQ(ulog::queue::IsPassed(1, 0xFFFFFFFF), false); 39 | ASSERT_EQ(ulog::queue::IsPassed(0xFFFFFFFF, 1), true); 40 | } 41 | -------------------------------------------------------------------------------- /include/ulog/queue/lite_notifier.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawn on 24-11-17. 3 | // 4 | 5 | #pragma once 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ulog { 11 | 12 | /** 13 | * @brief In conjunction with a lock-free queue, this notifier should be used when the queue is full or empty, as 14 | * retries can affect performance. 15 | */ 16 | class LiteNotifier { 17 | public: 18 | LiteNotifier() : waiters_(0) {} 19 | 20 | template 21 | void wait(Predicate pred) { 22 | if (pred()) return; 23 | 24 | std::unique_lock lock(mutex_); 25 | waiters_.fetch_add(1, std::memory_order_release); 26 | cv_.wait(lock, pred); 27 | waiters_.fetch_sub(1, std::memory_order_release); 28 | } 29 | 30 | template 31 | bool wait_for(const std::chrono::duration& timeout, Predicate pred) { 32 | if (pred()) return true; 33 | 34 | std::unique_lock lock(mutex_); 35 | waiters_.fetch_add(1, std::memory_order_release); 36 | const bool result = cv_.wait_for(lock, timeout, pred); 37 | waiters_.fetch_sub(1, std::memory_order_release); 38 | return result; 39 | } 40 | 41 | void notify_all() { 42 | if (waiters_.load(std::memory_order_acquire) > 0) { 43 | std::lock_guard lock(mutex_); 44 | cv_.notify_all(); 45 | } 46 | } 47 | 48 | void notify_one() { 49 | if (waiters_.load(std::memory_order_acquire) > 0) { 50 | std::lock_guard lock(mutex_); 51 | cv_.notify_one(); 52 | } 53 | } 54 | 55 | private: 56 | std::atomic waiters_; 57 | std::mutex mutex_; 58 | std::condition_variable cv_; 59 | }; 60 | 61 | } // namespace ulog -------------------------------------------------------------------------------- /include/ulog/file/file_writer_base.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2020-05-25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "ulog/status.h" 10 | 11 | namespace ulog::file { 12 | 13 | class FileWriterBase { 14 | public: 15 | FileWriterBase() = default; 16 | virtual ~FileWriterBase() = default; 17 | 18 | // Disable copy and assign. 19 | FileWriterBase(const FileWriterBase&) = delete; 20 | FileWriterBase& operator=(const FileWriterBase&) = delete; 21 | 22 | static constexpr size_t kNoLimit = static_cast(-1); 23 | 24 | /** 25 | * Open the file. 26 | * @param filename File name. 27 | * @param truncate If true, the file will be truncated. 28 | * @param limit File write data size limit 29 | * @return 0 if success 30 | * @return Negative numbers are errors, use Status to judge 31 | */ 32 | virtual Status Open(const std::string& filename, bool truncate, size_t limit) = 0; 33 | 34 | /** 35 | * Flush the file. 36 | * @return 0 if success 37 | * @return Negative numbers are errors, use Status to judge 38 | */ 39 | virtual Status Flush() = 0; 40 | 41 | /** 42 | * Close the file. 43 | * @return 0 if success 44 | * @return Negative numbers are errors, use Status to judge 45 | */ 46 | virtual Status Close() = 0; 47 | 48 | /** 49 | * Write data to the file. 50 | * @param data The data to write. 51 | * @param len The length of the data. 52 | * @return Status::OK() if success 53 | * @return Status::Full() if the file is full 54 | */ 55 | virtual Status Write(const void* data, std::size_t len) = 0; 56 | 57 | /** 58 | * Get the current file write position(filesize). 59 | * @return The current file write position. 60 | */ 61 | virtual size_t TellP() = 0; 62 | }; 63 | } // namespace ulog::file 64 | -------------------------------------------------------------------------------- /tools/logroller/logroller_getopt.ggo: -------------------------------------------------------------------------------- 1 | package "logroller" 2 | version "0.6.1" 3 | 4 | usage "logroller -f PATH [OPTIONS]..." 5 | purpose "Loop logging of standard inputs to several files." 6 | description "Examples:\n\ 7 | your_program | logroller --file-path=log.txt --file-size=1MB --max-files=8\n\ 8 | your_program | logroller -f log.txt -s 1MB -n 8 --zstd-compress" 9 | 10 | section "File options" 11 | option "file-path" f "File path to record log" 12 | string typestr="PATH" required 13 | option "file-size" s "Size of each file" 14 | string typestr="SIZE" optional default="1MB" 15 | option "max-files" n "Maximum number of files" 16 | int typestr="NUM" optional default="8" 17 | option "flush-interval" i "Time interval between flushing to disk" 18 | string typestr="time" optional default="1s" 19 | option "rotation-strategy" - "File rotation strategy:\n\ 20 | rename: log.1.txt -> log.2.txt\n\ 21 | incremental: log-24.txt ... log-34.txt" 22 | string typestr="STR" values="rename","incremental" default="rename" optional 23 | option "rotate-first" - "Should rotate first before write" 24 | flag off 25 | 26 | section "Buffer options" 27 | option "fifo-size" c "Size of the FIFO buffer" 28 | string typestr="SIZE" optional default="32KB" 29 | 30 | section "Compress options" 31 | option "zstd-compress" - "Compress with zstd" 32 | flag off 33 | option "zstd-params" - "Parameters for zstd compression,\ 34 | \nlarger == more compression and memory (e.g., level=3,window-log=21,chain-log=16,hash-log=17)" 35 | string typestr="PARAMS" optional dependon="zstd-compress" 36 | 37 | text "\nThe SIZE parameter units are K, M, G (power of 1024). If the unit is not specified, the default is bytes." 38 | text "\nThe TIME parameter units are s, sec, ms, min, hour. If the unit is not specified, the default is seconds." 39 | -------------------------------------------------------------------------------- /include/ulog/file/rotation_strategy_rename.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ulog/file/file.h" 6 | #include "ulog/file/rotation_strategy_base.h" 7 | 8 | namespace ulog::file { 9 | 10 | class RotationStrategyRename final : public RotationStrategyBase { 11 | public: 12 | RotationStrategyRename(std::string basename, std::string ext, const size_t max_files) 13 | : basename_(std::move(basename)), ext_(std::move(ext)), max_files_(max_files == 0 ? 1 : max_files) {} 14 | 15 | // Rotate files: 16 | // log.txt -> log.1.txt 17 | // log.1.txt -> log.2.txt 18 | // log.2.txt -> log.3.txt 19 | // log.3.txt -> delete 20 | // "tail -f" may be interrupted when rename is executed, and "tail -F" can 21 | // be used instead, but some "-F" implementations (busybox tail) cannot 22 | // obtain all logs in real time. 23 | Status Rotate() override { 24 | for (size_t i = max_files_ - 1; i > 0; --i) { 25 | std::string src = get_filename(i - 1); 26 | if (!path_exists(src)) { 27 | continue; 28 | } 29 | RenameFile(src, get_filename(i)); 30 | } 31 | 32 | // Try to clear the files that were previously configured too much 33 | size_t not_exists = 0; 34 | for (size_t i = max_files_;; ++i) { 35 | std::string filename = get_filename(i); 36 | if (!path_exists(filename)) { 37 | if (++not_exists == 2) break; 38 | continue; 39 | } 40 | std::remove(filename.c_str()); 41 | } 42 | return Status::OK(); 43 | } 44 | 45 | std::string LatestFilename() override { return basename_ + ext_; } 46 | 47 | private: 48 | [[nodiscard]] std::string get_filename(const size_t index) const { 49 | if (index == 0u) { 50 | return basename_ + ext_; 51 | } 52 | 53 | return basename_ + "." + std::to_string(index) + ext_; 54 | } 55 | std::string basename_; 56 | std::string ext_; 57 | size_t max_files_; 58 | }; 59 | 60 | } // namespace ulog::file -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | test: 15 | name: ${{ matrix.os }}.${{ matrix.compiler.name }} 16 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 17 | # You can convert this to a matrix build if you need cross-platform coverage. 18 | # See: https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#using-a-matrix-strategy 19 | strategy: 20 | matrix: 21 | os: [ ubuntu-latest, macos-latest ] 22 | compiler: 23 | - { name: GNU, CC: gcc, CXX: g++, GCOV: gcov } 24 | - { name: LLVM, CC: clang, CXX: clang++ } 25 | runs-on: ${{ matrix.os }} 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Configure CMake 31 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 32 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 33 | run: 'cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DULOG_BUILD_TESTS=ON 34 | -DCMAKE_C_COMPILER=${{matrix.compiler.CC}} -DCMAKE_CXX_COMPILER=${{matrix.compiler.CXX}}' 35 | 36 | - name: Build 37 | # Build your program with the given configuration 38 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 39 | 40 | - name: Test 41 | working-directory: ${{github.workspace}}/build 42 | # Execute tests defined by the CMake configuration. 43 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 44 | run: ctest -C ${{env.BUILD_TYPE}} --extra-verbose 45 | -------------------------------------------------------------------------------- /tests/spsc_ring_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-3-31. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ulog/queue/spsc_ring.h" 12 | #include "ulog/ulog.h" 13 | 14 | template 15 | static void spsc(const uint32_t buffer_size, const uint64_t limit) { 16 | auto buffer = T::Create(buffer_size); 17 | 18 | std::thread write_thread{[&] { 19 | typename T::Producer producer(buffer); 20 | std::random_device rd; 21 | std::mt19937 gen(rd()); 22 | std::uniform_int_distribution dis(1, std::max(buffer_size / 100, 2)); 23 | uint64_t write_count = 0; 24 | while (write_count < limit) { 25 | size_t size = dis(gen); 26 | 27 | decltype(producer.Reserve(size)) data; 28 | while ((data = producer.Reserve(size)) == nullptr) { 29 | std::this_thread::yield(); 30 | } 31 | for (size_t i = 0; i < size; ++i) data[i] = write_count++; 32 | producer.Commit(data, size); 33 | } 34 | }}; 35 | 36 | std::thread read_thread{[&] { 37 | typename T::Consumer consumer(buffer); 38 | uint64_t read_count = 0; 39 | while (read_count < limit) { 40 | auto data = consumer.Read(); 41 | 42 | if (!data) { 43 | std::this_thread::yield(); 44 | continue; 45 | } 46 | 47 | while (const auto packet = data.next()) { 48 | for (size_t i = 0; i < packet.size; ++i) { 49 | ASSERT_EQ(packet.data[i], read_count++); 50 | } 51 | } 52 | consumer.Release(data); 53 | } 54 | }}; 55 | 56 | write_thread.join(); 57 | read_thread.join(); 58 | printf("Finished test: buffer_size: %u, limit: %" PRIu64 "\n", buffer_size, limit); 59 | } 60 | 61 | TEST(BipBufferTestSingle, singl_producer_single_consumer) { 62 | LOGGER_TIME_CODE({ spsc>(1 << 4, 1024 * 1024); }); 63 | LOGGER_TIME_CODE({ spsc>(1 << 6, 1024 * 1024); }); 64 | LOGGER_TIME_CODE({ spsc>(1 << 8, 1024 * 1024); }); 65 | LOGGER_TIME_CODE({ spsc>(1 << 10, 1024 * 1024); }); 66 | LOGGER_TIME_CODE({ spsc>(1 << 12, 1024 * 1024); }); 67 | LOGGER_TIME_CODE({ spsc>(1 << 14, 1024 * 1024); }); 68 | LOGGER_TIME_CODE({ spsc>(1 << 16, 1024 * 1024); }); 69 | } 70 | -------------------------------------------------------------------------------- /include/ulog/file/file_writer_buffered_io.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2020-05-25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "file.h" 10 | #include "file_writer_base.h" 11 | 12 | namespace ulog::file { 13 | 14 | class FileWriterBufferedIo final : public FileWriterBase { 15 | public: 16 | explicit FileWriterBufferedIo() : file_limit_size_(kNoLimit) {} 17 | ~FileWriterBufferedIo() override { FileWriterBufferedIo::Close(); } 18 | 19 | Status Open(const std::string &filename, const bool truncate, size_t limit) override { 20 | if (file_stream_.is_open()) { 21 | return Status::Corruption("File already opened!", filename); 22 | } 23 | 24 | // create containing folder if not exists already. 25 | if (!create_dir(file::dir_name(filename))) { 26 | return Status::Corruption("Error creating directory", filename); 27 | } 28 | 29 | file_stream_.open(filename, truncate ? std::ios::trunc : std::ios::app); 30 | if (!file_stream_) { 31 | return Status::IOError("Error opening file", filename); 32 | } 33 | 34 | file_write_size_ = file_stream_.tellp(); 35 | file_limit_size_ = limit; 36 | return Status::OK(); 37 | } 38 | 39 | Status Write(const void *data, const size_t length) override { 40 | if (!file_stream_.is_open()) { 41 | return Status::Corruption("Not opened"); 42 | } 43 | 44 | if (file_write_size_ + length > file_limit_size_) { 45 | return Status::Full(); 46 | } 47 | 48 | if (!file_stream_.write(static_cast(data), length).good()) { 49 | return Status::IOError("Error writing to file", std::to_string(file_stream_.rdstate())); 50 | } 51 | 52 | file_write_size_ += length; 53 | return Status::OK(); 54 | } 55 | 56 | Status Flush() override { 57 | if (!file_stream_.is_open()) { 58 | return Status::Corruption("Not opened"); 59 | } 60 | 61 | file_stream_.flush(); 62 | return Status::OK(); 63 | } 64 | 65 | Status Close() override { 66 | if (!file_stream_.is_open()) { 67 | return Status::Corruption("Not opened"); 68 | } 69 | 70 | file_stream_.close(); 71 | 72 | return Status::OK(); 73 | } 74 | 75 | size_t TellP() override { return file_write_size_; } 76 | 77 | private: 78 | // config 79 | size_t file_limit_size_; 80 | 81 | std::ofstream file_stream_; 82 | size_t file_write_size_{0}; 83 | }; 84 | } // namespace ulog::file 85 | -------------------------------------------------------------------------------- /include/ulog/queue/power_of_two.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-6-25. 3 | // 4 | 5 | #pragma once 6 | #include 7 | 8 | namespace ulog { 9 | namespace queue { 10 | 11 | /** 12 | * Data structure used when reading data externally 13 | */ 14 | template 15 | struct Packet { 16 | explicit Packet(const size_t s = 0, T *d = nullptr) : size(s), data(d) {} 17 | Packet(const Packet &other) = default; 18 | Packet &operator=(const Packet &other) = default; 19 | 20 | Packet(Packet &&other) noexcept : size(other.size), data(other.data) { 21 | other.data = nullptr; 22 | other.size = 0; 23 | } 24 | 25 | explicit operator bool() const noexcept { return data != nullptr; } 26 | size_t size = 0; 27 | T *data = nullptr; 28 | }; 29 | 30 | template 31 | static inline constexpr bool is_power_of_2(T x) { 32 | return ((x) != 0 && (((x) & ((x)-1)) == 0)); 33 | } 34 | 35 | template 36 | static inline auto RoundPowOfTwo(T n) -> decltype(n) { 37 | uint64_t value = n; 38 | 39 | // Fill 1 40 | value |= value >> 1U; 41 | value |= value >> 2U; 42 | value |= value >> 4U; 43 | value |= value >> 8U; 44 | value |= value >> 16U; 45 | value |= value >> 32U; 46 | 47 | // Unable to round-up, take the value of round-down 48 | if (decltype(n)(value + 1) == 0) { 49 | value >>= 1U; 50 | } 51 | 52 | return value + 1; 53 | } 54 | 55 | static inline auto RoundUpPowOfTwo(uint32_t n) -> decltype(n) { 56 | if (n == 0) return 1; 57 | 58 | // Avoid is already a power of 2 59 | return RoundPowOfTwo(n - 1); 60 | } 61 | 62 | static inline auto RoundDownPowOfTwo(uint32_t n) -> decltype(n) { return RoundPowOfTwo(n >> 1U); } 63 | 64 | /** 65 | * @brief Check if value is in range [left, right], considering the wraparound 66 | * case when right < left (overflow) 67 | */ 68 | static inline bool IsInRange(unsigned left, unsigned value, unsigned right) { 69 | if (right >= left) { 70 | // Normal 71 | return (left <= value) && (value <= right); 72 | } else { 73 | // Maybe the data overflowed and a wraparound occurred 74 | return (left <= value) || (value <= right); 75 | } 76 | } 77 | 78 | /** 79 | * @brief Check if the tail has passed the head, considering the wraparound case when tail < head (overflow) 80 | */ 81 | static bool inline IsPassed(const uint32_t head, const uint32_t tail) { return tail - head < (1U << 31); } 82 | 83 | } // namespace queue 84 | } // namespace ulog 85 | -------------------------------------------------------------------------------- /examples/unix/ulog_example_asyn.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "ulog/queue/fifo_power_of_two.h" 7 | #include "ulog/ulog.h" 8 | 9 | void *ulog_asyn_thread(void *arg) { 10 | auto &fifo = *(ulog::FifoPowerOfTwo *)(arg); 11 | 12 | char str[1024]; 13 | 14 | size_t empty_times = 0; 15 | while (empty_times < 2) { 16 | auto len = fifo.OutputWaitIfEmpty(str, sizeof(str) - 1, 100); 17 | if (len > 0) { 18 | str[len] = '\0'; 19 | printf("%s", str); 20 | empty_times = 0; 21 | } else { 22 | ++empty_times; 23 | } 24 | } 25 | 26 | printf("fifo.num_dropped():%zu, fifo.peak():%zu, fifo.size():%zu", fifo.num_dropped(), fifo.peak(), fifo.size()); 27 | return nullptr; 28 | } 29 | 30 | int main() { 31 | auto &fifo = *new ulog::FifoPowerOfTwo{32768}; 32 | 33 | // Initial logger 34 | logger_set_user_data(ULOG_GLOBAL, &fifo); 35 | logger_set_output_callback(ULOG_GLOBAL, [](void *user_data, const char *str) { 36 | auto &fifo = *(ulog::FifoPowerOfTwo *)(user_data); 37 | return (int)fifo.InputPacketOrDrop(str, strlen(str)); 38 | }); 39 | logger_set_flush_callback(ULOG_GLOBAL, [](void *user_data) { 40 | auto &fifo = *(ulog::FifoPowerOfTwo *)(user_data); 41 | fifo.Flush(); 42 | }); 43 | 44 | pthread_t tid; 45 | pthread_create(&tid, nullptr, ulog_asyn_thread, &fifo); 46 | 47 | uint32_t num_threads = 20; 48 | while (num_threads--) { 49 | pthread_t tid_output; 50 | pthread_create( 51 | &tid_output, nullptr, 52 | [](void *unused) -> void * { 53 | (void)unused; 54 | double pi = 3.14159265; 55 | LOGGER_DEBUG("PI = %.3f", pi); 56 | LOGGER_RAW("PI = %.3f\r\n", pi); 57 | 58 | // Output debugging expression 59 | LOGGER_TOKEN(pi); 60 | LOGGER_TOKEN(50 * pi / 180); 61 | LOGGER_TOKEN(&pi); // print address of pi 62 | 63 | const char *text = "Ulog is a micro log library."; 64 | LOGGER_TOKEN(text); 65 | 66 | // Hex dump 67 | LOGGER_HEX_DUMP(text, 45, 16); 68 | 69 | // Output multiple tokens to one line 70 | time_t now = 1577259816; 71 | struct tm lt = *localtime(&now); 72 | 73 | LOGGER_MULTI_TOKEN(lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday); 74 | LOGGER_MULTI_TOKEN(lt.tm_wday, lt.tm_hour, lt.tm_min, lt.tm_sec); 75 | 76 | return nullptr; 77 | }, 78 | nullptr); 79 | } 80 | 81 | pthread_exit(nullptr); 82 | } 83 | -------------------------------------------------------------------------------- /include/ulog/file/file_writer_unbuffered_io.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawn on 25-3-13. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "file.h" 10 | #include "file_writer_base.h" 11 | 12 | namespace ulog::file { 13 | 14 | class FileWriterUnbufferedIo final : public FileWriterBase { 15 | public: 16 | explicit FileWriterUnbufferedIo() : file_limit_size_(kNoLimit) {} 17 | ~FileWriterUnbufferedIo() override { FileWriterUnbufferedIo::Close(); } 18 | 19 | Status Open(const std::string &filename, const bool truncate, const size_t limit) override { 20 | if (fd_ != -1) { 21 | return Status::Corruption("File already opened!", filename); 22 | } 23 | 24 | // create containing folder if not exists already. 25 | if (!create_dir(file::dir_name(filename))) { 26 | return Status::Corruption("Error creating directory", filename); 27 | } 28 | 29 | fd_ = open(filename.c_str(), O_WRONLY | O_CREAT | (truncate ? O_TRUNC : O_APPEND), 0644); 30 | if (fd_ == -1) { 31 | return Status::IOError("Error opening file", filename); 32 | } 33 | 34 | file_write_size_ = lseek(fd_, 0, SEEK_END); 35 | file_limit_size_ = limit; 36 | return Status::OK(); 37 | } 38 | 39 | Status Write(const void *data, const size_t length) override { 40 | if (fd_ == -1) { 41 | return Status::Corruption("Not opened"); 42 | } 43 | 44 | if (file_write_size_ + length > file_limit_size_) { 45 | return Status::Full(); 46 | } 47 | 48 | ssize_t written = write(fd_, data, length); 49 | if (written == -1 || static_cast(written) != length) { 50 | return Status::IOError("Error writing to file"); 51 | } 52 | 53 | file_write_size_ += length; 54 | return Status::OK(); 55 | } 56 | 57 | Status Flush() override { 58 | if (fd_ == -1) { 59 | return Status::Corruption("Not opened"); 60 | } 61 | 62 | if (fsync(fd_) == -1) { 63 | return Status::IOError("Error flushing file"); 64 | } 65 | return Status::OK(); 66 | } 67 | 68 | Status Close() override { 69 | if (fd_ == -1) { 70 | return Status::Corruption("Not opened"); 71 | } 72 | 73 | if (close(fd_) == -1) { 74 | return Status::IOError("Error closing file"); 75 | } 76 | fd_ = -1; 77 | 78 | return Status::OK(); 79 | } 80 | 81 | size_t TellP() override { return file_write_size_; } 82 | 83 | private: 84 | // config 85 | size_t file_limit_size_; 86 | 87 | int fd_{-1}; 88 | size_t file_write_size_{0}; 89 | }; 90 | } // namespace ulog::file 91 | -------------------------------------------------------------------------------- /tests/ulog_test.c: -------------------------------------------------------------------------------- 1 | #include "ulog/ulog.h" 2 | 3 | #include 4 | #include 5 | 6 | static int put_str(void *user_data, const char *str) { 7 | (void)user_data; // unused 8 | #if defined(WIN32) || defined(__unix__) || defined(__APPLE__) 9 | return printf("%s", str); 10 | #else 11 | return 0; // Need to implement a function to put string 12 | #endif 13 | } 14 | 15 | int main() { 16 | struct ulog_s *local_logger = logger_create(); 17 | // Initial logger 18 | logger_set_output_callback(local_logger, put_str); 19 | logger_set_output_callback(ULOG_GLOBAL, put_str); 20 | 21 | // Different log levels 22 | double pi = 3.14159265; 23 | 24 | LOGGER_TRACE("PI = %.3f", pi); 25 | LOGGER_LOCAL_TRACE(local_logger, "PI = %.3f", pi); 26 | 27 | LOGGER_DEBUG("PI = %.3f", pi); 28 | LOGGER_LOCAL_DEBUG(local_logger, "PI = %.3f", pi); 29 | 30 | LOGGER_INFO("PI = %.3f", pi); 31 | LOGGER_LOCAL_INFO(local_logger, "PI = %.3f", pi); 32 | 33 | LOGGER_WARN("PI = %.3f", pi); 34 | LOGGER_WARN("PI = %.3f", pi); 35 | LOGGER_ERROR("PI = %.3f", pi); 36 | LOGGER_FATAL("PI = %.3f", pi); 37 | LOGGER_RAW("PI = %.3f\r\n", pi); 38 | 39 | // Output debugging expression 40 | LOGGER_TOKEN(pi); 41 | LOGGER_TOKEN(50 * pi / 180); 42 | LOGGER_TOKEN(&pi); // print address of pi 43 | 44 | LOGGER_TOKEN((char)10); 45 | LOGGER_TOKEN((signed char)10); 46 | LOGGER_TOKEN((unsigned char)10); 47 | LOGGER_TOKEN((const char)10); 48 | LOGGER_TOKEN((const signed char)10); 49 | LOGGER_TOKEN((const unsigned char)10); 50 | LOGGER_TOKEN((char *)"test"); 51 | LOGGER_TOKEN((signed char *)"test"); 52 | LOGGER_TOKEN((unsigned char *)"test"); 53 | 54 | LOGGER_TOKEN((short)10); 55 | LOGGER_TOKEN((signed short)10); 56 | LOGGER_TOKEN((unsigned short)10); 57 | 58 | LOGGER_TOKEN((int)10); 59 | LOGGER_TOKEN((signed int)10); 60 | LOGGER_TOKEN((unsigned int)10); 61 | 62 | LOGGER_TOKEN((long)10); 63 | LOGGER_TOKEN((signed long)10); 64 | LOGGER_TOKEN((unsigned long)10); 65 | 66 | const char *text = "Ulog is a micro log library."; 67 | LOGGER_TOKEN(text); 68 | 69 | // Hex dump 70 | LOGGER_HEX_DUMP(text, 45, 16); 71 | 72 | // Output multiple tokens to one line 73 | time_t now = 1577259816; 74 | struct tm lt = *localtime(&now); 75 | LOGGER_MULTI_TOKEN(lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday); 76 | LOGGER_MULTI_TOKEN(lt.tm_wday, lt.tm_hour, lt.tm_min, lt.tm_sec); 77 | 78 | // Output execution time of some statements 79 | LOGGER_TIME_CODE( 80 | 81 | uint32_t n = 1000 * 1000; while (n--); 82 | 83 | ); 84 | 85 | logger_destroy(&local_logger); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /tests/ulog_test.cc: -------------------------------------------------------------------------------- 1 | #include "ulog/ulog.h" 2 | 3 | #include 4 | #include 5 | 6 | static int put_str(void *user_data, const char *str) { 7 | (void)user_data; // unused 8 | #if defined(WIN32) || defined(__unix__) || defined(__APPLE__) 9 | return printf("%s", str); 10 | #else 11 | return 0; // Need to implement a function to put string 12 | #endif 13 | } 14 | 15 | int main() { 16 | struct ulog_s *local_logger = logger_create(); 17 | // Initial logger 18 | logger_set_output_callback(local_logger, put_str); 19 | logger_set_output_callback(ULOG_GLOBAL, put_str); 20 | 21 | // Different log levels 22 | double pi = 3.14159265; 23 | 24 | LOGGER_TRACE("PI = %.3f", pi); 25 | LOGGER_LOCAL_TRACE(local_logger, "PI = %.3f", pi); 26 | 27 | LOGGER_DEBUG("PI = %.3f", pi); 28 | LOGGER_LOCAL_DEBUG(local_logger, "PI = %.3f", pi); 29 | 30 | LOGGER_INFO("PI = %.3f", pi); 31 | LOGGER_LOCAL_INFO(local_logger, "PI = %.3f", pi); 32 | 33 | LOGGER_WARN("PI = %.3f", pi); 34 | LOGGER_WARN("PI = %.3f", pi); 35 | LOGGER_ERROR("PI = %.3f", pi); 36 | LOGGER_FATAL("PI = %.3f", pi); 37 | LOGGER_RAW("PI = %.3f\r\n", pi); 38 | 39 | // Output debugging expression 40 | LOGGER_TOKEN(pi); 41 | LOGGER_TOKEN(50 * pi / 180); 42 | LOGGER_TOKEN(&pi); // print address of pi 43 | 44 | LOGGER_TOKEN((char)10); 45 | LOGGER_TOKEN((signed char)10); 46 | LOGGER_TOKEN((unsigned char)10); 47 | LOGGER_TOKEN((const char)10); 48 | LOGGER_TOKEN((const signed char)10); 49 | LOGGER_TOKEN((const unsigned char)10); 50 | LOGGER_TOKEN((char *)"test"); 51 | LOGGER_TOKEN((signed char *)"test"); 52 | LOGGER_TOKEN((unsigned char *)"test"); 53 | 54 | LOGGER_TOKEN((short)10); 55 | LOGGER_TOKEN((signed short)10); 56 | LOGGER_TOKEN((unsigned short)10); 57 | 58 | LOGGER_TOKEN((int)10); 59 | LOGGER_TOKEN((signed int)10); 60 | LOGGER_TOKEN((unsigned int)10); 61 | 62 | LOGGER_TOKEN((long)10); 63 | LOGGER_TOKEN((signed long)10); 64 | LOGGER_TOKEN((unsigned long)10); 65 | 66 | const char *text = "Ulog is a micro log library."; 67 | LOGGER_TOKEN(text); 68 | 69 | // Hex dump 70 | LOGGER_HEX_DUMP(text, 45, 16); 71 | 72 | // Output multiple tokens to one line 73 | time_t now = 1577259816; 74 | struct tm lt = *localtime(&now); 75 | LOGGER_MULTI_TOKEN(lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday); 76 | LOGGER_MULTI_TOKEN(lt.tm_wday, lt.tm_hour, lt.tm_min, lt.tm_sec); 77 | 78 | // Output execution time of some statements 79 | LOGGER_TIME_CODE( 80 | 81 | uint32_t n = 1000 * 1000; while (n--); 82 | 83 | ); 84 | 85 | logger_destroy(&local_logger); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /tests/mpsc_ring_test.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-3-31. 3 | // 4 | 5 | #include "ulog/queue/mpsc_ring.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | static void mq_test(const size_t buffer_size, const size_t write_thread_count, const size_t publish_count, 16 | const size_t read_thread_count) { 17 | const auto umq = Mq::Create(buffer_size); 18 | uint8_t data_source[256]; 19 | for (size_t i = 0; i < sizeof(data_source); i++) { 20 | data_source[i] = i; 21 | } 22 | 23 | std::atomic_uint64_t total_write_size(0); 24 | 25 | auto write_entry = [=, &total_write_size] { 26 | typename Mq::Producer producer(umq); 27 | std::random_device rd; 28 | std::mt19937 gen(rd()); 29 | std::uniform_int_distribution dis(8, std::min(256UL, buffer_size / 4)); 30 | 31 | uint64_t total_num = 0; 32 | while (total_num++ < publish_count) { 33 | size_t size = dis(gen); 34 | const auto data = producer.ReserveOrWaitFor(size, std::chrono::milliseconds(100)); 35 | if (data == nullptr) { 36 | continue; 37 | } 38 | memcpy(data, data_source, std::min(sizeof(data_source), size)); 39 | producer.Commit(data, size); 40 | total_write_size += size; 41 | } 42 | producer.Flush(); 43 | }; 44 | 45 | std::vector write_thread; 46 | for (size_t i = 0; i < write_thread_count; ++i) write_thread.emplace_back(write_entry); 47 | 48 | std::atomic_size_t total_read_packet(0); 49 | std::atomic_size_t total_read_size(0); 50 | auto read_entry = [=, &total_read_packet, &total_read_size] { 51 | typename Mq::Consumer consumer(umq); 52 | 53 | while (auto data = consumer.ReadOrWait(std::chrono::milliseconds(10))) { 54 | total_read_packet += data.remain(); 55 | while (const auto packet = data.next()) { 56 | ASSERT_EQ(memcmp(data_source, packet.data, packet.size), 0); 57 | total_read_size += packet.size; 58 | } 59 | consumer.Release(data); 60 | } 61 | }; 62 | 63 | std::vector read_thread; 64 | for (size_t i = 0; i < read_thread_count; ++i) read_thread.emplace_back(read_entry); 65 | 66 | for (auto& t : write_thread) t.join(); 67 | for (auto& t : read_thread) t.join(); 68 | 69 | ASSERT_EQ(total_read_packet, publish_count * write_thread_count); 70 | ASSERT_EQ(total_read_size, total_write_size); 71 | LOGGER_MULTI_TOKEN(total_write_size.load()); 72 | } 73 | 74 | TEST(MpscRingTest, multi_producer_single_consumer) { mq_test(64 * 1024, 4, 1024 * 100, 1); } 75 | -------------------------------------------------------------------------------- /include/ulog/file/rotation_strategy_incremental.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rotation_strategy_base.h" 9 | #include "ulog/status.h" 10 | 11 | namespace ulog::file { 12 | 13 | class RotationStrategyIncremental final : public RotationStrategyBase { 14 | public: 15 | RotationStrategyIncremental(std::string basename, std::string ext, const size_t max_files) 16 | : basename_(std::move(basename)), ext_(std::move(ext)), max_files_(max_files == 0 ? 1 : max_files) { 17 | if (const auto status = ReadNumberFile(get_number_filename(), &final_number_); !status.ok()) { 18 | // Maybe first write 19 | final_number_ = 0; 20 | } 21 | } 22 | 23 | // Example: 24 | // max_files == 10; 25 | // final_num == 29 + 1; 26 | // remove file: log-20.txt 27 | // remain files: log-21.txt log-22.txt ... log-29.txt 28 | // Next, call LatestFilename() to get log-30.txt for writing 29 | Status Rotate() override { 30 | ++final_number_; 31 | 32 | if (final_number_ >= max_files_) { 33 | std::remove(get_filename(final_number_ - max_files_).c_str()); 34 | 35 | // Try to clear the files that were previously configured too much 36 | size_t not_exists = 0; 37 | for (size_t i = final_number_ - max_files_; i != 0; --i) { 38 | std::string filename = get_filename(i); 39 | if (!path_exists(filename)) { 40 | if (++not_exists == 2) break; 41 | continue; 42 | } 43 | std::remove(filename.c_str()); 44 | } 45 | std::remove(get_filename(0).c_str()); 46 | } 47 | return WriteNumberFile(get_number_filename(), final_number_); 48 | } 49 | 50 | std::string LatestFilename() override { return basename_ + "-" + std::to_string(final_number_) + ext_; } 51 | 52 | private: 53 | [[nodiscard]] std::string get_number_filename() const { return basename_ + ext_ + ".latest"; } 54 | [[nodiscard]] std::string get_filename(const size_t index) const { 55 | return basename_ + "-" + std::to_string(index) + ext_; 56 | } 57 | 58 | static Status ReadNumberFile(const std::string& filename, size_t* const final_number) { 59 | std::ifstream input_file(filename); 60 | if (!input_file.is_open()) { 61 | *final_number = 0; 62 | return Status::IOError("Failed to open file: " + filename); 63 | } 64 | 65 | input_file >> *final_number; 66 | input_file.close(); 67 | return Status::OK(); 68 | } 69 | 70 | // Directly writes the latest number to the file 71 | static Status WriteNumberFile(const std::string& filename, const size_t number) { 72 | std::ofstream output_file(filename); 73 | 74 | if (!output_file.is_open()) { 75 | return Status::IOError("Failed to write number to: " + filename); 76 | } 77 | 78 | output_file << number; 79 | output_file.close(); 80 | return Status::OK(); 81 | } 82 | 83 | // config 84 | const std::string basename_; 85 | const std::string ext_; 86 | const size_t max_files_; 87 | 88 | size_t final_number_ = 0; 89 | }; 90 | 91 | } // namespace ulog::file -------------------------------------------------------------------------------- /examples/unix/ulog_example_rotate_file.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ulog/error.h" 8 | #include "ulog/file/sink_async_wrapper.h" 9 | #include "ulog/file/sink_limit_size_file.h" 10 | #include "ulog/file/sink_rotating_file.h" 11 | #include "ulog/queue/mpsc_ring.h" 12 | #include "ulog/ulog.h" 13 | 14 | static void OutputFunc() { 15 | int i = 10; 16 | LOGGER_TIME_CODE(while (i--) { 17 | double pi = 3.14159265; 18 | LOGGER_DEBUG("PI = %.3f", pi); 19 | 20 | LOGGER_ERROR("Error log test"); 21 | 22 | // Output debugging expression 23 | LOGGER_TOKEN(pi); 24 | LOGGER_TOKEN(50 * pi / 180); 25 | LOGGER_TOKEN(&pi); // print address of pi 26 | 27 | const char *text = "Ulog is a micro log library."; 28 | LOGGER_TOKEN(text); 29 | 30 | // Hex dump 31 | LOGGER_HEX_DUMP(text, 45, 16); 32 | 33 | // Output multiple tokens to one line 34 | time_t now = 1577259816; 35 | struct tm lt = *localtime(&now); 36 | 37 | LOGGER_MULTI_TOKEN(lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday); 38 | LOGGER_MULTI_TOKEN(lt.tm_wday, lt.tm_hour, lt.tm_min, lt.tm_sec); 39 | }); 40 | } 41 | 42 | int main() { 43 | std::unique_ptr file_writer = std::make_unique(); 44 | std::unique_ptr rotating_file = std::make_unique( 45 | std::move(file_writer), "/tmp/ulog/test.txt", 100 * 1024, 5, true, ulog::file::kRename, [] { 46 | std::string file_head = "This is ulog lib file head.\n"; 47 | return std::vector(file_head.begin(), file_head.end()); 48 | }); 49 | 50 | const auto [basename, ext] = ulog::file::SplitByExtension("/tmp/ulog/test.txt"); 51 | std::unique_ptr limit_size_file = std::make_unique( 52 | std::make_unique(), basename + "-head" + ext, 10 * 1024); 53 | ulog::file::SinkAsyncWrapper async_rotate(65536 * 2, std::chrono::seconds{1}, 54 | std::move(rotating_file), std::move(limit_size_file)); 55 | 56 | // Initial logger 57 | logger_format_enable(ULOG_GLOBAL, ULOG_F_NUMBER); 58 | logger_set_user_data(ULOG_GLOBAL, &async_rotate); 59 | logger_set_output_callback(ULOG_GLOBAL, [](void *user_data, const char *str) { 60 | printf("%s", str); 61 | auto &async = *static_cast(user_data); 62 | int len = strlen(str); 63 | return async.SinkIt(str, len) ? len : 0; 64 | }); 65 | logger_set_flush_callback(ULOG_GLOBAL, [](void *user_data) { 66 | auto &async = *static_cast(user_data); 67 | const auto status = async.Flush(); 68 | if (!status) { 69 | ULOG_ERROR("Failed to flush file: %s", status.ToString().c_str()); 70 | } 71 | }); 72 | 73 | // Create some output thread 74 | std::vector threads; 75 | uint32_t num_threads = 10; 76 | while (num_threads--) threads.emplace_back(OutputFunc); 77 | for (auto &thread : threads) thread.join(); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /tools/logroller/README.md: -------------------------------------------------------------------------------- 1 | # logroller 2 | 3 | Loop logging of standard inputs to several files 4 | 5 | This is a command-line tool for circularly writing the output of other programs to some files via a pipe ("|"). 6 | 7 | Written using ulog's asynchronous fifo and extended headers for file loop writing. 8 | 9 | ## Base Usage 10 | 11 | ```shell 12 | your_program | logroller --file-path /tmp/example/log.txt --file-size=100KB --max-files=3 13 | ``` 14 | 15 | This will output the output of your program to `/tmp/example/log.txt` through `logroller`, and limit the size and number of a single file according to the options. 16 | 17 | Eventually there will be `log.3.txt, log.2.txt, log.1.txt, log.txt` in the `/tmp/example` directory. 18 | 19 | These files will circulate in the order `log.(N).txt -> log.(N+1).txt`, and always keep at least 3 complete files. 20 | 21 | The `log.txt` can be viewed in real time using `tail -F /tmp/example/log.txt`. 22 | 23 | ## Advanced Usage 24 | 25 | ```shell 26 | your_program | logroller --file-path /tmp/example/log.txt --file-size=10MB --max-files=8 --flush-interval=3s --zstd-compress --zstd-params=level=3,window-log=16,chain-log=12,hash-log=13 27 | ``` 28 | 29 | Please refer to the following command line options for more advanced usage. 30 | 31 | ## Command line options 32 | 33 | ```bash 34 | > logroller --help 35 | Usage: logroller -f PATH [OPTIONS]... 36 | Loop logging of standard inputs to several files. 37 | 38 | Examples: 39 | your_program | logroller --file-path=log.txt --file-size=1MB --max-files=8 40 | your_program | logroller -f log.txt -s 1MB -n 8 --zstd-compress 41 | 42 | -h, --help Print help and exit 43 | -V, --version Print version and exit 44 | 45 | File options: 46 | -f, --file-path=PATH File path to record log 47 | -s, --file-size=SIZE Size of each file (default=`1MB') 48 | -n, --max-files=NUM Maximum number of files (default=`8') 49 | -i, --flush-interval=time Time interval between flushing to disk 50 | (default=`1s') 51 | --rotation-strategy=STR File rotation strategy: 52 | rename: log.1.txt -> log.2.txt 53 | incremental: log-24.txt ... log-34.txt 54 | (possible values="rename", "incremental" 55 | default=`rename') 56 | --rotate-first Should rotate first before write (default=off) 57 | 58 | Buffer options: 59 | -c, --fifo-size=SIZE Size of the FIFO buffer (default=`32KB') 60 | 61 | Compress options: 62 | --zstd-compress Compress with zstd (default=off) 63 | --zstd-params=PARAMS Parameters for zstd compression, 64 | larger == more compression and memory (e.g., 65 | level=3,window-log=21,chain-log=16,hash-log=17) 66 | 67 | The SIZE parameter units are K, M, G (power of 1024). If the unit is not 68 | specified, the default is bytes. 69 | The TIME parameter units are s, sec, ms, min, hour. If the unit is not 70 | specified, the default is seconds. 71 | ``` 72 | 73 | Command line options are generated by the gnu [gengetopt](https://www.gnu.org/software/gengetopt) tool with `getopt`. `cmdline.c/h` files are generated by the `gengetopt < logroller_getopt.ggo` command. 74 | -------------------------------------------------------------------------------- /include/ulog/file/sink_rotating_file.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2020-05-25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "file_writer_buffered_io.h" 13 | #include "rotation_strategy_incremental.h" 14 | #include "rotation_strategy_rename.h" 15 | #include "sink_base.h" 16 | 17 | namespace ulog::file { 18 | 19 | enum RotationStrategy { 20 | kRename = 1, 21 | kIncrement = 0, 22 | }; 23 | 24 | class SinkRotatingFile final : public SinkBase { 25 | public: 26 | /** 27 | * Outputs data to a file and rotates the file 28 | * @param writer File writer 29 | * @param filename File name 30 | * @param file_size File write data size limit 31 | * @param max_files Number of files that can be divided 32 | * @param rotate_on_open Whether to rotate the file when opening 33 | * @param rotation_strategy Rotation strategy 34 | * @param cb_file_head Called to obtain header data before writing each file 35 | */ 36 | SinkRotatingFile(std::unique_ptr &&writer, std::string filename, const std::size_t file_size, 37 | const std::size_t max_files, const bool rotate_on_open, const RotationStrategy rotation_strategy, 38 | std::function()> &&cb_file_head) 39 | : file_size_(file_size), 40 | max_files_(max_files), 41 | writer_(std::move(writer)), 42 | filename_(std::move(filename)), 43 | cb_file_head_(std::move(cb_file_head)) { 44 | auto [basename, ext] = SplitByExtension(filename_); 45 | if (rotation_strategy == kIncrement) { 46 | rotator_ = std::make_unique(basename, ext, max_files_); 47 | } else { 48 | rotator_ = std::make_unique(basename, ext, max_files_); 49 | } 50 | 51 | if (rotate_on_open) { 52 | rotator_->Rotate(); 53 | } 54 | 55 | if (const auto status = writer_->Open(rotator_->LatestFilename(), rotate_on_open, file_size_); !status) { 56 | ULOG_ERROR("Failed to open file: %s", status.ToString().c_str()); 57 | } 58 | 59 | if (cb_file_head_) { 60 | const auto head_data = cb_file_head_(); 61 | if (const auto status = writer_->Write(head_data.data(), head_data.size()); !status) { 62 | ULOG_ERROR("Failed to write file header"); 63 | } 64 | } 65 | } 66 | 67 | Status SinkIt(const void *data, const size_t len, [[maybe_unused]] std::chrono::milliseconds timeout) override { 68 | return SinkIt(data, len); 69 | } 70 | 71 | Status SinkIt(const void *data, const size_t len) override { 72 | Status status = writer_->Write(data, len); 73 | 74 | if (status.IsFull()) { 75 | writer_->Close(); 76 | rotator_->Rotate(); 77 | 78 | status = writer_->Open(rotator_->LatestFilename(), true, file_size_); 79 | if (!status) { 80 | return status; 81 | } 82 | 83 | if (cb_file_head_) { 84 | const auto head_data = cb_file_head_(); 85 | status = writer_->Write(head_data.data(), head_data.size()); 86 | if (!status) { 87 | return status; 88 | } 89 | } 90 | 91 | return writer_->Write(data, len); 92 | } 93 | 94 | return status; 95 | } 96 | 97 | [[nodiscard]] Status Flush() override { return writer_->Flush(); } 98 | 99 | private: 100 | const std::size_t file_size_; 101 | const std::size_t max_files_; 102 | std::unique_ptr writer_; 103 | 104 | std::string filename_; 105 | std::unique_ptr rotator_; 106 | std::function()> cb_file_head_; 107 | }; 108 | 109 | } // namespace ulog::file 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ulog 2 | 3 | ![Tests](https://github.com/ShawnFeng0/ulog/actions/workflows/tests.yml/badge.svg) 4 | 5 | A library written in C/C ++ for printing logs of lightweight embedded devices. 6 | 7 | ## Platforms and Dependent 8 | 9 | * Current version only supports unix-like platforms. 10 | * LOGGER_TOKEN and LOGGER_MULTI_TOKEN requires C++11 or GCC extension support, but other functions are not needed. 11 | 12 | ## Features 13 | 14 | * Different log levels 15 | * Log color identification 16 | * Print token (any basic type is automatically recognized and printed) 17 | * Hex dump (print the hexadecimal content in the specified address) 18 | * Statistics code running time 19 | 20 | ## Quick Demo 21 | 22 | ### Code 23 | 24 | ```C++ 25 | #include "ulog/ulog.h" 26 | 27 | #include 28 | #include 29 | 30 | static int put_str(void *user_data, const char *str) { 31 | user_data = user_data; // unused 32 | #if defined(WIN32) || defined(__unix__) || defined(__APPLE__) 33 | return printf("%s", str); 34 | #else 35 | return 0; // Need to implement a function to put string 36 | #endif 37 | } 38 | 39 | int main() { 40 | // Initial logger 41 | logger_set_output_callback(ULOG_GLOBAL, NULL, put_str); 42 | 43 | double pi = 3.14159265; 44 | // Different log levels 45 | LOGGER_TRACE("PI = %.3f", pi); 46 | LOGGER_DEBUG("PI = %.3f", pi); 47 | LOGGER_INFO("PI = %.3f", pi); 48 | LOGGER_WARN("PI = %.3f", pi); 49 | LOGGER_ERROR("PI = %.3f", pi); 50 | LOGGER_FATAL("PI = %.3f", pi); 51 | LOGGER_RAW("PI = %.3f\r\n", pi); 52 | 53 | // Output debugging expression 54 | LOGGER_TOKEN(pi); 55 | LOGGER_TOKEN(50 * pi / 180); 56 | LOGGER_TOKEN(&pi); // print address of pi 57 | 58 | char *text = (char *)"Ulog is a micro log library."; 59 | LOGGER_TOKEN((char *)text); 60 | 61 | // Hex dump 62 | LOGGER_HEX_DUMP(text, 45, 16); 63 | 64 | // Output multiple tokens to one line 65 | time_t now = 1577259816; 66 | struct tm lt = *localtime(&now); 67 | 68 | LOGGER_MULTI_TOKEN(lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday); 69 | LOGGER_MULTI_TOKEN(lt.tm_wday, lt.tm_hour, lt.tm_min, lt.tm_sec); 70 | 71 | // Output execution time of some statements 72 | LOGGER_TIME_CODE( 73 | 74 | uint32_t n = 1000 * 1000; while (n--); 75 | 76 | ); 77 | 78 | return 0; 79 | } 80 | 81 | ``` 82 | 83 | ### Output 84 | 85 | ![example](doc/figures/example.png) 86 | 87 | ## Build and integration into the project 88 | 89 | ```bash 90 | git submodule add https://github.com/ShawnFeng0/ulog.git 91 | ``` 92 | 93 | 1. As a submodule or [Download](https://github.com/ShawnFeng0/ulog/archive/master.zip) the entire project. 94 | 95 | If you use cmake (recommended): 96 | 97 | 2. Configure the following in the cmakelists.txt of the main project: 98 | 99 | ```cmake 100 | add_subdirectory(ulog) 101 | target_link_libraries(${PROJECT_NAME} PUBLIC ulog) # Link libulog.a and include ulog/include path 102 | ``` 103 | 104 | or: 105 | 106 | 2. Add **ulog/include** to the include path of your project 107 | 108 | 3. Add ulog / src / ulog.c to the project's source file list 109 | 110 | ## Install (Optional, Only in Unix) 111 | 112 | If you want to install this library system-wide, you can do so via 113 | 114 | ```bash 115 | mkdir build && cd build 116 | cmake ../ 117 | sudo make install 118 | ``` 119 | 120 | ## Document 121 | 122 | * [How to use ulog to print logs(API usage overview)](doc/api_usage_overview.md) 123 | 124 | ## Extension tools 125 | 126 | ### logroller 127 | 128 | [logroller](tools/logroller) is a command-line tool for circularly writing the output of other programs to some files via a pipe ("|"). 129 | 130 | It is written using ulog's asynchronous fifo and extended headers for file loop writing. 131 | 132 | Usage: 133 | 134 | ```bash 135 | your_program | logroller --file-path /tmp/example/log.txt --file-size=100000 --max-files=3 136 | ``` 137 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Build 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v?[0-9]+.[0-9]+.[0-9]+*' 7 | 8 | env: 9 | BUILD_TYPE: Release 10 | 11 | jobs: 12 | build: 13 | name: Build ${{ matrix.platform.name }} 14 | runs-on: ${{ matrix.platform.os }} 15 | strategy: 16 | matrix: 17 | platform: 18 | - name: linux-x86_64 19 | os: ubuntu-latest 20 | arch: x86_64 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - name: Install build dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y clang libzstd-dev 30 | 31 | - name: Configure CMake 32 | run: | 33 | cmake -B ${{github.workspace}}/build \ 34 | -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ 35 | -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ 36 | -DULOG_BUILD_TOOLS=ON \ 37 | -DULOG_BUILD_EXAMPLES=OFF \ 38 | -DULOG_BUILD_TESTS=OFF 39 | 40 | - name: Build 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc) 42 | 43 | - name: Install to staging area 44 | run: | 45 | mkdir -p ${{github.workspace}}/package 46 | cmake --install ${{github.workspace}}/build --prefix ${{github.workspace}}/package 47 | 48 | - name: Copy binaries 49 | run: | 50 | # No need to copy binaries, install step already handles this 51 | 52 | - name: Create package archive 53 | run: | 54 | repo_name="${{ github.event.repository.name }}" 55 | cd ${{github.workspace}}/package 56 | tar -czf ../${repo_name}-${{ matrix.platform.name }}-${{ github.ref_name }}.tar.gz * 57 | 58 | - name: Upload build artifacts 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: ${{ github.event.repository.name }}-${{ matrix.platform.name }}-${{ github.ref_name }} 62 | path: ${{github.workspace}}/${{ github.event.repository.name }}-${{ matrix.platform.name }}-${{ github.ref_name }}.tar.gz 63 | retention-days: 30 64 | 65 | release: 66 | name: Create Release 67 | needs: build 68 | runs-on: ubuntu-latest 69 | if: startsWith(github.ref, 'refs/tags/') 70 | permissions: 71 | contents: write 72 | 73 | steps: 74 | - name: Checkout code 75 | uses: actions/checkout@v4 76 | 77 | - name: Download all artifacts 78 | uses: actions/download-artifact@v4 79 | with: 80 | path: ./artifacts 81 | 82 | - name: Display structure of downloaded files 83 | run: ls -la ./artifacts/ 84 | 85 | - name: Create release body from changelog 86 | shell: bash 87 | run: | 88 | tag="${{ github.ref_name }}" 89 | tag_no_v="${tag#v}" 90 | tag_escaped=$(echo "$tag_no_v" | sed 's/\./\\./g') 91 | awk "/## \\[(v)?$tag_escaped\]/ {flag=1; next} /^## \\[/ {flag=0} flag" CHANGELOG.md | sed '/^$/d' > release_body.md 92 | repo_url="https://github.com/${{ github.repository }}" 93 | echo -e "\nFor more release notes, see [CHANGELOG.md]($repo_url/blob/${{ github.ref_name }}/CHANGELOG.md)" >> release_body.md 94 | 95 | - name: Create Release and Upload Assets 96 | run: | 97 | gh release create ${{ github.ref_name }} \ 98 | --title "Release ${{ github.ref_name }}" \ 99 | --notes-file release_body.md 100 | for artifact_dir in ./artifacts/*/; do 101 | artifact_file=$(find "$artifact_dir" -name "*.tar.gz" | head -1) 102 | if [ -f "$artifact_file" ]; then 103 | echo "Uploading $artifact_file" 104 | gh release upload ${{ github.ref_name }} "$artifact_file" 105 | fi 106 | done 107 | env: 108 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /include/ulog/file/file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ulog::file { 9 | 10 | // folder separator 11 | static constexpr char kFolderSep = '/'; 12 | 13 | // Return true if path exists (file or directory) 14 | // Ref: 15 | // https://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exists-using-standard-c-c11-14-17-c 16 | static inline bool path_exists(const std::string &filename) { 17 | struct stat buffer{}; 18 | return (::stat(filename.c_str(), &buffer) == 0); 19 | } 20 | 21 | // Return file size according to open FILE* object 22 | static inline std::size_t filesize(FILE *f) { 23 | if (f == nullptr) { 24 | return 0; 25 | } 26 | 27 | int fd = ::fileno(f); 28 | // 64 bits(but not in osx or cygwin, where fstat64 is deprecated) 29 | #if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) 30 | struct stat64 st{}; 31 | if (::fstat64(fd, &st) == 0) { 32 | return static_cast(st.st_size); 33 | } 34 | #else // other unix or linux 32 bits or cygwin 35 | struct stat st{}; 36 | if (::fstat(fd, &st) == 0) { 37 | return static_cast(st.st_size); 38 | } 39 | #endif 40 | return 0; 41 | } 42 | 43 | // return true on success 44 | static inline bool mkdir_(const std::string &path) { 45 | int ret = ::mkdir(path.c_str(), mode_t(0755)); 46 | if (ret == 0 || (ret == -1 && errno == EEXIST)) { 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | // create the given directory - and all directories leading to it 53 | // return true on success or if the directory already exists 54 | static inline bool create_dir(const std::string &path) { 55 | if (path_exists(path)) { 56 | return true; 57 | } 58 | 59 | if (path.empty()) { 60 | return true; 61 | } 62 | 63 | size_t search_offset = 0; 64 | do { 65 | auto token_pos = path.find(kFolderSep, search_offset); 66 | // treat the entire path as a folder if no folder separator not found 67 | if (token_pos == std::string::npos) { 68 | token_pos = path.size(); 69 | } 70 | 71 | auto subdir = path.substr(0, token_pos); 72 | 73 | if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) { 74 | return false; // return error if failed creating dir 75 | } 76 | search_offset = token_pos + 1; 77 | } while (search_offset < path.size()); 78 | 79 | return true; 80 | } 81 | 82 | // Return directory name from given path or empty string 83 | // "abc/file" => "abc" 84 | // "abc/" => "abc" 85 | // "abc" => "" 86 | // "abc///" => "abc//" 87 | static inline std::string dir_name(const std::string &path) { 88 | auto pos = path.find_last_of(kFolderSep); 89 | return pos != std::string::npos ? path.substr(0, pos) : std::string{}; 90 | } 91 | 92 | // return file path and its extension: 93 | // 94 | // "mylog.txt" => ("mylog", ".txt") 95 | // "mylog" => ("mylog", "") 96 | // "mylog." => ("mylog.", "") 97 | // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") 98 | // 99 | // the starting dot in filenames is ignored (hidden files): 100 | // 101 | // ".mylog" => (".mylog". "") 102 | // "my_folder/.mylog" => ("my_folder/.mylog", "") 103 | // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") 104 | // 105 | // "my_folder/.mylog.txt.zst" => ("my_folder/.mylog", ".txt.zst") 106 | // "/etc/rc.d/somelogfile" => ("/etc/rc.d/somelogfile", "") 107 | static inline std::tuple SplitByExtension(const std::string &filename) { 108 | const auto folder_index = filename.rfind(file::kFolderSep); 109 | const auto ext_index = filename.find('.', folder_index == std::string::npos ? 1 : folder_index + 2); 110 | 111 | // no valid extension found - return whole path and empty string as extension 112 | if (ext_index == std::string::npos || ext_index == 0 || ext_index == filename.size() - 1) { 113 | return std::make_tuple(filename, std::string()); 114 | } 115 | 116 | // finally - return a valid base and extension tuple 117 | return std::make_tuple(filename.substr(0, ext_index), filename.substr(ext_index)); 118 | } 119 | 120 | // Rename the src file to target 121 | // return true on success, false otherwise. 122 | static inline bool RenameFile(const std::string &src_filename, const std::string &target_filename) { 123 | // If the new filename already exists, according to the posix standard, 124 | // rename will automatically delete the new file name, and will ensure that 125 | // the path of the new file is always valid. If you use remove() to delete 126 | // the new file first, other processes will not be able to access the new 127 | // file for a moment. 128 | // Ref: 129 | // https://pubs.opengroup.org/onlinepubs/000095399/functions/rename.html 130 | return std::rename(src_filename.c_str(), target_filename.c_str()) == 0; 131 | } 132 | 133 | } // namespace ulog::file 134 | -------------------------------------------------------------------------------- /include/ulog/file/file_writer_zstd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "file.h" 9 | #include "file_writer_base.h" 10 | #include "file_writer_buffered_io.h" 11 | 12 | namespace ulog::file { 13 | 14 | class FileWriterZstd final : public FileWriterBase { 15 | public: 16 | explicit FileWriterZstd(std::unique_ptr&& file_writer, const int level = 3, const int window_log = 14, 17 | const int chain_log = 14, const int hash_log = 15, const int search_log = 2, 18 | const int min_match = 4, const int target_length = 0, const int strategy = 2, 19 | const size_t max_frame_in = 8 << 20) 20 | : config_file_limit_size_(kNoLimit), config_zstd_max_frame_in_(max_frame_in), file_(std::move(file_writer)) { 21 | zstd_out_buffer_.resize(16 * 1024); 22 | out_buffer_ = {zstd_out_buffer_.data(), zstd_out_buffer_.size(), 0}; 23 | 24 | zstd_cctx_ = ZSTD_createCCtx(); 25 | assert(zstd_cctx_ != nullptr); 26 | 27 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_compressionLevel, level); 28 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_windowLog, window_log); 29 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_chainLog, chain_log); 30 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_hashLog, hash_log); 31 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_searchLog, search_log); 32 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_minMatch, min_match); 33 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_targetLength, target_length); 34 | ZSTD_CCtx_setParameter(zstd_cctx_, ZSTD_c_strategy, strategy); 35 | } 36 | 37 | ~FileWriterZstd() override { 38 | FileWriterZstd::Close(); 39 | ZSTD_freeCCtx(zstd_cctx_); 40 | } 41 | 42 | Status Open(const std::string& filename, const bool truncate, const size_t limit) override { 43 | if (!zstd_cctx_) { 44 | return Status::Corruption("Error creating ZSTD context"); 45 | } 46 | 47 | // create containing folder if not exists already. 48 | if (!create_dir(dir_name(filename))) { 49 | return Status::Corruption("Error creating directory", filename); 50 | } 51 | 52 | if (const auto status = file_->Open(filename, truncate, limit); !status) { 53 | return status; 54 | } 55 | 56 | zstd_frame_in_ = 0; 57 | config_file_limit_size_ = limit; 58 | return Status::OK(); 59 | } 60 | 61 | Status Write(const void* data, const size_t length) override { 62 | if (file_->TellP() + ZSTD_compressBound(zstd_frame_in_ + length) + zstd_header_size() > config_file_limit_size_) { 63 | return Status::Full(); 64 | } 65 | 66 | const ZSTD_EndDirective mode = 67 | (zstd_frame_in_ + length >= config_zstd_max_frame_in_) ? ZSTD_e_end : ZSTD_e_continue; 68 | auto status = WriteCompressData(data, length, mode); 69 | if (status) { 70 | zstd_frame_in_ = mode == ZSTD_e_end ? 0 : zstd_frame_in_ + length; 71 | } 72 | return status; 73 | } 74 | 75 | Status Flush() override { 76 | if (zstd_frame_in_ > 0) { 77 | if (const auto status = WriteCompressData(nullptr, 0, ZSTD_e_end); !status) { 78 | return status; 79 | } 80 | 81 | zstd_frame_in_ = 0; 82 | } 83 | 84 | file_->Flush(); 85 | return Status::OK(); 86 | } 87 | 88 | Status Close() override { 89 | auto f_result = Flush(); 90 | auto c_result = file_->Close(); 91 | 92 | return std::move(f_result.ok() ? c_result : f_result); 93 | } 94 | 95 | size_t TellP() override { return file_->TellP(); } 96 | 97 | private: 98 | static size_t zstd_header_size() { 99 | static size_t header_size = ZSTD_CStreamOutSize() > ZSTD_compressBound(ZSTD_CStreamInSize()) 100 | ? ZSTD_CStreamOutSize() - ZSTD_compressBound(ZSTD_CStreamInSize()) 101 | : 0; 102 | return header_size; 103 | } 104 | 105 | /* Compress until the input buffer is empty, each time flushing the output. */ 106 | Status WriteCompressData(const void* data, const size_t length, const ZSTD_EndDirective mode) { 107 | size_t remaining = 0; 108 | ZSTD_inBuffer in_buffer = {data, length, 0}; 109 | do { 110 | remaining = ZSTD_compressStream2(zstd_cctx_, &out_buffer_, &in_buffer, mode); 111 | if (ZSTD_isError(remaining)) { 112 | return Status::Corruption(ZSTD_getErrorName(remaining)); 113 | } 114 | 115 | if (mode == ZSTD_e_end || out_buffer_.size == out_buffer_.pos) { 116 | if (const auto status = file_->Write(out_buffer_.dst, out_buffer_.pos); !status) { 117 | return status; 118 | } 119 | out_buffer_.pos = 0; 120 | } 121 | 122 | } while (remaining != 0); 123 | 124 | return Status::OK(); 125 | } 126 | 127 | // config 128 | size_t config_file_limit_size_; 129 | const size_t config_zstd_max_frame_in_; 130 | 131 | std::unique_ptr file_; 132 | ZSTD_CCtx* zstd_cctx_ = nullptr; 133 | size_t zstd_frame_in_{0}; 134 | std::vector zstd_out_buffer_; 135 | ZSTD_outBuffer out_buffer_{}; 136 | }; 137 | 138 | } // namespace ulog::file 139 | -------------------------------------------------------------------------------- /include/ulog/status.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawn on 25-1-17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | // Reference from leveldb 12 | namespace ulog { 13 | 14 | class Status { 15 | public: 16 | // Create a success status. 17 | Status() noexcept : code_(kOk), error_(nullptr) {} 18 | ~Status() { delete error_; } 19 | 20 | Status(const Status& rhs) : code_(rhs.code_), error_(rhs.error_ ? new std::string(*rhs.error_) : nullptr) {} 21 | Status& operator=(const Status& rhs) { 22 | if (this == &rhs) { 23 | return *this; 24 | } 25 | 26 | if (rhs.error_) { 27 | if (!error_) error_ = new std::string(); 28 | *error_ = *rhs.error_; 29 | 30 | } else { 31 | delete error_; 32 | } 33 | 34 | code_ = rhs.code_; 35 | return *this; 36 | } 37 | 38 | Status(Status&& rhs) noexcept : code_(rhs.code_), error_(rhs.error_) { 39 | rhs.code_ = kOk; 40 | rhs.error_ = nullptr; 41 | } 42 | Status& operator=(Status&& rhs) noexcept { 43 | std::swap(code_, rhs.code_); 44 | std::swap(error_, rhs.error_); 45 | return *this; 46 | } 47 | 48 | // Return a success status. 49 | static Status OK() { return Status{}; } 50 | 51 | // Return error status of an appropriate type. 52 | static Status NotFound(const std::string& msg, const std::string& msg2 = "") { return {kNotFound, msg, msg2}; } 53 | static Status Corruption(const std::string& msg, const std::string& msg2 = "") { return {kCorruption, msg, msg2}; } 54 | static Status NotSupported(const std::string& msg, const std::string& msg2 = "") { 55 | return {kNotSupported, msg, msg2}; 56 | } 57 | static Status InvalidArgument(const std::string& msg, const std::string& msg2 = "") { 58 | return {kInvalidArgument, msg, msg2}; 59 | } 60 | static Status IOError(const std::string& msg, const std::string& msg2 = "") { return {kIOError, msg, msg2}; } 61 | static Status Full(const std::string& msg) { return {kFull, msg}; } 62 | static Status Full() { return Status{kFull}; } 63 | static Status Empty(const std::string& msg) { return {kEmpty, msg}; } 64 | static Status Empty() { return Status{kEmpty}; } 65 | 66 | // Returns true if the status indicates success. 67 | [[nodiscard]] bool ok() const { return code() == kOk; } 68 | explicit operator bool() const { return ok(); } 69 | 70 | // Returns true if the status indicates a NotFound error. 71 | [[nodiscard]] bool IsNotFound() const { return code() == kNotFound; } 72 | 73 | // Returns true if the status indicates a Corruption error. 74 | [[nodiscard]] bool IsCorruption() const { return code() == kCorruption; } 75 | 76 | // Returns true if the status indicates an IOError. 77 | [[nodiscard]] bool IsIOError() const { return code() == kIOError; } 78 | 79 | // Returns true if the status indicates a NotSupportedError. 80 | [[nodiscard]] bool IsNotSupportedError() const { return code() == kNotSupported; } 81 | 82 | // Returns true if the status indicates an InvalidArgument. 83 | [[nodiscard]] bool IsInvalidArgument() const { return code() == kInvalidArgument; } 84 | 85 | // Returns true if the status indicates a Full error. 86 | [[nodiscard]] bool IsFull() const { return code() == kFull; } 87 | 88 | // Returns true if the status indicates an Empty error. 89 | [[nodiscard]] bool IsEmpty() const { return code() == kEmpty; } 90 | 91 | // Return a string representation of this status suitable for printing. 92 | // Returns the string "OK" for success. 93 | [[nodiscard]] std::string ToString() const; 94 | 95 | private: 96 | enum Code { 97 | kOk = 0, 98 | kNotFound = -1, 99 | kCorruption = -2, 100 | kNotSupported = -3, 101 | kInvalidArgument = -4, 102 | kIOError = -5, 103 | kFull = -6, 104 | kEmpty = -7 105 | }; 106 | 107 | [[nodiscard]] Code code() const { return static_cast(code_); } 108 | 109 | explicit Status(const Code code) : code_(code), error_(nullptr) {} 110 | Status(const Code code, const std::string& msg, const std::string& msg2 = "") 111 | : code_(code), error_(new std::string{msg + (msg2.empty() ? "" : ": " + msg2)}) {} 112 | 113 | int code_; 114 | std::string* error_; 115 | }; 116 | 117 | inline std::string Status::ToString() const { 118 | char tmp[30]; 119 | const char* type; 120 | switch (code()) { 121 | case kOk: 122 | type = "OK"; 123 | break; 124 | case kNotFound: 125 | type = "NotFound"; 126 | break; 127 | case kCorruption: 128 | type = "Corruption"; 129 | break; 130 | case kNotSupported: 131 | type = "Not implemented"; 132 | break; 133 | case kInvalidArgument: 134 | type = "Invalid argument"; 135 | break; 136 | case kIOError: 137 | type = "IO error"; 138 | break; 139 | case kFull: 140 | type = "Full"; 141 | break; 142 | case kEmpty: 143 | type = "Empty"; 144 | break; 145 | default: 146 | std::snprintf(tmp, sizeof(tmp), "Unknown code(%d): ", static_cast(code())); 147 | type = tmp; 148 | break; 149 | } 150 | if (!error_) { 151 | return type; 152 | } 153 | return std::string(type) + ": " + *error_; 154 | } 155 | } // namespace ulog 156 | -------------------------------------------------------------------------------- /include/ulog/file/sink_async_wrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2020-05-26. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ulog/error.h" 14 | #include "ulog/file/sink_base.h" 15 | #include "ulog/status.h" 16 | 17 | namespace ulog::file { 18 | 19 | template 20 | class SinkAsyncWrapper final : public SinkBase { 21 | public: 22 | /** 23 | * Build a logger that asynchronously outputs data to a file 24 | * @param fifo_size Asynchronous first-in first-out buffer size 25 | * @param max_flush_period Maximum file flush period (some file systems and platforms only refresh once every 60s by 26 | * default, which is too slow) 27 | * @param next_sink Next sinker 28 | * @param args More sinker 29 | */ 30 | template 31 | SinkAsyncWrapper(size_t fifo_size, std::chrono::milliseconds max_flush_period, std::unique_ptr &&next_sink, 32 | Args &&...args) 33 | : umq_(Queue::Create(fifo_size)) { 34 | sinks_.emplace_back(std::move(next_sink)); 35 | (sinks_.emplace_back(std::forward(args)), ...); 36 | 37 | auto async_thread_function = [max_flush_period, this] { 38 | auto last_flush_time = std::chrono::steady_clock::now(); 39 | bool need_wait_flush = false; // Whether to wait for the next flush 40 | typename Queue::Consumer reader(umq_->shared_from_this()); 41 | 42 | while (!should_exit_) { 43 | bool flush_now = false; 44 | 45 | auto data_packet = [&] { 46 | if (should_flush_) { 47 | should_flush_ = false; 48 | flush_now = true; 49 | return reader.Read(); 50 | } 51 | return need_wait_flush 52 | ? reader.ReadOrWait(max_flush_period, [&] { return should_flush_.load() || should_exit_.load(); }) 53 | : reader.ReadOrWait([&] { return should_flush_.load() || should_exit_.load(); }); 54 | }(); 55 | 56 | while (const auto data = data_packet.next()) { 57 | auto status = SinkAll(data.data, data.size); 58 | if (!status) { 59 | ULOG_ERROR("Failed to sink: %s", status.ToString().c_str()); 60 | } 61 | } 62 | reader.Release(data_packet); 63 | 64 | // Flush 65 | const auto cur_time = std::chrono::steady_clock::now(); 66 | if (flush_now || (need_wait_flush && cur_time - last_flush_time >= max_flush_period)) { 67 | if (auto status = FlushAllSink(); !status) { 68 | ULOG_ERROR("Failed to flush file: %s", status.ToString().c_str()); 69 | continue; 70 | } 71 | last_flush_time = cur_time; 72 | 73 | // Already flushed, only need to wait forever for the next data 74 | need_wait_flush = false; 75 | 76 | } else { 77 | // data has been written and the maximum waiting time is until the next flush 78 | need_wait_flush = true; 79 | } 80 | } 81 | }; 82 | async_thread_ = std::make_unique(async_thread_function); 83 | } 84 | 85 | SinkAsyncWrapper(const SinkAsyncWrapper &) = delete; 86 | SinkAsyncWrapper &operator=(const SinkAsyncWrapper &) = delete; 87 | 88 | ~SinkAsyncWrapper() override { 89 | umq_->Flush(std::chrono::seconds(5)); 90 | should_exit_ = true; 91 | umq_->Notify(); 92 | if (async_thread_) async_thread_->join(); 93 | } 94 | 95 | Status Flush() override { 96 | should_flush_.store(true); 97 | umq_->Flush(); 98 | return Status::OK(); 99 | } 100 | 101 | typename Queue::Producer CreateProducer() const { return typename Queue::Producer(umq_->shared_from_this()); } 102 | 103 | Status SinkIt(const void *data, const size_t len, std::chrono::milliseconds timeout) override { 104 | typename Queue::Producer writer(umq_->shared_from_this()); 105 | if (auto *buffer = writer.ReserveOrWaitFor(len, timeout)) { 106 | memcpy(buffer, data, len); 107 | writer.Commit(buffer, len); 108 | return Status::OK(); 109 | } 110 | return Status::Full(); 111 | } 112 | 113 | Status SinkIt(const void *data, const size_t len) override { 114 | typename Queue::Producer writer(umq_->shared_from_this()); 115 | if (auto *buffer = writer.Reserve(len)) { 116 | memcpy(buffer, data, len); 117 | writer.Commit(buffer, len); 118 | return Status::OK(); 119 | } 120 | return Status::Full(); 121 | } 122 | 123 | private: 124 | [[nodiscard]] Status SinkAll(const void *data, const size_t len) { 125 | Status result = Status::OK(); 126 | for (auto sink = sinks_.begin(); sink != sinks_.end();) { 127 | if (const auto status = (*sink)->SinkIt(data, len); !status) { 128 | if (status.IsFull()) { 129 | sink = sinks_.erase(sink); 130 | continue; 131 | } 132 | result = status; 133 | } 134 | 135 | ++sink; 136 | } 137 | return result; 138 | } 139 | 140 | [[nodiscard]] Status FlushAllSink() const { 141 | Status result = Status::OK(); 142 | for (auto &sink : sinks_) { 143 | if (const auto status = sink->Flush(); !status) { 144 | result = status; 145 | } 146 | } 147 | return result; 148 | } 149 | 150 | std::shared_ptr umq_; 151 | std::list> sinks_; 152 | std::unique_ptr async_thread_; 153 | 154 | std::atomic_bool should_exit_{false}; 155 | std::atomic_bool should_flush_{false}; 156 | }; 157 | 158 | } // namespace ulog::file 159 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres 6 | to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | [Unreleased]: https://github.com/ShawnFeng0/ulog/compare/v0.6.2...HEAD 11 | 12 | ## [0.6.2] - 2025-04-15 13 | 14 | [0.6.2]: https://github.com/ShawnFeng0/ulog/compare/v0.6.1...v0.6.2 15 | 16 | ### Fixed 17 | 18 | * fix: file size check for zstd compression 19 | 20 | ## [0.6.1] - 2025-03-25 21 | 22 | [0.6.1]: https://github.com/ShawnFeng0/ulog/compare/v0.6.0...v0.6.1 23 | 24 | ### Added 25 | 26 | * logroller: Add native unbuffered write interface, enabled by default when logroller is compressed 27 | 28 | ### Changed 29 | 30 | * file: limit_size_file no longer automatically adds the "-head" suffix, but is user-defined 31 | * logroller: Set up SCHED_BATCH scheduling strategy as a background task 32 | 33 | ### Fixed 34 | 35 | * fix: log sequence number does not support multithreading 36 | 37 | ## [0.6.0] - 2025-02-08 38 | 39 | [0.6.0]: https://github.com/ShawnFeng0/ulog/compare/v0.5.0...v0.6.0 40 | 41 | ### Added 42 | 43 | * Add lock-free and wait-free spsc (single producer-single consumer) and mpsc (multiple producers-single consumer) queue 44 | libraries based on bip-buffer algorithm 45 | * logroller: Supports zstd real-time compression 46 | * logroller: Support incremental file rotation strategy 47 | * async_file: Add file header callback function 48 | 49 | ### Changed 50 | 51 | * logrotate -> logroller: To avoid naming conflicts with logrotate, change the name to logroller 52 | * logroller: SIZE and TIME parameters support string formats such as kb/mb and s/min/hour 53 | * logroller: Improve performance, using lock-free spsc queue implementation 54 | 55 | ## [0.5.0] - 2024-11-20 56 | 57 | [0.5.0]: https://github.com/ShawnFeng0/ulog/compare/v0.4.2...v0.5.0 58 | 59 | ### Added 60 | 61 | ### Changed 62 | 63 | * Delete log lock function 64 | * flush: Automatically execute flush only when logging at FATAL level 65 | * Format modification uses a unified function: logger_check_format/logger_format_disable/logger_format_enable 66 | * Remove the stdout function for file storage 67 | * CI: Change the CI provider from travis-ci to github workflows 68 | * Use "\n" instead of "\r\n" for line breaks 69 | * format: Level and debug information are separated by " " instead of "/" 70 | 71 | ### Fixed 72 | 73 | * remove() does not need to be executed when file is rotated 74 | 75 | ## [0.4.2] - 2023-02-09 76 | 77 | [0.4.2]: https://github.com/ShawnFeng0/ulog/compare/v0.4.1...v0.4.2 78 | 79 | ### Fixed 80 | 81 | * Fix: logrotate compilation error: pthread symbol missing 82 | 83 | ## [0.4.1] - 2023-02-08 84 | 85 | [0.4.1]: https://github.com/ShawnFeng0/ulog/compare/v0.4.0...v0.4.1 86 | 87 | ### Added 88 | 89 | * Added command line tool logrotate for circular logging to file 90 | 91 | ## [0.4.0] - 2022-09-08 92 | 93 | [0.4.0]: https://github.com/ShawnFeng0/ulog/compare/v0.3.1...v0.4.0 94 | 95 | ### Added 96 | 97 | * Add MIT license 98 | * Add changelog file 99 | 100 | ### Fixed 101 | 102 | * Resolve compile warnings under -W -Wall -Waddress compile options 103 | * TIME_CODE() and TOKEN(string) cannot turn off color output 104 | * Token output is not limited by level 105 | 106 | ## [0.3.1] - 2021-06-21 107 | 108 | [0.3.1]: https://github.com/ShawnFeng0/ulog/compare/v0.3.0...v0.3.1 109 | 110 | ### Added 111 | 112 | * Execute flush operation when outputting logs above the error level 113 | * Support setting flush callback function 114 | 115 | ### Changed 116 | 117 | * Use enum ulog_level_e to replace LogLevel to avoid naming conflicts with other projects 118 | * Set ulog cmake public header file path (use modern cmake target_include_directories(xxx PUBLIC xxx) syntax) 119 | * Optimize the internal processing of the snprintf function to reduce the call of strlen() 120 | * Set user_data and output_callback separately, keeping each setting independent, keep the logger_create function simple 121 | 122 | ### Fixed 123 | 124 | * Support signed char type token printing 125 | 126 | ## [0.3.0] - 2021-02-04 127 | 128 | [0.3.0]: https://github.com/ShawnFeng0/ulog/compare/v0.2.0...v0.3.0 129 | 130 | ### Added 131 | 132 | * Support the creation of different logger instances 133 | 134 | ## [0.2.0] - 2020-11-26 135 | 136 | [0.2.0]: https://github.com/ShawnFeng0/ulog/compare/v0.1.2...v0.2.0 137 | 138 | ### Added 139 | 140 | * Add user private data settings to make the output more flexible 141 | * Add examples of asynchronous file output and auxiliary classes 142 | 143 | ### Changed 144 | 145 | * Use LOGGER_XXX macros instead of LOG_XXX to avoid naming conflicts 146 | * Remove the brackets from the time information 147 | 148 | ### Fixed 149 | 150 | * fix: It is executed twice when the log parameter is a function 151 | 152 | ### Removed 153 | 154 | * Delete the ABORT() and ASSERT() auxiliary functions, it should not be the responsibility of the log library 155 | 156 | ## [0.1.2] - 2020-05-25 157 | 158 | [0.1.2]: https://github.com/ShawnFeng0/ulog/compare/v0.1.1...v0.1.2 159 | 160 | ### Removed 161 | 162 | * Delete too many macro definitions (try to use functions instead of macros): 163 | * Delete ULOG_DISABLE macro 164 | * Delete ULOG_DEFAULT_LEVEL macro 165 | * Delete ULOG_DISABLE_COLOR macro 166 | * Delete ULOG_CLS macro 167 | 168 | ### Fixed 169 | 170 | * After the first line of hexdump indicates that the output fails, the output will not continue 171 | 172 | ## [0.1.1] - 2020-05-22 173 | 174 | [0.1.1]: https://github.com/ShawnFeng0/ulog/compare/v0.1.0...v0.1.1 175 | 176 | ### Changed 177 | 178 | * LOG_TIME_CODE uses microseconds as the output unit 179 | * Make asynchronous logs block waiting 180 | 181 | ## [0.1.0] - 2020-02-07 182 | 183 | [0.1.0]: https://github.com/ShawnFeng0/ulog/releases/tag/v0.1.0 184 | 185 | * Realize basic functions 186 | * Various log levels output, rich colors, hexdump output, token output, time_code statistics 187 | 188 | * Add fifo for asynchronous output 189 | -------------------------------------------------------------------------------- /tests/mpsc_benchmarks/mpsc_benchmarks.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by shawnfeng on 23-3-31. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ulog/queue/fifo_power_of_two.h" 12 | #include "ulog/queue/mpsc_ring.h" 13 | #include "ulog/ulog.h" 14 | 15 | // Returns ANSI color string for ratio value 16 | #include 17 | static std::string get_ratio_color(float ratio, float min = -0.5f, float max = 0.5f) { 18 | float clamped = std::clamp(ratio, min, max); 19 | int target_r = clamped < 0 ? 255 : 0; 20 | int target_g = clamped < 0 ? 0 : 255; 21 | int target_b = 0; 22 | float t = (max != min) ? fabsf(clamped) / (max - min) * 2.0f : 0.0f; 23 | if (t > 1.0f) t = 1.0f; 24 | int r = (int)(255 * (1.0f - t) + target_r * t); 25 | int g = (int)(255 * (1.0f - t) + target_g * t); 26 | int b = (int)(255 * (1.0f - t) + target_b * t); 27 | char buf[32]; 28 | snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", r, g, b); 29 | return std::string(buf); 30 | } 31 | 32 | static void umq_mpsc(const size_t buffer_size, const size_t max_write_thread, const size_t publish_count, 33 | const size_t max_read_thread) { 34 | const auto umq = ulog::mpsc::Mq::Create(buffer_size); 35 | 36 | auto write_entry = [=] { 37 | ulog::mpsc::Mq::Producer producer(umq); 38 | std::random_device rd; 39 | std::mt19937 gen(rd()); 40 | std::uniform_int_distribution dis(8, 256); 41 | 42 | uint64_t total_num = 0; 43 | while (total_num++ < publish_count) { 44 | size_t size = dis(gen); 45 | const auto data = static_cast(producer.ReserveOrWaitFor(size, std::chrono::milliseconds(100))); 46 | if (data == nullptr) { 47 | continue; 48 | } 49 | constexpr char data_source[] = 50 | "asdfqwerasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwer" 51 | "as" 52 | "dfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqwerrr" 53 | "rr" 54 | "sdfeeraerasdfqwersdfqwerrerasdfqwer"; 55 | memcpy(data, data_source, std::min(sizeof(data_source), size)); 56 | producer.Commit(data, size); 57 | } 58 | producer.Flush(); 59 | }; 60 | 61 | std::vector write_thread; 62 | for (size_t i = 0; i < max_write_thread; ++i) write_thread.emplace_back(write_entry); 63 | 64 | auto read_entry = [=] { 65 | ulog::mpsc::Mq::Consumer consumer(umq); 66 | size_t total_packet = 0; 67 | while (ulog::mpsc::DataPacket data = consumer.ReadOrWait(std::chrono::milliseconds(10))) { 68 | total_packet += data.remain(); 69 | while (const auto packet = data.next()) { 70 | uint8_t data_recv[100 * 1024]; 71 | memcpy(data_recv, packet.data, packet.size); 72 | } 73 | consumer.Release(data); 74 | } 75 | LOGGER_MULTI_TOKEN(buffer_size, total_packet); 76 | }; 77 | std::vector read_thread; 78 | for (size_t i = 0; i < max_read_thread; ++i) read_thread.emplace_back(read_entry); 79 | 80 | for (auto& t : write_thread) t.join(); 81 | for (auto& t : read_thread) t.join(); 82 | } 83 | 84 | static void fifo_mpsc(const size_t buffer_size, const size_t max_write_thread, const size_t publish_count, 85 | const size_t max_read_thread) { 86 | auto fifo = new ulog::FifoPowerOfTwo(buffer_size, 1); 87 | 88 | auto write_entry = [=] { 89 | std::random_device rd; 90 | std::mt19937 gen(rd()); 91 | std::uniform_int_distribution dis(8, 256); 92 | 93 | constexpr uint8_t data_source[] = 94 | "asdfqwerasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqweras" 95 | "dfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqweasdfqwerasdfqwerasdfqwerrrrr" 96 | "sdfeeraerasdfqwersdfqwerrerasdfqwer"; 97 | 98 | uint64_t total_num = 0; 99 | while (total_num++ < publish_count) { 100 | size_t size = dis(gen); 101 | fifo->InputWaitIfFull(data_source, size, 100); 102 | } 103 | }; 104 | 105 | std::vector write_thread; 106 | for (size_t i = 0; i < max_write_thread; ++i) write_thread.emplace_back(write_entry); 107 | 108 | auto read_entry = [=] { 109 | uint64_t total_num = 0; 110 | uint8_t data_recv[100 * 1024]; 111 | while (const auto size = fifo->OutputWaitIfEmpty(data_recv, sizeof(data_recv), 10)) { 112 | total_num += size; 113 | } 114 | LOGGER_MULTI_TOKEN(buffer_size, total_num); 115 | }; 116 | std::vector read_thread; 117 | for (size_t i = 0; i < max_read_thread; ++i) read_thread.emplace_back(read_entry); 118 | 119 | for (auto& t : write_thread) t.join(); 120 | for (auto& t : read_thread) t.join(); 121 | } 122 | 123 | int main() { 124 | logger_format_disable(ulog_global_logger, 125 | ULOG_F_FUNCTION | ULOG_F_TIME | ULOG_F_PROCESS_ID | ULOG_F_LEVEL | ULOG_F_FILE_LINE); 126 | logger_set_output_level(ulog_global_logger, ULOG_LEVEL_INFO); 127 | 128 | auto mpsc_test = [](const size_t buffer_size) { 129 | constexpr size_t publish_count = 10 * 1024; 130 | LOGGER_INFO("Algorithm buffer size: %lu", buffer_size); 131 | LOGGER_INFO("Number of packet published per thread: %lu", publish_count); 132 | LOGGER_INFO("Each packet has 8 ~ 256 bytes."); 133 | LOGGER_INFO("%16s %16s %16s %16s", "write_thread", "umq", "kfifo+mutex", "ratio"); 134 | for (int thread_count = 1; thread_count <= 200; 135 | thread_count = thread_count < 10 ? thread_count << 1 : thread_count + 10) { 136 | int64_t mpsc_time = LOGGER_TIME_CODE({ umq_mpsc(buffer_size, thread_count, publish_count, 1); }); 137 | int64_t fifo_time = LOGGER_TIME_CODE({ fifo_mpsc(buffer_size, thread_count, publish_count, 1); }); 138 | float ratio = (float)(fifo_time - mpsc_time) / fifo_time; 139 | const std::string color_start = get_ratio_color(ratio); 140 | LOGGER_INFO("\033[0m%16d %14lums %14lums %s%16.2f", thread_count, mpsc_time, fifo_time, color_start.c_str(), 141 | ratio); 142 | } 143 | }; 144 | 145 | mpsc_test(64 * 1024); 146 | mpsc_test(64 * 1024 * 8); 147 | 148 | pthread_exit(nullptr); 149 | } 150 | -------------------------------------------------------------------------------- /tools/logroller/logroller.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cmdline.h" 12 | #include "ulog/file/file_writer_buffered_io.h" 13 | #include "ulog/file/file_writer_unbuffered_io.h" 14 | #include "ulog/file/file_writer_zstd.h" 15 | #include "ulog/file/sink_async_wrapper.h" 16 | #include "ulog/file/sink_limit_size_file.h" 17 | #include "ulog/file/sink_rotating_file.h" 18 | #include "ulog/queue/spsc_ring.h" 19 | 20 | #define ZSTD_DEFAULT_LEVEL 3 21 | 22 | static auto to_bytes(const std::string &size_str) { 23 | size_t i = 0; 24 | const auto size = std::stoull(size_str, &i); 25 | 26 | std::string unit = size_str.substr(i); 27 | std::transform(unit.begin(), unit.end(), unit.begin(), ::tolower); 28 | 29 | if (unit == "k" || unit == "kb" || unit == "kib") return size << 10; 30 | if (unit == "m" || unit == "mb" || unit == "mib") return size << 20; 31 | if (unit == "g" || unit == "gb" || unit == "gib") return size << 30; 32 | 33 | return size; 34 | } 35 | 36 | static std::chrono::milliseconds to_chrono_time(const std::string &time_str) { 37 | size_t i = 0; 38 | const auto size = std::stoull(time_str, &i); 39 | 40 | std::string unit = time_str.substr(i); 41 | std::transform(unit.begin(), unit.end(), unit.begin(), ::tolower); 42 | 43 | if (unit == "ms") return std::chrono::milliseconds{size}; 44 | if (unit == "s" || unit == "sec") return std::chrono::seconds{size}; 45 | if (unit == "min") return std::chrono::minutes{size}; 46 | if (unit == "hour") return std::chrono::hours{size}; 47 | 48 | return std::chrono::seconds{size}; 49 | } 50 | 51 | static std::map ParseParametersMap(const std::string &input) { 52 | std::map params; 53 | std::string item; 54 | std::stringstream ss(input); 55 | 56 | while (std::getline(ss, item, ',')) { 57 | const std::size_t pos = item.find('='); 58 | if (pos != std::string::npos) { 59 | const std::string key = item.substr(0, pos); 60 | const std::string value = item.substr(pos + 1); 61 | params[key] = value; 62 | } 63 | } 64 | 65 | return params; 66 | } 67 | 68 | // Ref: android logcat 69 | // Lower priority and set to batch scheduling 70 | static void SetupSchedulingPolicy() { 71 | #if defined(__linux__) 72 | constexpr sched_param param = {}; 73 | if (sched_setscheduler(0, SCHED_BATCH, ¶m) < 0) { 74 | ULOG_ERROR("failed to set to batch scheduler\n"); 75 | } 76 | #endif 77 | 78 | constexpr int PRIORITY_BACKGROUND = 10; 79 | 80 | if (setpriority(PRIO_PROCESS, 0, PRIORITY_BACKGROUND) < 0) { 81 | ULOG_ERROR("failed set to priority\n"); 82 | } 83 | } 84 | 85 | int main(const int argc, char *argv[]) { 86 | gengetopt_args_info args_info{}; 87 | 88 | if (cmdline_parser(argc, argv, &args_info) != 0) exit(1); 89 | 90 | SetupSchedulingPolicy(); 91 | 92 | const auto fifo_size = std::max(to_bytes(args_info.fifo_size_arg), 16ULL * 1024); 93 | const auto max_flush_period = to_chrono_time(args_info.flush_interval_arg); 94 | const auto rotation_strategy = std::string(args_info.rotation_strategy_arg) == "incremental" 95 | ? ulog::file::RotationStrategy::kIncrement 96 | : ulog::file::RotationStrategy::kRename; 97 | std::string filename = args_info.file_path_arg; 98 | 99 | std::unique_ptr file_writer; 100 | // Zstd compression 101 | if (args_info.zstd_compress_flag) { 102 | // Append .zst extension if not present 103 | std::string basename, ext; 104 | std::tie(basename, ext) = ulog::file::SplitByExtension(filename); 105 | if (ext.find(".zst") == std::string::npos) filename += ".zst"; 106 | 107 | // Parse zstd parameters 108 | if (args_info.zstd_params_given) { 109 | const std::map result = ParseParametersMap(args_info.zstd_params_arg); 110 | file_writer = std::make_unique( 111 | std::make_unique(), 112 | result.count("level") ? std::stoi(result.at("level")) : ZSTD_DEFAULT_LEVEL, 113 | result.count("window-log") ? std::stoi(result.at("window-log")) : 0, 114 | result.count("chain-log") ? std::stoi(result.at("chain-log")) : 0, 115 | result.count("hash-log") ? std::stoi(result.at("hash-log")) : 0, 116 | result.count("search-log") ? std::stoi(result.at("search-log")) : 0, 117 | result.count("min-match") ? std::stoi(result.at("min-match")) : 0, 118 | result.count("target-length") ? std::stoi(result.at("target-length")) : 0, 119 | result.count("strategy") ? std::stoi(result.at("strategy")) : 0); 120 | 121 | // No zstd parameters 122 | } else { 123 | file_writer = 124 | std::make_unique(std::make_unique()); 125 | } 126 | 127 | // No compression 128 | } else { 129 | file_writer = std::make_unique(); 130 | } 131 | 132 | std::unique_ptr rotating_file = std::make_unique( 133 | std::move(file_writer), filename, to_bytes(args_info.file_size_arg), args_info.max_files_arg, 134 | args_info.rotate_first_flag, rotation_strategy, nullptr); 135 | const ulog::file::SinkAsyncWrapper> async_rotate(fifo_size, max_flush_period, 136 | std::move(rotating_file)); 137 | 138 | cmdline_parser_free(&args_info); 139 | 140 | // Set O_NONBLOCK flag 141 | { 142 | const int flag = fcntl(STDIN_FILENO, F_GETFL); 143 | if (flag < 0) { 144 | ULOG_ERROR("fcntl failed"); 145 | return -1; 146 | } 147 | fcntl(STDIN_FILENO, F_SETFL, flag | O_NONBLOCK); 148 | } 149 | 150 | const size_t reverse_size = std::min(4ULL * 1024, fifo_size / 8); 151 | auto writer = async_rotate.CreateProducer(); 152 | 153 | pollfd fds{.fd = STDIN_FILENO, .events = POLLIN, .revents = 0}; 154 | while (poll(&fds, 1, -1) >= 0) { 155 | auto *buffer = writer.ReserveOrWait(reverse_size); 156 | 157 | const auto real_size = read(STDIN_FILENO, buffer, reverse_size); 158 | 159 | // End of input 160 | if (real_size <= 0) { 161 | writer.Commit(buffer, 0); 162 | break; 163 | } 164 | 165 | writer.Commit(buffer, real_size); 166 | } 167 | return 0; 168 | } 169 | -------------------------------------------------------------------------------- /include/ulog/ulog.h: -------------------------------------------------------------------------------- 1 | #ifndef __ULOG_H__ 2 | #define __ULOG_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef int (*ulog_output_callback)(void *user_data, const char *ptr); 10 | typedef void (*ulog_flush_callback)(void *user_data); 11 | 12 | enum ulog_level_e { 13 | ULOG_LEVEL_TRACE = 0, 14 | ULOG_LEVEL_DEBUG, 15 | ULOG_LEVEL_INFO, 16 | ULOG_LEVEL_WARN, 17 | ULOG_LEVEL_ERROR, 18 | ULOG_LEVEL_FATAL, 19 | ULOG_LEVEL_NUMBER, 20 | 21 | // Raw data level means output is always available 22 | ULOG_LEVEL_RAW = ULOG_LEVEL_NUMBER 23 | }; 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | extern struct ulog_s *ulog_global_logger; 30 | #define ULOG_GLOBAL ulog_global_logger 31 | 32 | /** 33 | * Create a logger instance 34 | * @return Return a new ulog instance 35 | */ 36 | struct ulog_s *logger_create(void); 37 | 38 | /** 39 | * Destroy logger instance 40 | * @param logger_ptr Pointer to logger pointer, it will be set to NULL pointer 41 | */ 42 | void logger_destroy(struct ulog_s **logger_ptr); 43 | 44 | /** 45 | * Set user data, each output will be passed to output_callback/flush_callback, 46 | * making the output more flexible. 47 | * 48 | * @param user_data The user data pointer. 49 | */ 50 | void logger_set_user_data(struct ulog_s *logger, void *user_data); 51 | 52 | /** 53 | * Set the callback function for log output, the log is output through this 54 | * function 55 | * 56 | * @param logger 57 | * @param output_callback Callback function to output string 58 | */ 59 | void logger_set_output_callback(struct ulog_s *logger, 60 | ulog_output_callback output_callback); 61 | 62 | /** 63 | * Set the callback function of log flush, which is executed when the log level 64 | * is error 65 | * 66 | * @param logger 67 | * @param flush_callback Callback function to flush the log 68 | */ 69 | void logger_set_flush_callback(struct ulog_s *logger, 70 | ulog_flush_callback flush_callback); 71 | 72 | /***************************************************************************** 73 | * log format configuration: 74 | */ 75 | 76 | /** 77 | * Set the minimum log level. Logs below this level will not be output. The 78 | * default level is the lowest level, so logs of all levels are output. 79 | */ 80 | void logger_set_output_level(struct ulog_s *logger, enum ulog_level_e level); 81 | 82 | /** 83 | * Enable or disable log output, which is enabled by default 84 | */ 85 | void logger_enable_output(struct ulog_s *logger, bool enable); 86 | 87 | #define ULOG_F_COLOR 1 << 0 // Enable color output (default=on) 88 | #define ULOG_F_NUMBER 1 << 1 // Enable line number output (default=off) 89 | #define ULOG_F_TIME 1 << 2 // Enable time output (default=on) 90 | #define ULOG_F_PROCESS_ID 1 << 3 // Enable process id output (default=on) 91 | #define ULOG_F_LEVEL 1 << 4 // Enable log level output (default=on) 92 | #define ULOG_F_FILE_LINE 1 << 5 // Enable file line output (default=on) 93 | #define ULOG_F_FUNCTION 1 << 6 // Enable function name output (default=on) 94 | 95 | /** 96 | * Enable log format, can use (ULOG_F_XXX | ULOG_F_XXX) combination 97 | */ 98 | void logger_format_enable(struct ulog_s *logger, int32_t format); 99 | 100 | /** 101 | * Disable log format, can use (ULOG_F_XXX | ULOG_F_XXX) combination 102 | */ 103 | void logger_format_disable(struct ulog_s *logger, int32_t format); 104 | 105 | /** 106 | * Check if the log format is enabled 107 | */ 108 | bool logger_check_format(struct ulog_s *logger, int32_t format); 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | /***************************************************************************** 115 | * log related macros: 116 | */ 117 | 118 | #include "ulog/ulog_private.h" 119 | 120 | /** 121 | * Different levels of log output 122 | * @param fmt Format of the format string 123 | * @param ... Parameters in the format 124 | */ 125 | #define LOGGER_LOCAL_TRACE(logger, fmt, ...) \ 126 | ULOG_OUT_LOG(logger, ULOG_LEVEL_TRACE, fmt, ##__VA_ARGS__) 127 | #define LOGGER_TRACE(fmt, ...) \ 128 | LOGGER_LOCAL_TRACE(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 129 | 130 | #define LOGGER_LOCAL_DEBUG(logger, fmt, ...) \ 131 | ULOG_OUT_LOG(logger, ULOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__) 132 | #define LOGGER_DEBUG(fmt, ...) \ 133 | LOGGER_LOCAL_DEBUG(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 134 | 135 | #define LOGGER_LOCAL_INFO(logger, fmt, ...) \ 136 | ULOG_OUT_LOG(logger, ULOG_LEVEL_INFO, fmt, ##__VA_ARGS__) 137 | #define LOGGER_INFO(fmt, ...) LOGGER_LOCAL_INFO(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 138 | 139 | #define LOGGER_LOCAL_WARN(logger, fmt, ...) \ 140 | ULOG_OUT_LOG(logger, ULOG_LEVEL_WARN, fmt, ##__VA_ARGS__) 141 | #define LOGGER_WARN(fmt, ...) LOGGER_LOCAL_WARN(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 142 | 143 | #define LOGGER_LOCAL_ERROR(logger, fmt, ...) \ 144 | ULOG_OUT_LOG(logger, ULOG_LEVEL_ERROR, fmt, ##__VA_ARGS__) 145 | #define LOGGER_ERROR(fmt, ...) \ 146 | LOGGER_LOCAL_ERROR(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 147 | 148 | #define LOGGER_LOCAL_FATAL(logger, fmt, ...) \ 149 | ULOG_OUT_LOG(logger, ULOG_LEVEL_FATAL, fmt, ##__VA_ARGS__) 150 | #define LOGGER_FATAL(fmt, ...) \ 151 | LOGGER_LOCAL_FATAL(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 152 | 153 | #define LOGGER_LOCAL_RAW(logger, fmt, ...) \ 154 | ULOG_OUT_RAW(logger, ULOG_LEVEL_RAW, fmt, ##__VA_ARGS__) 155 | #define LOGGER_RAW(fmt, ...) LOGGER_LOCAL_RAW(ULOG_GLOBAL, fmt, ##__VA_ARGS__) 156 | 157 | /** 158 | * Output various tokens (Requires C++ 11 or GNU extension) 159 | * example: 160 | * double pi = 3.14; 161 | * LOG_TOKEN(pi); 162 | * LOG_TOKEN(pi * 50.f / 180.f); 163 | * LOG_TOKEN(&pi); // print address of pi 164 | * output: 165 | * (float) pi => 3.140000 166 | * (float) pi * 50.f / 180.f => 0.872222 167 | * (void *) &pi => 7fff2f5568d8 168 | * @param token Can be float, double, [unsigned / signed] char / short / int / 169 | * long / long long and pointers of the above type 170 | */ 171 | #define LOGGER_LOCAL_TOKEN(logger, token) ULOG_OUT_TOKEN(logger, token) 172 | #define LOGGER_TOKEN(token) LOGGER_LOCAL_TOKEN(ULOG_GLOBAL, token) 173 | 174 | /** 175 | * Output multiple tokens to one line (Requires C++ 11 or GNU extension) 176 | * example: 177 | * time_t now = 1577232000; // 2019-12-25 00:00:00 178 | * struct tm* lt = localtime(&now); 179 | * LOG_MULTI_TOKEN(lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday); 180 | * output: 181 | * lt->tm_year + 1900 => 2019, lt->tm_mon + 1 => 12, lt->tm_mday => 25 182 | * @param token Same definition as LOG_TOKEN parameter, but can output up to 16 183 | * tokens at the same time 184 | */ 185 | #define LOGGER_LOCAL_MULTI_TOKEN(logger, ...) \ 186 | ULOG_OUT_MULTI_TOKEN(logger, __VA_ARGS__) 187 | #define LOGGER_MULTI_TOKEN(...) \ 188 | LOGGER_LOCAL_MULTI_TOKEN(ULOG_GLOBAL, __VA_ARGS__) 189 | 190 | /** 191 | * Statistics code running time, 192 | * example: 193 | * LOG_TIME_CODE( 194 | * 195 | * uint32_t n = 1000 * 1000; 196 | * while (n--); 197 | * 198 | * ); 199 | * output: 200 | * time { uint32_t n = 1000 * 1000; while (n--); } => 1315us 201 | */ 202 | #define LOGGER_LOCAL_TIME_CODE(logger, ...) ULOG_TIME_CODE(logger, __VA_ARGS__) 203 | #define LOGGER_TIME_CODE(...) LOGGER_LOCAL_TIME_CODE(ULOG_GLOBAL, __VA_ARGS__) 204 | 205 | /** 206 | * Display contents in hexadecimal and ascii. 207 | * Same format as "hexdump -C filename" 208 | * example: 209 | * char str1[5] = "test"; 210 | * char str2[10] = "1234"; 211 | * LOG_HEX_DUMP(&str1, 20, 16); 212 | * output: 213 | * hex_dump(data:&str1, length:20, width:8) => 214 | * 7fff2f556921 74 65 73 74 00 31 32 33 |test.123| 215 | * 7fff2f556929 34 00 00 00 00 00 00 30 |4......0| 216 | * 7fff2f556931 d3 a4 9b a7 |....| 217 | * 7fff2f556935 218 | * @param data The starting address of the data to be displayed 219 | * @param length Display length starting from "data" 220 | * @param width How many bytes of data are displayed in each line 221 | */ 222 | #define LOGGER_LOCAL_HEX_DUMP(logger, data, length, width) \ 223 | ULOG_HEX_DUMP(logger, data, length, width) 224 | #define LOGGER_HEX_DUMP(data, length, width) \ 225 | LOGGER_LOCAL_HEX_DUMP(ULOG_GLOBAL, data, length, width) 226 | 227 | #endif //__ULOG_H__ 228 | -------------------------------------------------------------------------------- /doc/api_usage_overview.md: -------------------------------------------------------------------------------- 1 | # How to use ulog to print logs(API usage overview) 2 | 3 | Please refer to the examples in the [tests](tests) or [examples](examples) folder, there are examples of asynchronous 4 | log output written in C++ and asynchronous output to files in the [examples](examples) directory. 5 | 6 | Detailed documentation is described in ulog.h 7 | 8 | ## 1 Initialization 9 | 10 | Unix platform has default configuration, you can use it directly without configuration 11 | 12 | ### 1.1 Initialize the logger and set the string output callback function 13 | 14 | The simplest configuration is just to configure the output callback. 15 | 16 | user_data: Set by the user, each output will be passed to output_callback, output can be more flexible. 17 | 18 | ```C 19 | // Create a logger instance, the ULOG_GLOBAL is an existing global instance. If the system uses only one logger instance, there is no need to create it again. 20 | struct ulog_s *logger_create(void); 21 | void logger_destroy(struct ulog_s **logger_ptr); 22 | 23 | // Set user data, each output will be passed to output_callback/flush_callback, making the output more flexible. 24 | void logger_set_user_data(struct ulog_s *logger, void *user_data); 25 | 26 | // Set the callback function for log output, the log is output through this function 27 | typedef int (*ulog_output_callback)(void *user_data, const char *ptr); 28 | void logger_set_output_callback(struct ulog_s *logger, ulog_output_callback output_callback); 29 | 30 | // Set the callback function of log flush, which is executed when the log level is error 31 | typedef void (*ulog_flush_callback)(void *user_data); 32 | void logger_set_flush_callback(struct ulog_s *logger, ulog_flush_callback flush_callback); 33 | 34 | // sample: 35 | static int put_str(void *user_data, const char *str) { 36 | user_data = user_data; // unused 37 | return printf("%s", str); 38 | } 39 | int main(int argc, char *argv[]) { 40 | struct ulog_s *local_logger = logger_create(); 41 | 42 | logger_set_output_callback(local_logger, put_str); 43 | logger_set_output_callback(ULOG_GLOBAL, put_str); 44 | 45 | logger_set_flush_callback(ULOG_GLOBAL, NULL); 46 | logger_set_flush_callback(local_logger, NULL); 47 | 48 | // ... 49 | 50 | logger_destroy(&local_logger); 51 | } 52 | ``` 53 | 54 | ## 2 Print log 55 | 56 | The `LOGGER_XXX(fmt, ...)` just uses `ULOG_GLOBAL` for `LOGGER_LOCAL_XXXX` 57 | example: `#define LOGGER_TRACE(fmt, ...) LOGGER_LOCAL_TRACE(ULOG_GLOBAL, fmt, ##__VA_ARGS__)` 58 | 59 | ### 2.1 Normal log 60 | 61 | Same format as **printf**. 62 | 63 | ```C 64 | double pi = 3.14159265; 65 | LOGGER_TRACE("PI = %.3f", pi); // Use a separate logger: LOGGER_LOCAL_TRACE(local_logger, "PI = %.3f", pi); 66 | LOGGER_DEBUG("PI = %.3f", pi); 67 | LOGGER_INFO("PI = %.3f", pi); 68 | LOGGER_WARN("PI = %.3f", pi); 69 | LOGGER_ERROR("PI = %.3f", pi); 70 | LOGGER_FATAL("PI = %.3f", pi); 71 | LOGGER_RAW("PI = %.3f\r\n", pi); 72 | 73 | /* Output: --------------------------------------------------------------- 74 | [2020-02-05 18:48:55.111] 14890-14890 T/(ulog_test.cpp:47 main) PI = 3.142 75 | [2020-02-05 18:48:55.111] 14890-14890 D/(ulog_test.cpp:48 main) PI = 3.142 76 | [2020-02-05 18:48:55.111] 14890-14890 I/(ulog_test.cpp:49 main) PI = 3.142 77 | [2020-02-05 18:48:55.111] 14890-14890 W/(ulog_test.cpp:50 main) PI = 3.142 78 | [2020-02-05 18:48:55.111] 14890-14890 E/(ulog_test.cpp:51 main) PI = 3.142 79 | [2020-02-05 18:48:55.111] 14890-14890 F/(ulog_test.cpp:52 main) PI = 3.142 80 | */ 81 | ``` 82 | 83 | ### 2.2 Print variable (Requires C++ 11 or GNU extension) 84 | 85 | Output various tokens, the function will automatically recognize the type of token and print. 86 | 87 | ```C 88 | /* 89 | @param token Can be float, double, [unsigned / signed] char / short / int / long / long long and pointers of the above type 90 | */ 91 | #define LOGGER_LOCAL_TOKEN(logger, token) ... 92 | #define LOGGER_TOKEN(token) LOGGER_LOCAL_TOKEN(ULOG_GLOBAL, token) 93 | ``` 94 | 95 | Output multiple tokens to one line, each parameter can be a different type 96 | 97 | ```C 98 | /** 99 | * @param token Same definition as LOGGER_TOKEN parameter, but can output up to 16 100 | * tokens at the same time 101 | */ 102 | #define LOGGER_LOCAL_MULTI_TOKEN(logger, ...) ... 103 | #define LOGGER_MULTI_TOKEN(...) LOGGER_LOCAL_MULTI_TOKEN(ULOG_GLOBAL, __VA_ARGS__) 104 | ``` 105 | 106 | Example: 107 | 108 | ```C 109 | double pi = 3.14; 110 | LOGGER_TOKEN(pi); 111 | LOGGER_TOKEN(pi * 50.f / 180.f); 112 | LOGGER_TOKEN(&pi); // print address of pi 113 | 114 | time_t now = 1577232000; // 2019-12-25 00:00:00 115 | struct tm* lt = localtime(&now); 116 | LOGGER_MULTI_TOKEN(lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday); 117 | 118 | /* Output: --------------------------------------------------------- 119 | (float) pi => 3.140000 120 | (float) pi * 50.f / 180.f => 0.872222 121 | (void *) &pi => 7fff2f5568d8 122 | lt->tm_year + 1900 => 2019, lt->tm_mon + 1 => 12, lt->tm_mday => 25 123 | */ 124 | ``` 125 | 126 | ### 2.3 Hex dump 127 | 128 | Display contents in hexadecimal and ascii. Same format as "hexdump -C filename" 129 | 130 | ```C 131 | /* 132 | * @param data The starting address of the data to be displayed 133 | * @param length Display length starting from "data" 134 | * @param width How many bytes of data are displayed in each line 135 | */ 136 | #define LOGGER_LOCAL_HEX_DUMP(logger, data, length, width) ... 137 | #define LOGGER_HEX_DUMP(data, length, width) LOGGER_LOCAL_HEX_DUMP(ULOG_GLOBAL, data, length, width) 138 | ``` 139 | 140 | Example: 141 | 142 | ```C 143 | char str1[5] = "test"; 144 | char str2[10] = "1234"; 145 | LOGGER_HEX_DUMP(&str1, 20, 16); 146 | 147 | /* Output: --------------------------------------- 148 | hex_dump(data:&str1, length:20, width:8) => 149 | 7fff2f556921 74 65 73 74 00 31 32 33 |test.123| 150 | 7fff2f556929 34 00 00 00 00 00 00 30 |4......0| 151 | 7fff2f556931 d3 a4 9b a7 |....| 152 | 7fff2f556935 153 | */ 154 | ``` 155 | 156 | ### 2.4 Statistics code running time 157 | 158 | ```C 159 | /* 160 | * Statistics code running time 161 | * @param Code snippet 162 | */ 163 | #define LOGGER_LOCAL_TIME_CODE(logger, ...) ... 164 | #define LOGGER_TIME_CODE(...) LOGGER_LOCAL_TIME_CODE(ULOG_GLOBAL, __VA_ARGS__) 165 | ``` 166 | 167 | Example: 168 | 169 | ```C 170 | LOGGER_TIME_CODE( 171 | 172 | uint32_t n = 1000 * 1000; 173 | while (n--); 174 | 175 | ); 176 | 177 | /* Output: ----------------------------------------------- 178 | time { uint32_t n = 1000 * 1000; while (n--); } => 1315us 179 | */ 180 | ``` 181 | 182 | ## 3 Output customization 183 | 184 | ```C 185 | // Enable log output, which is enabled by default 186 | void logger_enable_output(struct ulog_s *logger, bool enable); 187 | ``` 188 | 189 | ```C 190 | #define ULOG_F_COLOR 1 << 0 // Enable color output (default=on) 191 | #define ULOG_F_NUMBER 1 << 1 // Enable line number output (default=off) 192 | #define ULOG_F_TIME 1 << 2 // Enable time output (default=on) 193 | #define ULOG_F_PROCESS_ID 1 << 3 // Enable process id output (default=on) 194 | #define ULOG_F_LEVEL 1 << 4 // Enable log level output (default=on) 195 | #define ULOG_F_FILE_LINE 1 << 5 // Enable file line output (default=on) 196 | #define ULOG_F_FUNCTION 1 << 6 // Enable function name output (default=on) 197 | 198 | /** 199 | * Enable log format, can use (ULOG_F_XXX | ULOG_F_XXX) combination 200 | */ 201 | void logger_format_enable(struct ulog_s *logger, int32_t format); 202 | 203 | /** 204 | * Disable log format, can use (ULOG_F_XXX | ULOG_F_XXX) combination 205 | */ 206 | void logger_format_disable(struct ulog_s *logger, int32_t format); 207 | ``` 208 | 209 | ```C 210 | // Set the log level. Logs below this level will not be output 211 | // The default level is the lowest level, so logs of all levels are output. 212 | 213 | // Different levels: 214 | // ULOG_LEVEL_TRACE 215 | // ULOG_LEVEL_DEBUG 216 | // ULOG_LEVEL_INFO 217 | // ULOG_LEVEL_WARN 218 | // ULOG_LEVEL_ERROR 219 | // ULOG_LEVEL_FATAL 220 | void logger_set_output_level(struct ulog_s *logger, enum ulog_level_e level); 221 | ``` 222 | -------------------------------------------------------------------------------- /include/ulog/queue/spsc_ring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "lite_notifier.h" 8 | #include "power_of_two.h" 9 | 10 | namespace ulog { 11 | namespace spsc { 12 | 13 | template 14 | class Producer; 15 | template 16 | class Consumer; 17 | template 18 | class Mq; 19 | 20 | template 21 | class DataPacket { 22 | friend class Mq; 23 | friend class Consumer; 24 | 25 | public: 26 | explicit DataPacket(const uint32_t end_index = 0, queue::Packet group0 = queue::Packet{}, 27 | queue::Packet group1 = queue::Packet{}) 28 | : end_index_(end_index), group0_(std::move(group0)), group1_(std::move(group1)) {} 29 | 30 | explicit operator bool() const noexcept { return remain() > 0; } 31 | size_t remain() const { return (group0_ ? 1 : 0) + (group1_ ? 1 : 0); } 32 | 33 | queue::Packet next() { 34 | if (group0_) return std::move(group0_); 35 | if (group1_) return std::move(group1_); 36 | return queue::Packet{0, nullptr}; 37 | } 38 | 39 | private: 40 | uint32_t end_index_; 41 | queue::Packet group0_; 42 | queue::Packet group1_; 43 | }; 44 | 45 | template 46 | class Mq : public std::enable_shared_from_this> { 47 | friend class Producer; 48 | friend class Consumer; 49 | 50 | struct Private { 51 | explicit Private() = default; 52 | }; 53 | 54 | public: 55 | explicit Mq(size_t num_elements, Private) : out_(0), in_(0), last_(0) { 56 | if (num_elements < 2) 57 | num_elements = 2; 58 | else { 59 | // round up to the next power of 2, since our 'let the indices wrap' 60 | // technique works only in this case. 61 | num_elements = queue::RoundUpPowOfTwo(num_elements); 62 | } 63 | mask_ = num_elements - 1; 64 | data_ = new T[num_elements]; 65 | } 66 | ~Mq() { delete[] data_; } 67 | 68 | // Everyone else has to use this factory function 69 | static std::shared_ptr Create(size_t num_elements) { return std::make_shared(num_elements, Private()); } 70 | using Producer = spsc::Producer; 71 | using Consumer = spsc::Consumer; 72 | 73 | /** 74 | * Ensure that all currently written data has been read and processed 75 | * @param wait_time The maximum waiting time 76 | */ 77 | void Flush(const std::chrono::milliseconds wait_time = std::chrono::milliseconds(1000)) { 78 | prod_notifier_.notify_all(); 79 | const auto prod_head = in_.load(); 80 | cons_notifier_.wait_for(wait_time, [&]() { return queue::IsPassed(prod_head, out_.load()); }); 81 | } 82 | 83 | /** 84 | * Notify all waiting threads, so that they can check the status of the queue 85 | */ 86 | void Notify() { 87 | prod_notifier_.notify_all(); 88 | cons_notifier_.notify_all(); 89 | } 90 | 91 | private: 92 | size_t size() const { return mask_ + 1; } 93 | 94 | size_t mask() const { return mask_; } 95 | 96 | size_t next_buffer(const size_t index) const { return (index & ~mask()) + size(); } 97 | 98 | T *data_; 99 | size_t mask_; 100 | 101 | [[maybe_unused]] uint8_t pad0[64]{}; // Using cache line filling technology can improve performance by 15% 102 | 103 | // for reader thread 104 | std::atomic_uint32_t out_; 105 | 106 | [[maybe_unused]] uint8_t pad1[64]{}; 107 | 108 | // for writer thread 109 | std::atomic_uint32_t in_; 110 | std::atomic_uint32_t last_; 111 | 112 | [[maybe_unused]] uint8_t pad2[64]{}; 113 | LiteNotifier prod_notifier_; 114 | LiteNotifier cons_notifier_; 115 | }; 116 | 117 | template 118 | class Producer { 119 | public: 120 | explicit Producer(const std::shared_ptr> &ring) : ring_(ring), wrapped_(false) {} 121 | 122 | /** 123 | * Reserve space of size, automatically retry until timeout 124 | * @param size size of space to reserve 125 | * @param timeout The maximum waiting time if there is insufficient space in the queue 126 | * @return data pointer if successful, otherwise nullptr 127 | */ 128 | T *ReserveOrWaitFor(const size_t size, const std::chrono::milliseconds timeout) { 129 | T *ptr; 130 | ring_->cons_notifier_.wait_for(timeout, [&] { return (ptr = Reserve(size)) != nullptr; }); 131 | return ptr; 132 | } 133 | 134 | T *ReserveOrWait(const size_t size) { 135 | T *ptr; 136 | ring_->cons_notifier_.wait([&] { return (ptr = Reserve(size)) != nullptr; }); 137 | return ptr; 138 | } 139 | 140 | /** 141 | * Try to reserve space of size 142 | * @param size size of space to reserve 143 | * @return data pointer if successful, otherwise nullptr 144 | */ 145 | T *Reserve(const size_t size) { 146 | const auto out = ring_->out_.load(std::memory_order_relaxed); 147 | const auto in = ring_->in_.load(std::memory_order_relaxed); 148 | 149 | const auto unused = ring_->size() - (in - out); 150 | if (unused < size) { 151 | return nullptr; 152 | } 153 | 154 | // The current block has enough free space 155 | if (ring_->size() - (in & ring_->mask()) >= size) { 156 | wrapped_ = false; 157 | return &ring_->data_[in & ring_->mask()]; 158 | } 159 | 160 | // new_pos is in the next block 161 | if ((out & ring_->mask()) >= size) { 162 | wrapped_ = true; 163 | return &ring_->data_[0]; 164 | } 165 | 166 | return nullptr; 167 | } 168 | 169 | /** 170 | * Commits the data to the buffer, so that it can be read out. 171 | * @param size the size of the data to be committed 172 | * 173 | * NOTE: 174 | * The validity of the size is not checked, it needs to be within the range 175 | * returned by the TryReserve function. 176 | */ 177 | void Commit(const T * /* unused */, const size_t size) { 178 | if (size == 0) return; // Discard empty data 179 | 180 | // only written from push thread 181 | const auto in = ring_->in_.load(std::memory_order_relaxed); 182 | 183 | if (wrapped_) { 184 | ring_->last_.store(in, std::memory_order_relaxed); 185 | ring_->in_.store(ring_->next_buffer(in) + size, std::memory_order_release); 186 | } else { 187 | // Whenever we wrap around, we update the last variable to ensure logical 188 | // consistency. 189 | const auto new_pos = in + size; 190 | if ((new_pos & ring_->mask()) == 0) { 191 | ring_->last_.store(new_pos, std::memory_order_relaxed); 192 | } 193 | ring_->in_.store(new_pos, std::memory_order_release); 194 | } 195 | 196 | ring_->prod_notifier_.notify_all(); 197 | } 198 | 199 | private: 200 | std::shared_ptr> ring_; 201 | bool wrapped_; 202 | }; 203 | 204 | template 205 | class Consumer { 206 | public: 207 | explicit Consumer(const std::shared_ptr> &ring) : ring_(ring) {} 208 | 209 | /** 210 | * Gets a pointer to the contiguous block in the buffer, and returns the size of that block. automatically retry until 211 | * timeout 212 | * @param timeout The maximum waiting time 213 | * @param other_condition Other wake-up conditions 214 | * @return pointer to the contiguous block 215 | */ 216 | template 217 | DataPacket ReadOrWait(const std::chrono::milliseconds timeout, Condition other_condition) { 218 | DataPacket ptr; 219 | ring_->prod_notifier_.wait_for(timeout, [&] { return (ptr = Read()).remain() > 0 || other_condition(); }); 220 | return ptr; 221 | } 222 | DataPacket ReadOrWait(const std::chrono::milliseconds timeout) { 223 | return ReadOrWait(timeout, [] { return false; }); 224 | } 225 | template 226 | DataPacket ReadOrWait(Condition other_condition) { 227 | DataPacket ptr; 228 | ring_->prod_notifier_.wait([&] { return (ptr = Read()).remain() > 0 || other_condition(); }); 229 | return ptr; 230 | } 231 | 232 | DataPacket Read() { 233 | const auto in = ring_->in_.load(std::memory_order_acquire); 234 | const auto last = ring_->last_.load(std::memory_order_relaxed); 235 | const auto out = ring_->out_.load(std::memory_order_relaxed); 236 | 237 | if (out == in) { 238 | return DataPacket{out}; 239 | } 240 | 241 | const auto cur_in = in & ring_->mask(); 242 | const auto cur_out = out & ring_->mask(); 243 | 244 | // read and write are still in the same block 245 | if (cur_out < cur_in) { 246 | return DataPacket{in, queue::Packet(in - out, &ring_->data_[cur_out])}; 247 | } 248 | 249 | // read and write are in different blocks, read the current remaining data 250 | if (out != last) { 251 | queue::Packet group0{last - out, &ring_->data_[cur_out]}; 252 | queue::Packet group1{cur_in, cur_in != 0 ? &ring_->data_[0] : nullptr}; 253 | return DataPacket{in, group0, group1}; 254 | } 255 | 256 | if (cur_in == 0) { 257 | return DataPacket{in}; 258 | } 259 | 260 | return DataPacket{in, queue::Packet{cur_in, &ring_->data_[0]}}; 261 | } 262 | 263 | void Release(const DataPacket &data) { 264 | ring_->out_.store(data.end_index_, std::memory_order_release); 265 | ring_->cons_notifier_.notify_all(); 266 | } 267 | 268 | private: 269 | std::shared_ptr> ring_; 270 | }; 271 | 272 | } // namespace spsc 273 | } // namespace ulog 274 | -------------------------------------------------------------------------------- /include/ulog/queue/fifo_power_of_two.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by fs on 2/5/20. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "power_of_two.h" 15 | 16 | namespace ulog { 17 | 18 | // Reference from kfifo of linux 19 | class FifoPowerOfTwo { 20 | template 21 | static inline constexpr bool is_power_of_2(T x) { 22 | return ((x) != 0 && (((x) & ((x)-1)) == 0)); 23 | } 24 | 25 | public: 26 | FifoPowerOfTwo(void *buffer, size_t buf_size, size_t element_size = 1) 27 | : data_((unsigned char *)buffer), 28 | is_allocated_memory_(false), 29 | element_size_(element_size != 0 ? element_size : 1) { 30 | size_t const num_elements = buf_size / element_size_; 31 | 32 | if (num_elements < 2) { 33 | mask_ = 0; 34 | data_ = nullptr; 35 | return; 36 | } 37 | 38 | // round down to the next power of 2, since our 'let the indices wrap' 39 | // technique works only in this case. 40 | // The kernel is roundup_pow_of_two, maybe it's wrong. 41 | mask_ = ulog::queue::RoundDownPowOfTwo(num_elements) - 1; 42 | } 43 | 44 | explicit FifoPowerOfTwo(size_t num_elements, size_t element_size = 1) 45 | : is_allocated_memory_(true), 46 | element_size_(element_size != 0 ? element_size : 1) { 47 | if (num_elements < 2) num_elements = 2; 48 | 49 | // round up to the next power of 2, since our 'let the indices wrap' 50 | // technique works only in this case. 51 | num_elements = queue::RoundUpPowOfTwo(num_elements); 52 | 53 | data_ = new unsigned char[num_elements * element_size_]; 54 | 55 | if (!data_) { 56 | mask_ = 0; 57 | return; 58 | } 59 | mask_ = num_elements - 1; 60 | } 61 | FifoPowerOfTwo(const FifoPowerOfTwo &) = delete; 62 | FifoPowerOfTwo &operator=(const FifoPowerOfTwo &) = delete; 63 | 64 | ~FifoPowerOfTwo() { 65 | if (is_allocated_memory_ && data_) delete[] data_; 66 | } 67 | 68 | size_t InputWaitIfFull(const void *buf, size_t num_elements, 69 | int32_t timeout_ms = -1) { 70 | auto until_condition = [&, this]() { return unused() > num_elements; }; 71 | return InputWaitUntil(buf, num_elements, timeout_ms, until_condition); 72 | } 73 | 74 | template 75 | size_t InputWaitUntil(const void *buf, size_t num_elements, 76 | int32_t timeout_ms, Predicate until_condition) { 77 | if (!buf) { 78 | return 0; 79 | } 80 | 81 | std::unique_lock lg(mutex_); 82 | 83 | if (timeout_ms < 0) { 84 | data_consumption_notify_.wait(lg, until_condition); 85 | } else { 86 | if (!data_consumption_notify_.wait_for( 87 | lg, std::chrono::milliseconds(timeout_ms), until_condition)) { 88 | // Did not wait 89 | return 0; 90 | } 91 | } 92 | 93 | peak_ = max(peak_, used()); 94 | 95 | CopyInLocked(buf, num_elements, in_); 96 | in_ += num_elements; 97 | 98 | data_production_notify_.notify_all(); 99 | return num_elements; 100 | } 101 | 102 | // A packet is entered, either completely written or discarded. 103 | size_t InputPacketOrDrop(const void *buf, size_t num_elements) { 104 | if (!buf) { 105 | return 0; 106 | } 107 | 108 | std::unique_lock lg(mutex_); 109 | 110 | if (unused() < num_elements) { 111 | num_dropped_ += num_elements; 112 | peak_ = size(); 113 | return 0; 114 | } 115 | 116 | peak_ = max(peak_, used()); 117 | 118 | CopyInLocked(buf, num_elements, in_); 119 | in_ += num_elements; 120 | 121 | data_production_notify_.notify_all(); 122 | return num_elements; 123 | } 124 | 125 | size_t Input(const void *buf, size_t num_elements) { 126 | if (!buf) { 127 | return 0; 128 | } 129 | 130 | std::unique_lock lg(mutex_); 131 | 132 | peak_ = max(peak_, used()); 133 | 134 | const size_t len = min(num_elements, unused()); 135 | 136 | CopyInLocked(buf, len, in_); 137 | in_ += len; 138 | num_dropped_ += num_elements - len; 139 | 140 | data_production_notify_.notify_all(); 141 | return len; 142 | } 143 | 144 | size_t OutputPeek(void *out_buf, size_t num_elements) { 145 | if (!out_buf) { 146 | return 0; 147 | } 148 | 149 | std::unique_lock lg(mutex_); 150 | num_elements = min(num_elements, used()); 151 | CopyOutLocked(out_buf, num_elements, out_); 152 | return num_elements; 153 | } 154 | 155 | size_t OutputWaitIfEmpty(void *out_buf, size_t num_elements, 156 | int32_t timeout_ms = -1) { 157 | auto until_condition = [this] { return !empty(); }; 158 | return OutputWaitUntil(out_buf, num_elements, timeout_ms, until_condition); 159 | } 160 | 161 | template 162 | size_t OutputWaitUntil(void *out_buf, size_t num_elements, int32_t timeout_ms, 163 | Predicate until_condition) { 164 | if (!out_buf) { 165 | return 0; 166 | } 167 | 168 | std::unique_lock lg(mutex_); 169 | 170 | if (timeout_ms < 0) { 171 | data_production_notify_.wait(lg, until_condition); 172 | } else { 173 | if (!data_production_notify_.wait_for( 174 | lg, std::chrono::milliseconds{timeout_ms}, until_condition)) { 175 | // Did not wait 176 | return 0; 177 | } 178 | } 179 | 180 | num_elements = min(num_elements, used()); 181 | if (!empty()) { 182 | CopyOutLocked(out_buf, num_elements, out_); 183 | out_ += num_elements; 184 | } 185 | 186 | if (empty()) { 187 | data_empty_notify_.notify_all(); 188 | } 189 | data_consumption_notify_.notify_all(); 190 | 191 | return num_elements; 192 | } 193 | 194 | size_t Output(void *out_buf, size_t num_elements) { 195 | if (!out_buf) { 196 | return 0; 197 | } 198 | 199 | std::unique_lock lg(mutex_); 200 | num_elements = min(num_elements, used()); 201 | CopyOutLocked(out_buf, num_elements, out_); 202 | out_ += num_elements; 203 | 204 | if (empty()) { 205 | data_empty_notify_.notify_all(); 206 | } 207 | data_consumption_notify_.notify_all(); 208 | 209 | return num_elements; 210 | } 211 | 212 | void Flush() const { 213 | std::unique_lock lg(mutex_); 214 | data_empty_notify_.wait(lg, [&] { return empty(); }); 215 | } 216 | 217 | void InterruptOutput() const { data_production_notify_.notify_all(); } 218 | 219 | /** 220 | * removes the entire fifo content 221 | * 222 | * Note: usage of Reset() is dangerous. It should be only called when the 223 | * fifo is exclusived locked or when it is secured that no other thread is 224 | * accessing the fifo. 225 | */ 226 | void Reset() { 227 | std::unique_lock lg(mutex_); 228 | out_ = in_; 229 | } 230 | 231 | /** 232 | * Check if the fifo is initialized 233 | * Return %true if fifo is initialized, otherwise %false. 234 | * Assumes the fifo was 0 before. 235 | */ 236 | bool initialized() const { return mask_ != 0; }; 237 | 238 | /* returns the size of the fifo in elements */ 239 | size_t size() const { return mask_ + 1; } 240 | 241 | /* returns the number of used elements in the fifo */ 242 | size_t used() const { return in_ - out_; } 243 | bool empty() const { return in_ == out_; } 244 | 245 | /* returns the number of unused elements in the fifo */ 246 | size_t unused() const { return size() - used(); } 247 | 248 | /* DEBUG: Used to count the number of new data discarded during use */ 249 | size_t num_dropped() const { return num_dropped_; } 250 | 251 | /* DEBUG: Used to count the maximum peak value during use */ 252 | size_t peak() const { return peak_; } 253 | 254 | private: 255 | uint8_t pad0[64]; 256 | size_t in_{}; // data is added at offset (in % size) 257 | uint8_t pad1[64]; 258 | size_t out_{}; // data is extracted from off. (out % size) 259 | uint8_t pad2[64]; 260 | unsigned char *data_{}; // the buffer holding the data 261 | const bool is_allocated_memory_; // Used to identify whether the internal 262 | // buffer is allocated internally 263 | size_t mask_{}; // (Constant) Mask used to match the correct in / out pointer 264 | const size_t element_size_; // the size of the element 265 | size_t num_dropped_{}; // Number of dropped elements 266 | size_t peak_{}; // fifo peak 267 | 268 | mutable std::mutex mutex_{}; 269 | mutable std::condition_variable data_production_notify_{}; 270 | mutable std::condition_variable data_consumption_notify_{}; 271 | mutable std::condition_variable data_empty_notify_{}; 272 | 273 | void CopyInLocked(const void *src, size_t len, size_t off) const { 274 | size_t size = this->size(); 275 | 276 | off &= mask_; 277 | if (element_size_ != 1) { 278 | off *= element_size_; 279 | size *= element_size_; 280 | len *= element_size_; 281 | } 282 | size_t l = min(len, size - off); 283 | 284 | memcpy(data_ + off, src, l); 285 | memcpy(data_, (unsigned char *)src + l, len - l); 286 | } 287 | 288 | void CopyOutLocked(void *dst, size_t len, size_t off) const { 289 | size_t size = this->size(); 290 | size_t l; 291 | 292 | off &= mask_; 293 | if (element_size_ != 1) { 294 | off *= element_size_; 295 | size *= element_size_; 296 | len *= element_size_; 297 | } 298 | l = min(len, size - off); 299 | 300 | memcpy(dst, data_ + off, l); 301 | memcpy((unsigned char *)dst + l, data_, len - l); 302 | } 303 | 304 | template 305 | static inline T min(T x, T y) { 306 | return x < y ? x : y; 307 | } 308 | template 309 | static inline T max(T x, T y) { 310 | return x > y ? x : y; 311 | } 312 | }; 313 | 314 | } // namespace ulog 315 | -------------------------------------------------------------------------------- /tools/logroller/cmdline.h: -------------------------------------------------------------------------------- 1 | /** @file cmdline.h 2 | * @brief The header file for the command line option parser 3 | * generated by GNU Gengetopt version 2.23 4 | * http://www.gnu.org/software/gengetopt. 5 | * DO NOT modify this file, since it can be overwritten 6 | * @author GNU Gengetopt */ 7 | 8 | #ifndef CMDLINE_H 9 | #define CMDLINE_H 10 | 11 | /* If we use autoconf. */ 12 | #ifdef HAVE_CONFIG_H 13 | #include "config.h" 14 | #endif 15 | 16 | #include /* for FILE */ 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif /* __cplusplus */ 21 | 22 | #ifndef CMDLINE_PARSER_PACKAGE 23 | /** @brief the program name (used for printing errors) */ 24 | #define CMDLINE_PARSER_PACKAGE "logroller" 25 | #endif 26 | 27 | #ifndef CMDLINE_PARSER_PACKAGE_NAME 28 | /** @brief the complete program name (used for help and version) */ 29 | #define CMDLINE_PARSER_PACKAGE_NAME "logroller" 30 | #endif 31 | 32 | #ifndef CMDLINE_PARSER_VERSION 33 | /** @brief the program version */ 34 | #define CMDLINE_PARSER_VERSION "0.6.0" 35 | #endif 36 | 37 | /** @brief Where the command line options are stored */ 38 | struct gengetopt_args_info 39 | { 40 | const char *help_help; /**< @brief Print help and exit help description. */ 41 | const char *version_help; /**< @brief Print version and exit help description. */ 42 | char * file_path_arg; /**< @brief File path to record log. */ 43 | char * file_path_orig; /**< @brief File path to record log original value given at command line. */ 44 | const char *file_path_help; /**< @brief File path to record log help description. */ 45 | char * file_size_arg; /**< @brief Size of each file (default='1MB'). */ 46 | char * file_size_orig; /**< @brief Size of each file original value given at command line. */ 47 | const char *file_size_help; /**< @brief Size of each file help description. */ 48 | int max_files_arg; /**< @brief Maximum number of files (default='8'). */ 49 | char * max_files_orig; /**< @brief Maximum number of files original value given at command line. */ 50 | const char *max_files_help; /**< @brief Maximum number of files help description. */ 51 | char * flush_interval_arg; /**< @brief Time interval between flushing to disk (default='1s'). */ 52 | char * flush_interval_orig; /**< @brief Time interval between flushing to disk original value given at command line. */ 53 | const char *flush_interval_help; /**< @brief Time interval between flushing to disk help description. */ 54 | char * rotation_strategy_arg; /**< @brief File rotation strategy: 55 | rename: log.1.txt -> log.2.txt 56 | incremental: log-24.txt ... log-34.txt (default='rename'). */ 57 | char * rotation_strategy_orig; /**< @brief File rotation strategy: 58 | rename: log.1.txt -> log.2.txt 59 | incremental: log-24.txt ... log-34.txt original value given at command line. */ 60 | const char *rotation_strategy_help; /**< @brief File rotation strategy: 61 | rename: log.1.txt -> log.2.txt 62 | incremental: log-24.txt ... log-34.txt help description. */ 63 | int rotate_first_flag; /**< @brief Should rotate first before write (default=off). */ 64 | const char *rotate_first_help; /**< @brief Should rotate first before write help description. */ 65 | char * fifo_size_arg; /**< @brief Size of the FIFO buffer (default='32KB'). */ 66 | char * fifo_size_orig; /**< @brief Size of the FIFO buffer original value given at command line. */ 67 | const char *fifo_size_help; /**< @brief Size of the FIFO buffer help description. */ 68 | int zstd_compress_flag; /**< @brief Compress with zstd (default=off). */ 69 | const char *zstd_compress_help; /**< @brief Compress with zstd help description. */ 70 | char * zstd_params_arg; /**< @brief Parameters for zstd compression, 71 | larger == more compression and memory (e.g., level=3,window-log=21,chain-log=16,hash-log=17). */ 72 | char * zstd_params_orig; /**< @brief Parameters for zstd compression, 73 | larger == more compression and memory (e.g., level=3,window-log=21,chain-log=16,hash-log=17) original value given at command line. */ 74 | const char *zstd_params_help; /**< @brief Parameters for zstd compression, 75 | larger == more compression and memory (e.g., level=3,window-log=21,chain-log=16,hash-log=17) help description. */ 76 | 77 | unsigned int help_given ; /**< @brief Whether help was given. */ 78 | unsigned int version_given ; /**< @brief Whether version was given. */ 79 | unsigned int file_path_given ; /**< @brief Whether file-path was given. */ 80 | unsigned int file_size_given ; /**< @brief Whether file-size was given. */ 81 | unsigned int max_files_given ; /**< @brief Whether max-files was given. */ 82 | unsigned int flush_interval_given ; /**< @brief Whether flush-interval was given. */ 83 | unsigned int rotation_strategy_given ; /**< @brief Whether rotation-strategy was given. */ 84 | unsigned int rotate_first_given ; /**< @brief Whether rotate-first was given. */ 85 | unsigned int fifo_size_given ; /**< @brief Whether fifo-size was given. */ 86 | unsigned int zstd_compress_given ; /**< @brief Whether zstd-compress was given. */ 87 | unsigned int zstd_params_given ; /**< @brief Whether zstd-params was given. */ 88 | 89 | } ; 90 | 91 | /** @brief The additional parameters to pass to parser functions */ 92 | struct cmdline_parser_params 93 | { 94 | int override; /**< @brief whether to override possibly already present options (default 0) */ 95 | int initialize; /**< @brief whether to initialize the option structure gengetopt_args_info (default 1) */ 96 | int check_required; /**< @brief whether to check that all required options were provided (default 1) */ 97 | int check_ambiguity; /**< @brief whether to check for options already specified in the option structure gengetopt_args_info (default 0) */ 98 | int print_errors; /**< @brief whether getopt_long should print an error message for a bad option (default 1) */ 99 | } ; 100 | 101 | /** @brief the purpose string of the program */ 102 | extern const char *gengetopt_args_info_purpose; 103 | /** @brief the usage string of the program */ 104 | extern const char *gengetopt_args_info_usage; 105 | /** @brief the description string of the program */ 106 | extern const char *gengetopt_args_info_description; 107 | /** @brief all the lines making the help output */ 108 | extern const char *gengetopt_args_info_help[]; 109 | 110 | /** 111 | * The command line parser 112 | * @param argc the number of command line options 113 | * @param argv the command line options 114 | * @param args_info the structure where option information will be stored 115 | * @return 0 if everything went fine, NON 0 if an error took place 116 | */ 117 | int cmdline_parser (int argc, char **argv, 118 | struct gengetopt_args_info *args_info); 119 | 120 | /** 121 | * The command line parser (version with additional parameters - deprecated) 122 | * @param argc the number of command line options 123 | * @param argv the command line options 124 | * @param args_info the structure where option information will be stored 125 | * @param override whether to override possibly already present options 126 | * @param initialize whether to initialize the option structure my_args_info 127 | * @param check_required whether to check that all required options were provided 128 | * @return 0 if everything went fine, NON 0 if an error took place 129 | * @deprecated use cmdline_parser_ext() instead 130 | */ 131 | int cmdline_parser2 (int argc, char **argv, 132 | struct gengetopt_args_info *args_info, 133 | int override, int initialize, int check_required); 134 | 135 | /** 136 | * The command line parser (version with additional parameters) 137 | * @param argc the number of command line options 138 | * @param argv the command line options 139 | * @param args_info the structure where option information will be stored 140 | * @param params additional parameters for the parser 141 | * @return 0 if everything went fine, NON 0 if an error took place 142 | */ 143 | int cmdline_parser_ext (int argc, char **argv, 144 | struct gengetopt_args_info *args_info, 145 | struct cmdline_parser_params *params); 146 | 147 | /** 148 | * Save the contents of the option struct into an already open FILE stream. 149 | * @param outfile the stream where to dump options 150 | * @param args_info the option struct to dump 151 | * @return 0 if everything went fine, NON 0 if an error took place 152 | */ 153 | int cmdline_parser_dump(FILE *outfile, 154 | struct gengetopt_args_info *args_info); 155 | 156 | /** 157 | * Save the contents of the option struct into a (text) file. 158 | * This file can be read by the config file parser (if generated by gengetopt) 159 | * @param filename the file where to save 160 | * @param args_info the option struct to save 161 | * @return 0 if everything went fine, NON 0 if an error took place 162 | */ 163 | int cmdline_parser_file_save(const char *filename, 164 | struct gengetopt_args_info *args_info); 165 | 166 | /** 167 | * Print the help 168 | */ 169 | void cmdline_parser_print_help(void); 170 | /** 171 | * Print the version 172 | */ 173 | void cmdline_parser_print_version(void); 174 | 175 | /** 176 | * Initializes all the fields a cmdline_parser_params structure 177 | * to their default values 178 | * @param params the structure to initialize 179 | */ 180 | void cmdline_parser_params_init(struct cmdline_parser_params *params); 181 | 182 | /** 183 | * Allocates dynamically a cmdline_parser_params structure and initializes 184 | * all its fields to their default values 185 | * @return the created and initialized cmdline_parser_params structure 186 | */ 187 | struct cmdline_parser_params *cmdline_parser_params_create(void); 188 | 189 | /** 190 | * Initializes the passed gengetopt_args_info structure's fields 191 | * (also set default values for options that have a default) 192 | * @param args_info the structure to initialize 193 | */ 194 | void cmdline_parser_init (struct gengetopt_args_info *args_info); 195 | /** 196 | * Deallocates the string fields of the gengetopt_args_info structure 197 | * (but does not deallocate the structure itself) 198 | * @param args_info the structure to deallocate 199 | */ 200 | void cmdline_parser_free (struct gengetopt_args_info *args_info); 201 | 202 | /** 203 | * Checks that all the required options were specified 204 | * @param args_info the structure to check 205 | * @param prog_name the name of the program that will be used to print 206 | * possible errors 207 | * @return 208 | */ 209 | int cmdline_parser_required (struct gengetopt_args_info *args_info, 210 | const char *prog_name); 211 | 212 | extern const char *cmdline_parser_rotation_strategy_values[]; /**< @brief Possible values for rotation-strategy. */ 213 | 214 | 215 | #ifdef __cplusplus 216 | } 217 | #endif /* __cplusplus */ 218 | #endif /* CMDLINE_H */ 219 | -------------------------------------------------------------------------------- /src/ulog.c: -------------------------------------------------------------------------------- 1 | #include "ulog/ulog.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define ULOG_DEFAULT_FORMAT \ 14 | (ULOG_F_COLOR | ULOG_F_TIME | ULOG_F_LEVEL | ULOG_F_FILE_LINE | ULOG_F_FUNCTION | ULOG_F_PROCESS_ID) 15 | 16 | enum { 17 | INDEX_LEVEL_COLOR, 18 | INDEX_LEVEL_MARK, 19 | INDEX_MAX, 20 | }; 21 | 22 | static const char *level_infos[ULOG_LEVEL_NUMBER][INDEX_MAX] = { 23 | {ULOG_STR_WHITE, "T"}, // TRACE 24 | {ULOG_STR_BLUE, "D"}, // DEBUG 25 | {ULOG_STR_GREEN, "I"}, // INFO 26 | {ULOG_STR_YELLOW, "W"}, // WARN 27 | {ULOG_STR_RED, "E"}, // ERROR 28 | {ULOG_STR_PURPLE, "F"}, // FATAL 29 | }; 30 | 31 | static inline int logger_printf(void *unused, const char *str) { 32 | (void)unused; 33 | return printf("%s", str); 34 | } 35 | 36 | static inline int logger_get_pid() { 37 | static int process_id = -1; 38 | if (process_id == -1) { 39 | process_id = getpid(); 40 | } 41 | return process_id; 42 | } 43 | 44 | // Get and thread id 45 | #if defined(__APPLE__) 46 | #include 47 | static inline long logger_get_tid() { return pthread_mach_thread_np(pthread_self()); } 48 | #else // defined(__APPLE__) 49 | #include 50 | static inline long logger_get_tid() { return syscall(SYS_gettid); } 51 | #endif // defined(__APPLE__) 52 | 53 | struct ulog_s { 54 | // Internal data 55 | atomic_int log_num_; 56 | 57 | // Private data set by the user will be passed to the output function 58 | void *user_data_; 59 | ulog_output_callback output_cb_; 60 | ulog_flush_callback flush_cb_; 61 | 62 | // Format configuration 63 | enum ulog_level_e log_level_; 64 | uint8_t format_; 65 | bool log_output_enabled_; 66 | }; 67 | 68 | // Will be exported externally 69 | static struct ulog_s global_logger_instance_ = { 70 | .log_num_ = 1, 71 | 72 | // Logger default configuration 73 | .user_data_ = NULL, 74 | .output_cb_ = logger_printf, 75 | .flush_cb_ = NULL, 76 | 77 | .format_ = ULOG_DEFAULT_FORMAT, 78 | .log_output_enabled_ = true, 79 | .log_level_ = ULOG_LEVEL_TRACE, 80 | }; 81 | 82 | struct ulog_s *ulog_global_logger = &global_logger_instance_; 83 | 84 | static inline bool is_logger_valid(struct ulog_s *logger) { 85 | return logger && logger->output_cb_ && logger->log_output_enabled_; 86 | } 87 | 88 | struct ulog_s *logger_create() { 89 | struct ulog_s *logger = malloc(sizeof(struct ulog_s)); 90 | if (!logger) return NULL; 91 | memset(logger, 0, sizeof(struct ulog_s)); 92 | 93 | logger->log_num_ = 1; 94 | 95 | logger->user_data_ = NULL; 96 | logger->output_cb_ = NULL; 97 | logger->flush_cb_ = NULL; 98 | 99 | logger->log_output_enabled_ = true; 100 | logger->format_ = ULOG_DEFAULT_FORMAT; 101 | logger->log_level_ = ULOG_LEVEL_TRACE; 102 | return logger; 103 | } 104 | 105 | void logger_destroy(struct ulog_s **logger_ptr) { 106 | if (!logger_ptr || !*logger_ptr) { 107 | return; 108 | } 109 | free(*logger_ptr); 110 | (*logger_ptr) = NULL; 111 | } 112 | 113 | #define ULOG_SET(logger, dest, source) \ 114 | ({ \ 115 | if (logger) (logger)->dest = source; \ 116 | }) 117 | 118 | void logger_set_user_data(struct ulog_s *logger, void *user_data) { ULOG_SET(logger, user_data_, user_data); } 119 | 120 | void logger_set_output_callback(struct ulog_s *logger, ulog_output_callback output_callback) { 121 | ULOG_SET(logger, output_cb_, output_callback); 122 | } 123 | 124 | void logger_set_flush_callback(struct ulog_s *logger, ulog_flush_callback flush_callback) { 125 | ULOG_SET(logger, flush_cb_, flush_callback); 126 | } 127 | 128 | void logger_enable_output(struct ulog_s *logger, bool enable) { 129 | (void)(logger && (logger->log_output_enabled_ = enable)); 130 | } 131 | 132 | void logger_format_enable(struct ulog_s *logger, int32_t format) { (void)(logger && (logger->format_ |= format)); } 133 | 134 | void logger_format_disable(struct ulog_s *logger, int32_t format) { (void)(logger && (logger->format_ &= ~format)); } 135 | 136 | bool logger_check_format(struct ulog_s *logger, int32_t format) { return logger && (logger->format_ & format); } 137 | 138 | void logger_set_output_level(struct ulog_s *logger, enum ulog_level_e level) { 139 | (void)(logger && (logger->log_level_ = level)); 140 | } 141 | 142 | uint64_t logger_real_time_us() { 143 | struct timespec tp; 144 | clock_gettime(CLOCK_REALTIME, &tp); 145 | return (uint64_t)(tp.tv_sec) * 1000 * 1000 + tp.tv_nsec / 1000; 146 | } 147 | 148 | uint64_t logger_monotonic_time_us() { 149 | struct timespec tp; 150 | clock_gettime(CLOCK_MONOTONIC, &tp); 151 | return (uint64_t)(tp.tv_sec) * 1000 * 1000 + tp.tv_nsec / 1000; 152 | } 153 | 154 | static inline int logger_flush(struct ulog_s *logger, struct ulog_buffer_s *log_buffer) { 155 | int ret = 0; 156 | if (is_logger_valid(logger) && log_buffer->cur_buf_ptr_ != log_buffer->log_out_buf_) { 157 | ret = logger->output_cb_(logger->user_data_, log_buffer->log_out_buf_); 158 | log_buffer->cur_buf_ptr_ = log_buffer->log_out_buf_; 159 | } 160 | return ret; 161 | } 162 | 163 | uintptr_t logger_hex_dump(struct ulog_s *logger, const void *data, size_t length, size_t width, uintptr_t base_address, 164 | bool tail_addr_out) { 165 | if (!data || width == 0 || !is_logger_valid(logger)) return 0; 166 | 167 | struct ulog_buffer_s log_buffer; 168 | logger_buffer_init(&log_buffer); 169 | 170 | const uint8_t *data_raw = data; 171 | const uint8_t *data_cur = data; 172 | 173 | bool out_break = false; 174 | while (length) { 175 | logger_snprintf(&log_buffer, "%08" PRIxPTR " ", data_cur - data_raw + base_address); 176 | for (size_t i = 0; i < width; i++) { 177 | if (i < length) 178 | logger_snprintf(&log_buffer, "%02" PRIx8 " %s", data_cur[i], i == width / 2 - 1 ? " " : ""); 179 | else 180 | logger_snprintf(&log_buffer, " %s", i == width / 2 - 1 ? " " : ""); 181 | } 182 | logger_snprintf(&log_buffer, " |"); 183 | for (size_t i = 0; i < width && i < length; i++) 184 | logger_snprintf(&log_buffer, "%c", isprint(data_cur[i]) ? data_cur[i] : '.'); 185 | logger_snprintf(&log_buffer, "|\n"); 186 | 187 | // The output fails, in order to avoid output confusion, the rest will not 188 | // be output 189 | if (logger_flush(logger, &log_buffer) <= 0) { 190 | out_break = true; 191 | break; 192 | } 193 | 194 | data_cur += length < width ? length : width; 195 | length -= length < width ? length : width; 196 | } 197 | 198 | if (out_break) { 199 | logger_snprintf(&log_buffer, "hex dump is break!\n"); 200 | } else if (tail_addr_out) { 201 | logger_snprintf(&log_buffer, "%08" PRIxPTR "\n", data_cur - data_raw + base_address); 202 | } 203 | logger_flush(logger, &log_buffer); 204 | return data_cur - data_raw + base_address; 205 | } 206 | 207 | void logger_raw(struct ulog_s *logger, enum ulog_level_e level, const char *fmt, ...) { 208 | if (!is_logger_valid(logger) || !fmt || level < logger->log_level_) return; 209 | 210 | struct ulog_buffer_s log_buffer; 211 | logger_buffer_init(&log_buffer); 212 | 213 | va_list ap; 214 | va_start(ap, fmt); 215 | logger_vsnprintf(&log_buffer, fmt, ap); 216 | va_end(ap); 217 | 218 | logger_flush(logger, &log_buffer); 219 | } 220 | 221 | void logger_log_with_header(struct ulog_s *logger, enum ulog_level_e level, const char *file, const char *func, 222 | uint32_t line, bool newline, bool flush, const char *fmt, ...) { 223 | if (!is_logger_valid(logger) || !fmt || level < logger->log_level_) return; 224 | 225 | struct ulog_buffer_s log_buffer; 226 | logger_buffer_init(&log_buffer); 227 | 228 | // Color 229 | if (logger_check_format(logger, ULOG_F_NUMBER | ULOG_F_TIME | ULOG_F_LEVEL)) 230 | logger_snprintf(&log_buffer, "%s", 231 | logger_check_format(logger, ULOG_F_COLOR) ? level_infos[level][INDEX_LEVEL_COLOR] : ""); 232 | 233 | const uint32_t log_num = atomic_fetch_add_explicit(&logger->log_num_, 1, memory_order_relaxed); 234 | 235 | // Print serial number 236 | if (logger_check_format(logger, ULOG_F_NUMBER)) logger_snprintf(&log_buffer, "#%06" PRIu32 " ", log_num); 237 | 238 | // Print time 239 | if (logger_check_format(logger, ULOG_F_TIME)) { 240 | uint64_t time_ms = logger_real_time_us() / 1000; 241 | time_t time_s = (time_t)(time_ms / 1000); 242 | struct tm lt = *localtime(&time_s); 243 | logger_snprintf(&log_buffer, "%04d-%02d-%02d %02d:%02d:%02d.%03d ", lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, 244 | lt.tm_hour, lt.tm_min, lt.tm_sec, (int)(time_ms % 1000)); 245 | } 246 | 247 | // Print process and thread id 248 | if (logger_check_format(logger, ULOG_F_PROCESS_ID)) 249 | logger_snprintf(&log_buffer, "%" PRId32 "-%" PRId32 " ", (int32_t)logger_get_pid(), (int32_t)logger_get_tid()); 250 | 251 | // Print level 252 | if (logger_check_format(logger, ULOG_F_LEVEL)) 253 | logger_snprintf(&log_buffer, "%s", level_infos[level][INDEX_LEVEL_MARK]); 254 | 255 | // Print gray color 256 | if (logger_check_format(logger, ULOG_F_LEVEL | ULOG_F_FILE_LINE | ULOG_F_FUNCTION)) 257 | logger_snprintf(&log_buffer, "%s", logger_check_format(logger, ULOG_F_COLOR) ? ULOG_STR_GRAY : ""); 258 | 259 | if (logger_check_format(logger, ULOG_F_LEVEL)) logger_snprintf(&log_buffer, " "); 260 | 261 | // Print '(' 262 | if (logger_check_format(logger, ULOG_F_FILE_LINE | ULOG_F_FUNCTION)) logger_snprintf(&log_buffer, "("); 263 | 264 | // Print file and line 265 | if (logger_check_format(logger, ULOG_F_FILE_LINE)) logger_snprintf(&log_buffer, "%s:%" PRIu32, file, line); 266 | 267 | // Print function 268 | if (logger_check_format(logger, ULOG_F_FUNCTION)) 269 | logger_snprintf(&log_buffer, "%s%s", logger_check_format(logger, ULOG_F_FILE_LINE) ? " " : "", func); 270 | 271 | // Print ')' 272 | if (logger_check_format(logger, ULOG_F_FILE_LINE | ULOG_F_FUNCTION)) logger_snprintf(&log_buffer, ")"); 273 | 274 | // Print ' ' 275 | if (logger_check_format(logger, ULOG_F_LEVEL | ULOG_F_FILE_LINE | ULOG_F_FUNCTION)) logger_snprintf(&log_buffer, " "); 276 | 277 | // Print log info 278 | logger_snprintf(&log_buffer, "%s", 279 | logger_check_format(logger, ULOG_F_COLOR) ? level_infos[level][INDEX_LEVEL_COLOR] : ""); 280 | 281 | va_list ap; 282 | va_start(ap, fmt); 283 | logger_vsnprintf(&log_buffer, fmt, ap); 284 | va_end(ap); 285 | 286 | logger_snprintf(&log_buffer, "%s", logger_check_format(logger, ULOG_F_COLOR) ? ULOG_STR_RESET : ""); 287 | 288 | if (newline) logger_snprintf(&log_buffer, "\n"); 289 | 290 | if (flush) { 291 | logger_flush(logger, &log_buffer); 292 | 293 | if (level == ULOG_LEVEL_FATAL && logger->flush_cb_) { 294 | logger->flush_cb_(logger->user_data_); 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/c,c++,cmake,clion+all,visualstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=c,c++,cmake,clion+all,visualstudio,visualstudiocode 4 | 5 | ### C ### 6 | # Prerequisites 7 | *.d 8 | 9 | # Object files 10 | *.o 11 | *.ko 12 | *.obj 13 | *.elf 14 | 15 | # Linker output 16 | *.ilk 17 | *.map 18 | *.exp 19 | 20 | # Precompiled Headers 21 | *.gch 22 | *.pch 23 | 24 | # Libraries 25 | *.lib 26 | *.a 27 | *.la 28 | *.lo 29 | 30 | # Shared objects (inc. Windows DLLs) 31 | *.dll 32 | *.so 33 | *.so.* 34 | *.dylib 35 | 36 | # Executables 37 | *.exe 38 | *.out 39 | *.app 40 | *.i*86 41 | *.x86_64 42 | *.hex 43 | 44 | # Debug files 45 | *.dSYM/ 46 | *.su 47 | *.idb 48 | *.pdb 49 | 50 | # Kernel Module Compile Results 51 | *.mod* 52 | *.cmd 53 | .tmp_versions/ 54 | modules.order 55 | Module.symvers 56 | Mkfile.old 57 | dkms.conf 58 | 59 | ### C++ ### 60 | # Prerequisites 61 | 62 | # Compiled Object files 63 | *.slo 64 | 65 | # Precompiled Headers 66 | 67 | # Compiled Dynamic libraries 68 | 69 | # Fortran module files 70 | *.mod 71 | *.smod 72 | 73 | # Compiled Static libraries 74 | *.lai 75 | 76 | # Executables 77 | 78 | ### CLion+all ### 79 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 80 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 81 | 82 | # User-specific stuff 83 | .idea/**/workspace.xml 84 | .idea/**/tasks.xml 85 | .idea/**/usage.statistics.xml 86 | .idea/**/dictionaries 87 | .idea/**/shelf 88 | 89 | # Generated files 90 | .idea/**/contentModel.xml 91 | 92 | # Sensitive or high-churn files 93 | .idea/**/dataSources/ 94 | .idea/**/dataSources.ids 95 | .idea/**/dataSources.local.xml 96 | .idea/**/sqlDataSources.xml 97 | .idea/**/dynamic.xml 98 | .idea/**/uiDesigner.xml 99 | .idea/**/dbnavigator.xml 100 | 101 | # Gradle 102 | .idea/**/gradle.xml 103 | .idea/**/libraries 104 | 105 | # Gradle and Maven with auto-import 106 | # When using Gradle or Maven with auto-import, you should exclude module files, 107 | # since they will be recreated, and may cause churn. Uncomment if using 108 | # auto-import. 109 | # .idea/modules.xml 110 | # .idea/*.iml 111 | # .idea/modules 112 | # *.iml 113 | # *.ipr 114 | 115 | # CMake 116 | cmake-build-*/ 117 | 118 | # Mongo Explorer plugin 119 | .idea/**/mongoSettings.xml 120 | 121 | # File-based project format 122 | *.iws 123 | 124 | # IntelliJ 125 | out/ 126 | 127 | # mpeltonen/sbt-idea plugin 128 | .idea_modules/ 129 | 130 | # JIRA plugin 131 | atlassian-ide-plugin.xml 132 | 133 | # Cursive Clojure plugin 134 | .idea/replstate.xml 135 | 136 | # Crashlytics plugin (for Android Studio and IntelliJ) 137 | com_crashlytics_export_strings.xml 138 | crashlytics.properties 139 | crashlytics-build.properties 140 | fabric.properties 141 | 142 | # Editor-based Rest Client 143 | .idea/httpRequests 144 | 145 | # Android studio 3.1+ serialized cache file 146 | .idea/caches/build_file_checksums.ser 147 | 148 | ### CLion+all Patch ### 149 | # Ignores the whole .idea folder and all .iml files 150 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 151 | 152 | .idea/ 153 | 154 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 155 | 156 | *.iml 157 | modules.xml 158 | .idea/misc.xml 159 | *.ipr 160 | 161 | # Sonarlint plugin 162 | .idea/sonarlint 163 | 164 | ### CMake ### 165 | CMakeLists.txt.user 166 | CMakeCache.txt 167 | CMakeFiles 168 | CMakeScripts 169 | Testing 170 | Makefile 171 | cmake_install.cmake 172 | install_manifest.txt 173 | compile_commands.json 174 | CTestTestfile.cmake 175 | _deps 176 | 177 | ### CMake Patch ### 178 | # External projects 179 | *-prefix/ 180 | 181 | ### VisualStudioCode ### 182 | .vscode/* 183 | !.vscode/settings.json 184 | !.vscode/tasks.json 185 | !.vscode/launch.json 186 | !.vscode/extensions.json 187 | 188 | ### VisualStudioCode Patch ### 189 | # Ignore all local history of files 190 | .history 191 | 192 | ### VisualStudio ### 193 | ## Ignore Visual Studio temporary files, build results, and 194 | ## files generated by popular Visual Studio add-ons. 195 | ## 196 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 197 | 198 | # User-specific files 199 | *.rsuser 200 | *.suo 201 | *.user 202 | *.userosscache 203 | *.sln.docstates 204 | 205 | # User-specific files (MonoDevelop/Xamarin Studio) 206 | *.userprefs 207 | 208 | # Mono auto generated files 209 | mono_crash.* 210 | 211 | # Build results 212 | [Dd]ebug/ 213 | [Dd]ebugPublic/ 214 | [Rr]elease/ 215 | [Rr]eleases/ 216 | x64/ 217 | x86/ 218 | [Aa][Rr][Mm]/ 219 | [Aa][Rr][Mm]64/ 220 | bld/ 221 | [Bb]in/ 222 | [Oo]bj/ 223 | [Ll]og/ 224 | 225 | # Visual Studio 2015/2017 cache/options directory 226 | .vs/ 227 | # Uncomment if you have tasks that create the project's static files in wwwroot 228 | #wwwroot/ 229 | 230 | # Visual Studio 2017 auto generated files 231 | Generated\ Files/ 232 | 233 | # MSTest test Results 234 | [Tt]est[Rr]esult*/ 235 | [Bb]uild[Ll]og.* 236 | 237 | # NUnit 238 | *.VisualState.xml 239 | TestResult.xml 240 | nunit-*.xml 241 | 242 | # Build Results of an ATL Project 243 | [Dd]ebugPS/ 244 | [Rr]eleasePS/ 245 | dlldata.c 246 | 247 | # Benchmark Results 248 | BenchmarkDotNet.Artifacts/ 249 | 250 | # .NET Core 251 | project.lock.json 252 | project.fragment.lock.json 253 | artifacts/ 254 | 255 | # StyleCop 256 | StyleCopReport.xml 257 | 258 | # Files built by Visual Studio 259 | *_i.c 260 | *_p.c 261 | *_h.h 262 | *.meta 263 | *.iobj 264 | *.ipdb 265 | *.pgc 266 | *.pgd 267 | *.rsp 268 | *.sbr 269 | *.tlb 270 | *.tli 271 | *.tlh 272 | *.tmp 273 | *.tmp_proj 274 | *_wpftmp.csproj 275 | *.log 276 | *.vspscc 277 | *.vssscc 278 | .builds 279 | *.pidb 280 | *.svclog 281 | *.scc 282 | 283 | # Chutzpah Test files 284 | _Chutzpah* 285 | 286 | # Visual C++ cache files 287 | ipch/ 288 | *.aps 289 | *.ncb 290 | *.opendb 291 | *.opensdf 292 | *.sdf 293 | *.cachefile 294 | *.VC.db 295 | *.VC.VC.opendb 296 | 297 | # Visual Studio profiler 298 | *.psess 299 | *.vsp 300 | *.vspx 301 | *.sap 302 | 303 | # Visual Studio Trace Files 304 | *.e2e 305 | 306 | # TFS 2012 Local Workspace 307 | $tf/ 308 | 309 | # Guidance Automation Toolkit 310 | *.gpState 311 | 312 | # ReSharper is a .NET coding add-in 313 | _ReSharper*/ 314 | *.[Rr]e[Ss]harper 315 | *.DotSettings.user 316 | 317 | # JustCode is a .NET coding add-in 318 | .JustCode 319 | 320 | # TeamCity is a build add-in 321 | _TeamCity* 322 | 323 | # DotCover is a Code Coverage Tool 324 | *.dotCover 325 | 326 | # AxoCover is a Code Coverage Tool 327 | .axoCover/* 328 | !.axoCover/settings.json 329 | 330 | # Visual Studio code coverage results 331 | *.coverage 332 | *.coveragexml 333 | 334 | # NCrunch 335 | _NCrunch_* 336 | .*crunch*.local.xml 337 | nCrunchTemp_* 338 | 339 | # MightyMoose 340 | *.mm.* 341 | AutoTest.Net/ 342 | 343 | # Web workbench (sass) 344 | .sass-cache/ 345 | 346 | # Installshield output folder 347 | [Ee]xpress/ 348 | 349 | # DocProject is a documentation generator add-in 350 | DocProject/buildhelp/ 351 | DocProject/Help/*.HxT 352 | DocProject/Help/*.HxC 353 | DocProject/Help/*.hhc 354 | DocProject/Help/*.hhk 355 | DocProject/Help/*.hhp 356 | DocProject/Help/Html2 357 | DocProject/Help/html 358 | 359 | # Click-Once directory 360 | publish/ 361 | 362 | # Publish Web Output 363 | *.[Pp]ublish.xml 364 | *.azurePubxml 365 | # Note: Comment the next line if you want to checkin your web deploy settings, 366 | # but database connection strings (with potential passwords) will be unencrypted 367 | *.pubxml 368 | *.publishproj 369 | 370 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 371 | # checkin your Azure Web App publish settings, but sensitive information contained 372 | # in these scripts will be unencrypted 373 | PublishScripts/ 374 | 375 | # NuGet Packages 376 | *.nupkg 377 | # NuGet Symbol Packages 378 | *.snupkg 379 | # The packages folder can be ignored because of Package Restore 380 | **/[Pp]ackages/* 381 | # except build/, which is used as an MSBuild target. 382 | !**/[Pp]ackages/build/ 383 | # Uncomment if necessary however generally it will be regenerated when needed 384 | #!**/[Pp]ackages/repositories.config 385 | # NuGet v3's project.json files produces more ignorable files 386 | *.nuget.props 387 | *.nuget.targets 388 | 389 | # Microsoft Azure Build Output 390 | csx/ 391 | *.build.csdef 392 | 393 | # Microsoft Azure Emulator 394 | ecf/ 395 | rcf/ 396 | 397 | # Windows Store app package directories and files 398 | AppPackages/ 399 | BundleArtifacts/ 400 | Package.StoreAssociation.xml 401 | _pkginfo.txt 402 | *.appx 403 | *.appxbundle 404 | *.appxupload 405 | 406 | # Visual Studio cache files 407 | # files ending in .cache can be ignored 408 | *.[Cc]ache 409 | # but keep track of directories ending in .cache 410 | !?*.[Cc]ache/ 411 | 412 | # Others 413 | ClientBin/ 414 | ~$* 415 | *~ 416 | *.dbmdl 417 | *.dbproj.schemaview 418 | *.jfm 419 | *.pfx 420 | *.publishsettings 421 | orleans.codegen.cs 422 | 423 | # Including strong name files can present a security risk 424 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 425 | #*.snk 426 | 427 | # Since there are multiple workflows, uncomment next line to ignore bower_components 428 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 429 | #bower_components/ 430 | 431 | # RIA/Silverlight projects 432 | Generated_Code/ 433 | 434 | # Backup & report files from converting an old project file 435 | # to a newer Visual Studio version. Backup files are not needed, 436 | # because we have git ;-) 437 | _UpgradeReport_Files/ 438 | Backup*/ 439 | UpgradeLog*.XML 440 | UpgradeLog*.htm 441 | ServiceFabricBackup/ 442 | *.rptproj.bak 443 | 444 | # SQL Server files 445 | *.mdf 446 | *.ldf 447 | *.ndf 448 | 449 | # Business Intelligence projects 450 | *.rdl.data 451 | *.bim.layout 452 | *.bim_*.settings 453 | *.rptproj.rsuser 454 | *- [Bb]ackup.rdl 455 | *- [Bb]ackup ([0-9]).rdl 456 | *- [Bb]ackup ([0-9][0-9]).rdl 457 | 458 | # Microsoft Fakes 459 | FakesAssemblies/ 460 | 461 | # GhostDoc plugin setting file 462 | *.GhostDoc.xml 463 | 464 | # Node.js Tools for Visual Studio 465 | .ntvs_analysis.dat 466 | node_modules/ 467 | 468 | # Visual Studio 6 build log 469 | *.plg 470 | 471 | # Visual Studio 6 workspace options file 472 | *.opt 473 | 474 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 475 | *.vbw 476 | 477 | # Visual Studio LightSwitch build output 478 | **/*.HTMLClient/GeneratedArtifacts 479 | **/*.DesktopClient/GeneratedArtifacts 480 | **/*.DesktopClient/ModelManifest.xml 481 | **/*.Server/GeneratedArtifacts 482 | **/*.Server/ModelManifest.xml 483 | _Pvt_Extensions 484 | 485 | # Paket dependency manager 486 | .paket/paket.exe 487 | paket-files/ 488 | 489 | # FAKE - F# Make 490 | .fake/ 491 | 492 | # CodeRush personal settings 493 | .cr/personal 494 | 495 | # Python Tools for Visual Studio (PTVS) 496 | __pycache__/ 497 | *.pyc 498 | 499 | # Cake - Uncomment if you are using it 500 | # tools/** 501 | # !tools/packages.config 502 | 503 | # Tabs Studio 504 | *.tss 505 | 506 | # Telerik's JustMock configuration file 507 | *.jmconfig 508 | 509 | # BizTalk build output 510 | *.btp.cs 511 | *.btm.cs 512 | *.odx.cs 513 | *.xsd.cs 514 | 515 | # OpenCover UI analysis results 516 | OpenCover/ 517 | 518 | # Azure Stream Analytics local run output 519 | ASALocalRun/ 520 | 521 | # MSBuild Binary and Structured Log 522 | *.binlog 523 | 524 | # NVidia Nsight GPU debugger configuration file 525 | *.nvuser 526 | 527 | # MFractors (Xamarin productivity tool) working folder 528 | .mfractor/ 529 | 530 | # Local History for Visual Studio 531 | .localhistory/ 532 | 533 | # BeatPulse healthcheck temp database 534 | healthchecksdb 535 | 536 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 537 | MigrationBackup/ 538 | 539 | # End of https://www.gitignore.io/api/c,c++,cmake,clion+all,visualstudio,visualstudiocode 540 | 541 | # vscode cmake build 542 | build 543 | build-* 544 | -------------------------------------------------------------------------------- /include/ulog/queue/mpsc_ring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "lite_notifier.h" 12 | #include "memory_logger.h" 13 | #include "power_of_two.h" 14 | 15 | // The basic principle of circular queue implementation: 16 | // A - B is the position of A relative to B 17 | 18 | // Diagram example: 19 | // 0--_____________0--_____________ 20 | // ^in ^new 21 | 22 | // Explain: 23 | // 0: The starting position of each range 24 | // -: Positions already occupied by other producers 25 | // _: Positions not occupied by producers 26 | 27 | // Experience in lock-free queue design: 28 | // 1. When using yield() and loop calls like the disruptor, when the number of competing threads exceeds the number of 29 | // CPUs , using yield() may not necessarily schedule to the target thread, and there will be a serious performance 30 | // degradation , even worse than the traditional locking method.The use of yield() should be minimized. 31 | // 2. Using cache lines to fill the frequently used write_index and read_index variables can indeed increase performance 32 | // by about 15 %. 33 | 34 | namespace ulog { 35 | namespace mpsc { 36 | 37 | inline unsigned align8(const unsigned size) { return (size + 7) & ~7; } 38 | 39 | class Producer; 40 | class Consumer; 41 | 42 | struct Header { 43 | static constexpr size_t kFlagMask = 1U << 31; 44 | static constexpr size_t kSizeMask = kFlagMask - 1; 45 | 46 | void set_size(const uint32_t size, const std::memory_order m) { data_size.store(size, m); } 47 | uint32_t size(const std::memory_order m) const { return data_size.load(m) & kSizeMask; } 48 | 49 | void mark_discarded(const std::memory_order m) { data_size.fetch_or(kFlagMask, m); } 50 | bool discarded(const std::memory_order m) const { return data_size.load(m) & kFlagMask; } 51 | bool valid(const std::memory_order m) const { return data_size.load(m) != 0; } 52 | 53 | std::atomic_uint32_t reverse_size; 54 | 55 | private: 56 | std::atomic_uint32_t data_size{0}; 57 | 58 | public: 59 | uint8_t data[0]; 60 | } __attribute__((aligned(8))); 61 | 62 | union HeaderPtr { 63 | explicit HeaderPtr(void *ptr = nullptr) : ptr_(static_cast(ptr)) {} 64 | 65 | HeaderPtr &operator=(uint8_t *ptr) { 66 | ptr_ = ptr; 67 | return *this; 68 | } 69 | 70 | explicit operator bool() const noexcept { return ptr_ != nullptr; } 71 | auto operator->() const -> Header * { return header; } 72 | auto get() const -> uint8_t * { return this->ptr_; } 73 | HeaderPtr next() const { return HeaderPtr(ptr_ + (sizeof(Header) + align8(header->reverse_size))); } 74 | 75 | private: 76 | Header *header; 77 | __attribute__((unused)) uint8_t *ptr_; 78 | }; 79 | 80 | class PacketGroup { 81 | public: 82 | explicit PacketGroup(const HeaderPtr packet_head = HeaderPtr{nullptr}, const size_t count = 0, 83 | const size_t total_size = 0) 84 | : packet_count_(count), packet_head_(packet_head), group_ptr_(packet_head.get()), group_size_(total_size) {} 85 | 86 | explicit operator bool() const noexcept { return remain() > 0; } 87 | size_t remain() const { return packet_count_; } 88 | 89 | queue::Packet<> next() { 90 | if (!remain()) return queue::Packet<>{}; 91 | 92 | const queue::Packet<> packet{packet_head_->size(std::memory_order_relaxed), packet_head_->data}; 93 | packet_head_ = packet_head_.next(); 94 | packet_count_--; 95 | return packet; 96 | } 97 | 98 | uint8_t *raw_ptr() const { return static_cast(group_ptr_); } 99 | size_t raw_size() const { return group_size_; } 100 | 101 | private: 102 | size_t packet_count_; 103 | HeaderPtr packet_head_; 104 | void *group_ptr_; 105 | size_t group_size_; 106 | }; 107 | 108 | class DataPacket { 109 | friend class Consumer; 110 | 111 | public: 112 | explicit DataPacket(const PacketGroup &group0 = PacketGroup{}, const PacketGroup &group1 = PacketGroup{}) 113 | : group0_(group0), group1_(group1) {} 114 | 115 | explicit operator bool() const noexcept { return remain() > 0; } 116 | size_t remain() const { return group0_.remain() + group1_.remain(); } 117 | 118 | queue::Packet<> next() { 119 | if (group0_.remain()) return group0_.next(); 120 | if (group1_.remain()) return group1_.next(); 121 | return queue::Packet<>{0, nullptr}; 122 | } 123 | 124 | private: 125 | PacketGroup group0_; 126 | PacketGroup group1_; 127 | }; 128 | 129 | class Mq : public std::enable_shared_from_this { 130 | friend class Producer; 131 | friend class Consumer; 132 | 133 | struct Private { 134 | explicit Private() = default; 135 | }; 136 | 137 | public: 138 | explicit Mq(size_t num_elements, Private) : cons_head_(0), prod_head_(0), prod_last_(0) { 139 | if (num_elements < 2) num_elements = 2; 140 | 141 | // round up to the next power of 2, since our 'let the indices wrap' 142 | // technique works only in this case. 143 | num_elements = queue::RoundUpPowOfTwo(num_elements); 144 | 145 | mask_ = num_elements - 1; 146 | data_ = new unsigned char[num_elements]; 147 | std::memset(data_, 0, num_elements); 148 | } 149 | ~Mq() { delete[] data_; } 150 | 151 | // Everyone else has to use this factory function 152 | static std::shared_ptr Create(size_t num_elements) { return std::make_shared(num_elements, Private()); } 153 | using Producer = mpsc::Producer; 154 | using Consumer = mpsc::Consumer; 155 | 156 | /** 157 | * Ensure that all currently written data has been read and processed 158 | * @param wait_time The maximum waiting time 159 | */ 160 | void Flush(const std::chrono::milliseconds wait_time = std::chrono::milliseconds(1000)) { 161 | prod_notifier_.notify_all(); 162 | const auto prod_head = prod_head_.load(); 163 | cons_notifier_.wait_for(wait_time, [&]() { return queue::IsPassed(prod_head, cons_head_.load()); }); 164 | } 165 | 166 | /** 167 | * Notify all waiting threads, so that they can check the status of the queue 168 | */ 169 | void Notify() { 170 | prod_notifier_.notify_all(); 171 | cons_notifier_.notify_all(); 172 | } 173 | 174 | private: 175 | size_t size() const { return mask_ + 1; } 176 | 177 | size_t mask() const { return mask_; } 178 | 179 | size_t next_buffer(const size_t index) const { return (index & ~mask()) + size(); } 180 | 181 | uint8_t *data_; // the buffer holding the data 182 | size_t mask_; 183 | 184 | [[maybe_unused]] uint8_t pad0[64]{}; // Using cache line filling technology can improve performance by 15% 185 | std::atomic cons_head_; 186 | 187 | [[maybe_unused]] uint8_t pad1[64]{}; 188 | std::atomic prod_head_; 189 | std::atomic prod_last_; 190 | 191 | [[maybe_unused]] uint8_t pad2[64]{}; 192 | LiteNotifier prod_notifier_; 193 | LiteNotifier cons_notifier_; 194 | }; 195 | 196 | class Producer { 197 | public: 198 | explicit Producer(const std::shared_ptr &ring) : ring_(ring) {} 199 | ~Producer() = default; 200 | 201 | /** 202 | * Reserve space of size, automatically retry until timeout 203 | * @param size size of space to reserve 204 | * @param timeout The maximum waiting time if there is insufficient space in the queue 205 | * @return data pointer if successful, otherwise nullptr 206 | */ 207 | uint8_t *ReserveOrWaitFor(const size_t size, const std::chrono::milliseconds timeout) { 208 | uint8_t *ptr; 209 | ring_->cons_notifier_.wait_for(timeout, [&] { return (ptr = Reserve(size)) != nullptr; }); 210 | return ptr; 211 | } 212 | 213 | uint8_t *ReserveOrWait(const size_t size) { 214 | uint8_t *ptr; 215 | ring_->cons_notifier_.wait([&] { return (ptr = Reserve(size)) != nullptr; }); 216 | return ptr; 217 | } 218 | 219 | /** 220 | * Try to reserve space of size 221 | * @param size size of space to reserve 222 | * @return data pointer if successful, otherwise nullptr 223 | */ 224 | uint8_t *Reserve(const size_t size) { 225 | const auto packet_size = sizeof(Header) + align8(size); 226 | HeaderPtr pending_packet_; 227 | 228 | auto packet_head_ = ring_->prod_head_.load(std::memory_order_relaxed); 229 | do { 230 | const auto cons_head = ring_->cons_head_.load(std::memory_order_acquire); 231 | packet_next_ = packet_head_ + packet_size; 232 | 233 | // Not enough space 234 | if (packet_next_ - cons_head > ring_->size()) { 235 | return nullptr; 236 | } 237 | 238 | const auto relate_pos = packet_next_ & ring_->mask(); 239 | // Both new position and write_index are in the same range 240 | // 0--_____________________________0--_____________________________ 241 | // ^in ^new 242 | // OR 243 | // 0--_____________________________0--_____________________________ 244 | // ^in ^new 245 | if (relate_pos >= packet_size || relate_pos == 0) { 246 | // After being fully read out, it will be marked as all 0 by the reader 247 | if (!ring_->prod_head_.compare_exchange_weak(packet_head_, packet_next_, std::memory_order_relaxed)) { 248 | continue; 249 | } 250 | 251 | // Whenever we wrap around, we update the last variable to ensure logical 252 | // consistency. 253 | if (relate_pos == 0) { 254 | ring_->prod_last_.store(packet_next_, std::memory_order_relaxed); 255 | } 256 | pending_packet_ = &ring_->data_[packet_head_ & ring_->mask()]; 257 | break; 258 | } 259 | 260 | if ((cons_head & ring_->mask()) >= packet_size) { 261 | // new_pos is in the next block 262 | // 0__________------------------___0__________------------------___ 263 | // ^out ^in 264 | packet_next_ = ring_->next_buffer(packet_head_) + packet_size; 265 | if (!ring_->prod_head_.compare_exchange_weak(packet_head_, packet_next_, std::memory_order_relaxed)) { 266 | continue; 267 | } 268 | 269 | ring_->prod_last_.store(packet_head_, std::memory_order_relaxed); 270 | pending_packet_ = &ring_->data_[0]; 271 | break; 272 | } 273 | // Neither the end of the current range nor the head of the next range is enough 274 | return nullptr; 275 | } while (true); 276 | 277 | pending_packet_->reverse_size.store(size, std::memory_order_relaxed); 278 | return &pending_packet_->data[0]; 279 | } 280 | 281 | /** 282 | * Commits the data to the buffer, so that it can be read out. 283 | */ 284 | void Commit(const uint8_t *data, const size_t real_size) { 285 | const HeaderPtr pending_packet_(intrusive::owner_of(data, &Header::data)); 286 | assert(real_size <= pending_packet_->reverse_size); 287 | 288 | if (real_size) { 289 | pending_packet_->set_size(real_size, std::memory_order_release); 290 | } else { 291 | pending_packet_->mark_discarded(std::memory_order_relaxed); 292 | } 293 | 294 | // prod_tail cannot be modified here: 295 | // 1. If you wait for prod_tail to update to the current position, there will be a lot of performance loss 296 | // 2. If you don't wait for prod_tail, just do a check and mark? It doesn't work either. Because it is a wait-free 297 | // process, in a highly competitive scenario, the queue may have been updated once, and the data is unreliable. 298 | ring_->prod_notifier_.notify_all(); 299 | } 300 | 301 | /** 302 | * Ensure that all currently written data has been read and processed 303 | * @param wait_time The maximum waiting time 304 | */ 305 | void Flush(const std::chrono::milliseconds wait_time = std::chrono::milliseconds(1000)) const { 306 | ring_->cons_notifier_.wait_for(wait_time, 307 | [&]() { return queue::IsPassed(packet_next_, ring_->cons_head_.load()); }); 308 | } 309 | 310 | private: 311 | std::shared_ptr ring_; 312 | decltype(ring_->prod_head_.load()) packet_next_{}; 313 | }; 314 | 315 | class Consumer { 316 | public: 317 | explicit Consumer(const std::shared_ptr &ring) : ring_(ring) {} 318 | 319 | ~Consumer() = default; 320 | 321 | /** 322 | * Gets a pointer to the contiguous block in the buffer, and returns the size of that block. automatically retry until 323 | * timeout 324 | * @param timeout The maximum waiting time 325 | * @param other_condition Other wake-up conditions 326 | * @return pointer to the contiguous block 327 | */ 328 | template 329 | DataPacket ReadOrWait(const std::chrono::milliseconds timeout, Condition other_condition) { 330 | DataPacket ptr; 331 | ring_->prod_notifier_.wait_for(timeout, [&] { return (ptr = Read()).remain() > 0 || other_condition(); }); 332 | return ptr; 333 | } 334 | DataPacket ReadOrWait(const std::chrono::milliseconds timeout) { 335 | return ReadOrWait(timeout, [] { return false; }); 336 | } 337 | template 338 | DataPacket ReadOrWait(Condition other_condition) { 339 | DataPacket ptr; 340 | ring_->prod_notifier_.wait([&] { return (ptr = Read()).remain() > 0 || other_condition(); }); 341 | return ptr; 342 | } 343 | 344 | /** 345 | * Gets a pointer to the contiguous block in the buffer, and returns the size of that block. 346 | * @return pointer to the contiguous block 347 | */ 348 | DataPacket Read() { 349 | cons_head = ring_->cons_head_.load(std::memory_order_relaxed); 350 | const auto prod_head = ring_->prod_head_.load(std::memory_order_acquire); 351 | 352 | // no data 353 | if (cons_head == prod_head) { 354 | return DataPacket{}; 355 | } 356 | 357 | const auto cur_prod_head = prod_head & ring_->mask(); 358 | const auto cur_cons_head = cons_head & ring_->mask(); 359 | 360 | // read and write are still in the same block 361 | // 0__________------------------___0__________------------------___ 362 | // ^cons_head ^prod_head 363 | if (cur_cons_head < cur_prod_head) { 364 | const auto group = CheckRealSize(&ring_->data_[cur_cons_head], cur_prod_head - cur_cons_head); 365 | if (!group) return DataPacket{}; 366 | 367 | cons_head_next = cons_head + group.raw_size(); 368 | 369 | return DataPacket{group}; 370 | } 371 | 372 | // Due to the update order, prod_head will be updated first and prod_last will be updated later. 373 | // prod_head is already in the next set of loops, so you need to make sure prod_last is updated to the position 374 | // before prod_head. 375 | auto prod_last = ring_->prod_last_.load(std::memory_order_relaxed); 376 | while (prod_last - cons_head > ring_->size()) { 377 | std::this_thread::yield(); 378 | prod_last = ring_->prod_last_.load(std::memory_order_relaxed); 379 | } 380 | 381 | // read and write are in different blocks, read the current remaining data 382 | // 0---_______________skip-skip-ski0---_______________skip-skip-ski 383 | // ^prod_head ^cons_head ^prod_head 384 | // ^prod_last 385 | if (cons_head == prod_last) { 386 | // The current block has been read, "write" has reached the next block 387 | // Move the read index, which can make room for the writer 388 | const auto group = CheckRealSize(&ring_->data_[0], cur_prod_head); 389 | if (!group) return DataPacket{}; 390 | 391 | if (cur_cons_head == 0) { 392 | cons_head_next = cons_head + group.raw_size(); 393 | } else { 394 | cons_head_next = ring_->next_buffer(cons_head) + group.raw_size(); 395 | } 396 | 397 | return DataPacket{group}; 398 | } 399 | 400 | // 0---___------------skip-skip-ski0---___------------skip-skip-ski 401 | // ^cons_head ^prod_last ^prod_head 402 | const size_t expected_size = prod_last - cons_head; 403 | const auto group0 = CheckRealSize(&ring_->data_[cur_cons_head], expected_size); 404 | if (!group0) return DataPacket{}; 405 | 406 | // The current packet group has been read, continue reading the next packet group 407 | if (expected_size == group0.raw_size()) { 408 | // Read the next group only if the current group has been committed 409 | const auto group1 = CheckRealSize(&ring_->data_[0], cur_prod_head); 410 | cons_head_next = ring_->next_buffer(cons_head) + group1.raw_size(); 411 | 412 | return DataPacket{group0, group1}; 413 | } 414 | 415 | cons_head_next = cons_head + group0.raw_size(); 416 | 417 | return DataPacket{group0}; 418 | } 419 | 420 | /** 421 | * Releases data from the buffer, so that more data can be written in. 422 | */ 423 | void Release(const DataPacket &data) const { 424 | for (auto &group : {data.group0_, data.group1_}) { 425 | if (!group.raw_size()) continue; 426 | 427 | std::memset(group.raw_ptr(), 0, group.raw_size()); 428 | } 429 | 430 | ring_->cons_head_.store(cons_head_next, std::memory_order_release); 431 | ring_->cons_notifier_.notify_all(); 432 | } 433 | 434 | private: 435 | static PacketGroup CheckRealSize(uint8_t *data, const size_t size, const size_t max_packet_count = 1024) { 436 | HeaderPtr pk; 437 | size_t count = 0; 438 | for (pk = data; pk.get() < data + size;) { 439 | if (pk->valid(std::memory_order_relaxed) == 0) break; 440 | 441 | count++; 442 | pk = pk.next(); 443 | 444 | if (count >= max_packet_count) break; 445 | } 446 | 447 | if (count == 0) return PacketGroup{}; 448 | 449 | return PacketGroup(HeaderPtr(data), count, pk.get() - data); 450 | } 451 | size_t cons_head_next = 0; 452 | size_t cons_head = 0; 453 | std::shared_ptr ring_; 454 | }; 455 | } // namespace mpsc 456 | } // namespace ulog 457 | -------------------------------------------------------------------------------- /include/ulog/ulog_private.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #define ULOG_STR_COLOR(color) "\x1b[" color "m" 5 | 6 | #define ULOG_STR_RESET ULOG_STR_COLOR("0") 7 | #define ULOG_STR_GRAY ULOG_STR_COLOR("38;5;8") 8 | 9 | #define ULOG_STR_BLACK ULOG_STR_COLOR("30") 10 | #define ULOG_STR_RED ULOG_STR_COLOR("31") 11 | #define ULOG_STR_GREEN ULOG_STR_COLOR("32") 12 | #define ULOG_STR_YELLOW ULOG_STR_COLOR("33") 13 | #define ULOG_STR_BLUE ULOG_STR_COLOR("34") 14 | #define ULOG_STR_PURPLE ULOG_STR_COLOR("35") 15 | #define ULOG_STR_SKYBLUE ULOG_STR_COLOR("36") 16 | #define ULOG_STR_WHITE ULOG_STR_COLOR("37") 17 | 18 | // Precompiler define to get only filename; 19 | #if !defined(__FILENAME__) 20 | static inline const char *ulog_get_filename(const char *filepath) { 21 | return strrchr(filepath, '/') ? strrchr(filepath, '/') + 1 22 | : strrchr(filepath, '\\') ? strrchr(filepath, '\\') + 1 23 | : filepath; 24 | } 25 | #define __FILENAME__ ulog_get_filename(__FILE__) 26 | #endif 27 | 28 | #if defined(__GNUC__) || defined(__clang__) 29 | #define ULOG_ATTRIBUTE_CHECK_FORMAT(m, n) __attribute__((format(printf, m, n))) 30 | #else 31 | #define ULOG_ATTRIBUTE_CHECK_FORMAT(m, n) 32 | #endif 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | #ifndef ULOG_OUTBUF_LEN 39 | #define ULOG_OUTBUF_LEN 1024 /* Size of buffer used for log printout */ 40 | #endif 41 | 42 | #if ULOG_OUTBUF_LEN < 128 43 | #pragma message("ULOG_OUTBUF_LEN is recommended to be greater than 64") 44 | #endif 45 | 46 | struct ulog_buffer_s { 47 | char log_out_buf_[ULOG_OUTBUF_LEN]; 48 | char *cur_buf_ptr_; 49 | }; 50 | 51 | static inline void logger_buffer_init(struct ulog_buffer_s *log_buf) { 52 | log_buf->cur_buf_ptr_ = log_buf->log_out_buf_; 53 | } 54 | 55 | static inline int logger_vsnprintf(struct ulog_buffer_s *buffer, 56 | const char *fmt, va_list ap) { 57 | char *buffer_end = buffer->log_out_buf_ + sizeof(buffer->log_out_buf_); 58 | ssize_t buffer_length = buffer_end - buffer->cur_buf_ptr_; 59 | 60 | int expected_length = 61 | vsnprintf((buffer)->cur_buf_ptr_, buffer_length, fmt, ap); 62 | 63 | if (expected_length < buffer_length) { 64 | buffer->cur_buf_ptr_ += expected_length; 65 | } else { 66 | // The buffer is filled, pointing to terminating null byte ('\0') 67 | buffer->cur_buf_ptr_ = buffer_end - 1; 68 | } 69 | return expected_length; 70 | } 71 | 72 | static inline int logger_snprintf(struct ulog_buffer_s *buffer, const char *fmt, 73 | ...) { 74 | va_list ap; 75 | va_start(ap, fmt); 76 | int expected_length = logger_vsnprintf(buffer, fmt, ap); 77 | va_end(ap); 78 | return expected_length; 79 | } 80 | 81 | /** 82 | * Display contents in hexadecimal and ascii. 83 | * Same format as "hexdump -C filename" 84 | * @param data The starting address of the data to be displayed 85 | * @param length Display length starting from "data" 86 | * @param width How many bytes of data are displayed in each line 87 | * @param base_address Base address, the displayed address starts from this 88 | * value 89 | * @param tail_addr_out Tail address output, whether to output the last 90 | * address after output 91 | * @return Last output address 92 | */ 93 | uintptr_t logger_hex_dump(struct ulog_s *logger, const void *data, 94 | size_t length, size_t width, uintptr_t base_address, 95 | bool tail_addr_out); 96 | 97 | /** 98 | * Raw data output, similar to printf 99 | * @param level Output level 100 | * @param fmt Format of the format string 101 | * @param ... Parameters in the format 102 | */ 103 | ULOG_ATTRIBUTE_CHECK_FORMAT(3, 4) 104 | void logger_raw(struct ulog_s *logger, enum ulog_level_e level, const char *fmt, 105 | ...); 106 | 107 | /** 108 | * Print log 109 | * Internal functions should not be called directly from outside, macros 110 | * such as LOG_DEBUG / LOG_INFO should be used 111 | * @param level Output level 112 | * @param file File name 113 | * @param func Function name 114 | * @param line Line number of the file 115 | * @param newline Whether to output a new line at the end 116 | * @param flush Flush the log buffer 117 | * @param fmt Format string, consistent with printf series functions 118 | * @param ... 119 | */ 120 | ULOG_ATTRIBUTE_CHECK_FORMAT(8, 9) 121 | void logger_log_with_header(struct ulog_s *logger, enum ulog_level_e level, 122 | const char *file, const char *func, uint32_t line, 123 | bool newline, bool flush, const char *fmt, ...); 124 | 125 | /** 126 | * Get time of clock_id::CLOCK_MONOTONIC 127 | * @return Returns the system startup time, in microseconds. 128 | */ 129 | uint64_t logger_monotonic_time_us(); 130 | 131 | /** 132 | * Get time of clock_id::CLOCK_REALTIME 133 | * @return Returns the real time, in microseconds. 134 | */ 135 | uint64_t logger_real_time_us(); 136 | 137 | #ifdef __cplusplus 138 | } 139 | #endif 140 | 141 | #define ULOG_OUT_LOG(logger, level, ...) \ 142 | ({ \ 143 | logger_log_with_header(logger, level, __FILENAME__, __FUNCTION__, \ 144 | __LINE__, true, true, ##__VA_ARGS__); \ 145 | }) 146 | 147 | #define ULOG_OUT_RAW(logger, level, fmt, ...) \ 148 | ({ logger_raw(logger, level, fmt, ##__VA_ARGS__); }) 149 | 150 | #define ULOG_GEN_TOKEN_FORMAT(color, format) \ 151 | color ? ULOG_STR_BLUE "%s " ULOG_STR_RED "=> " ULOG_STR_GREEN format \ 152 | : "%s => " format 153 | 154 | #define ULOG_GEN_STRING_TOKEN_FORMAT(color) \ 155 | color ? ULOG_STR_BLUE "%s " ULOG_STR_RED "=> " ULOG_STR_RED \ 156 | "\"" ULOG_STR_GREEN "%s" ULOG_STR_RED "\"" \ 157 | : "%s => \"%s\"" 158 | 159 | #ifdef __cplusplus 160 | namespace ulog { 161 | namespace _token { 162 | 163 | // void * 164 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 165 | const char *name, const void *value) { 166 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%p"), 167 | name ? name : "unnamed", value); 168 | } 169 | 170 | // const char * 171 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 172 | const char *name, const char *value) { 173 | logger_snprintf(log_buffer, ULOG_GEN_STRING_TOKEN_FORMAT(color), 174 | name ? name : "unnamed", value ? value : ""); 175 | } 176 | 177 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 178 | const char *name, const unsigned char *value) { 179 | print(log_buffer, color, name, reinterpret_cast(value)); 180 | } 181 | 182 | // double/float 183 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 184 | const char *name, double value) { 185 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%f"), 186 | name ? name : "unnamed", value); 187 | } 188 | 189 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 190 | const char *name, float value) { 191 | print(log_buffer, color, name, static_cast(value)); 192 | } 193 | 194 | // signed integer 195 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 196 | const char *name, long long value) { 197 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%" PRId64), 198 | name ? name : "unnamed", (int64_t)value); 199 | } 200 | 201 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 202 | const char *name, long value) { 203 | print(log_buffer, color, name, static_cast(value)); 204 | } 205 | 206 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 207 | const char *name, int value) { 208 | print(log_buffer, color, name, static_cast(value)); 209 | } 210 | 211 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 212 | const char *name, short value) { 213 | print(log_buffer, color, name, static_cast(value)); 214 | } 215 | 216 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 217 | const char *name, char value) { 218 | print(log_buffer, color, name, static_cast(value)); 219 | } 220 | 221 | // unsigned integer 222 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 223 | const char *name, unsigned long long value) { 224 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%" PRIu64), 225 | name ? name : "unnamed", (uint64_t)value); 226 | } 227 | 228 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 229 | const char *name, unsigned long value) { 230 | print(log_buffer, color, name, static_cast(value)); 231 | } 232 | 233 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 234 | const char *name, unsigned int value) { 235 | print(log_buffer, color, name, static_cast(value)); 236 | } 237 | 238 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 239 | const char *name, unsigned short value) { 240 | print(log_buffer, color, name, static_cast(value)); 241 | } 242 | 243 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 244 | const char *name, unsigned char value) { 245 | print(log_buffer, color, name, static_cast(value)); 246 | } 247 | 248 | inline void print(struct ulog_buffer_s *log_buffer, bool color, 249 | const char *name, bool value) { 250 | print(log_buffer, color, name, static_cast(value)); 251 | } 252 | 253 | } // namespace _token 254 | } // namespace ulog 255 | 256 | #define ULOG_OUT_TOKEN_IMPLEMENT(log_buffer, color, token) \ 257 | ulog::_token::print(log_buffer, color, #token, (token)) 258 | 259 | #else 260 | // C version: implemented through GCC extensionn 261 | #if defined(__GNUC__) || defined(__clang__) 262 | #define ULOG_IS_SAME_TYPE(var, type) \ 263 | __builtin_types_compatible_p(typeof(var), typeof(type)) 264 | #else 265 | #pragma message( \ 266 | "LOG_TOKEN is not available, plese use c++11 or clang or gcc compiler.") 267 | #define ULOG_IS_SAME_TYPE(var, type) false 268 | #endif 269 | 270 | #define ULOG_OUT_TOKEN_IMPLEMENT(log_buffer, color, token) \ 271 | ({ \ 272 | if (ULOG_IS_SAME_TYPE(token, float) || ULOG_IS_SAME_TYPE(token, double)) { \ 273 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%f"), #token, \ 274 | token); \ 275 | } else if (ULOG_IS_SAME_TYPE(token, bool)) { \ 276 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%d"), #token, \ 277 | ((int)(intptr_t)(token)) ? 1 : 0); \ 278 | /* Signed integer */ \ 279 | } else if (ULOG_IS_SAME_TYPE(token, char) || \ 280 | ULOG_IS_SAME_TYPE(token, signed char) || \ 281 | ULOG_IS_SAME_TYPE(token, short) || \ 282 | ULOG_IS_SAME_TYPE(token, int) || \ 283 | ULOG_IS_SAME_TYPE(token, long) || \ 284 | ULOG_IS_SAME_TYPE(token, long long)) { \ 285 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%" PRId64), \ 286 | #token, (int64_t)(token)); \ 287 | /* Unsigned integer */ \ 288 | } else if (ULOG_IS_SAME_TYPE(token, unsigned char) || \ 289 | ULOG_IS_SAME_TYPE(token, unsigned short) || \ 290 | ULOG_IS_SAME_TYPE(token, unsigned int) || \ 291 | ULOG_IS_SAME_TYPE(token, unsigned long) || \ 292 | ULOG_IS_SAME_TYPE(token, unsigned long long)) { \ 293 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%" PRIu64), \ 294 | #token, (uint64_t)(token)); \ 295 | } else if (ULOG_IS_SAME_TYPE(token, char *) || \ 296 | ULOG_IS_SAME_TYPE(token, const char *) || \ 297 | ULOG_IS_SAME_TYPE(token, signed char *) || \ 298 | ULOG_IS_SAME_TYPE(token, const signed char *) || \ 299 | ULOG_IS_SAME_TYPE(token, unsigned char *) || \ 300 | ULOG_IS_SAME_TYPE(token, const unsigned char *) || \ 301 | ULOG_IS_SAME_TYPE(token, char[]) || \ 302 | ULOG_IS_SAME_TYPE(token, const char[]) || \ 303 | ULOG_IS_SAME_TYPE(token, unsigned char[]) || \ 304 | ULOG_IS_SAME_TYPE(token, const unsigned char[])) { \ 305 | /* Arrays can be changed to pointer types by (var) +1, but this is not \ 306 | * compatible with (void *) types */ \ 307 | const char *_ulog_value = (const char *)(uintptr_t)(token); \ 308 | logger_snprintf(log_buffer, ULOG_GEN_STRING_TOKEN_FORMAT(color), #token, \ 309 | _ulog_value ? _ulog_value : "NULL"); \ 310 | } else if (ULOG_IS_SAME_TYPE(token, void *) || \ 311 | ULOG_IS_SAME_TYPE(token, short *) || \ 312 | ULOG_IS_SAME_TYPE(token, unsigned short *) || \ 313 | ULOG_IS_SAME_TYPE(token, int *) || \ 314 | ULOG_IS_SAME_TYPE(token, unsigned int *) || \ 315 | ULOG_IS_SAME_TYPE(token, long *) || \ 316 | ULOG_IS_SAME_TYPE(token, unsigned long *) || \ 317 | ULOG_IS_SAME_TYPE(token, long long *) || \ 318 | ULOG_IS_SAME_TYPE(token, unsigned long long *) || \ 319 | ULOG_IS_SAME_TYPE(token, float *) || \ 320 | ULOG_IS_SAME_TYPE(token, double *)) { \ 321 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "%p"), #token, \ 322 | (void *)(uintptr_t)(token)); \ 323 | } else { \ 324 | logger_snprintf(log_buffer, ULOG_GEN_TOKEN_FORMAT(color, "(none)"), \ 325 | #token); \ 326 | } \ 327 | }) 328 | #endif 329 | 330 | #define ULOG_OUT_TOKEN(logger, token) \ 331 | ({ \ 332 | struct ulog_buffer_s log_buffer; \ 333 | logger_buffer_init(&log_buffer); \ 334 | ULOG_OUT_TOKEN_IMPLEMENT( \ 335 | &log_buffer, logger_check_format(logger, ULOG_F_COLOR), token); \ 336 | LOGGER_LOCAL_DEBUG(logger, "%s", log_buffer.log_out_buf_); \ 337 | }) 338 | 339 | #define ULOG_EXPAND(...) __VA_ARGS__ 340 | #define ULOG_EAT_COMMA(...) , ##__VA_ARGS__ 341 | 342 | #define ULOG_ARG_COUNT_PRIVATE_MSVC( \ 343 | _none, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \ 344 | _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ 345 | _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, \ 346 | _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \ 347 | _60, _61, _62, _63, _64, ...) \ 348 | _64 349 | 350 | #define ULOG_ARG_COUNT_PRIVATE(...) \ 351 | ULOG_EXPAND(ULOG_ARG_COUNT_PRIVATE_MSVC(__VA_ARGS__)) 352 | 353 | /** 354 | * Get the number of parameters of the function-like macro 355 | */ 356 | #define ULOG_ARG_COUNT(...) \ 357 | ULOG_ARG_COUNT_PRIVATE( \ 358 | NULL ULOG_EAT_COMMA(__VA_ARGS__), 64, 63, 62, 61, 60, 59, 58, 57, 56, \ 359 | 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, \ 360 | 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ 361 | 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 362 | 363 | /** 364 | * String concatenation 365 | */ 366 | #define ULOG_MACRO_CONCAT_PRIVATE(l, r) l##r 367 | #define ULOG_MACRO_CONCAT(l, r) ULOG_MACRO_CONCAT_PRIVATE(l, r) 368 | #define ULOG_UNIQUE(name) ULOG_MACRO_CONCAT(name, __LINE__) 369 | 370 | #define ULOG_OUT_MULTI_TOKEN(logger, ...) \ 371 | ({ \ 372 | struct ulog_buffer_s log_buffer; \ 373 | logger_buffer_init(&log_buffer); \ 374 | ULOG_EXPAND(ULOG_MACRO_CONCAT(ULOG_TOKEN_AUX_, \ 375 | ULOG_ARG_COUNT(__VA_ARGS__))( \ 376 | &log_buffer, logger_check_format(logger, ULOG_F_COLOR), __VA_ARGS__)) \ 377 | LOGGER_LOCAL_DEBUG(logger, "%s", log_buffer.log_out_buf_); \ 378 | }) 379 | 380 | #define ULOG_OUT_TOKEN_WRAPPER(log_buffer, color, token, left) \ 381 | ({ \ 382 | ULOG_OUT_TOKEN_IMPLEMENT(log_buffer, color, token); \ 383 | if (left) \ 384 | logger_snprintf(log_buffer, (color) ? ULOG_STR_RED ", " : ", "); \ 385 | else if (color) \ 386 | logger_snprintf(log_buffer, ULOG_STR_RESET); \ 387 | }) 388 | 389 | #define ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, ...) \ 390 | ULOG_OUT_TOKEN_WRAPPER(log_buffer, color, _1, ULOG_ARG_COUNT(__VA_ARGS__)); \ 391 | ULOG_MACRO_CONCAT(ULOG_TOKEN_AUX_, ULOG_ARG_COUNT(__VA_ARGS__)) 392 | 393 | #define ULOG_TOKEN_AUX_0(log_buffer, color, ...) 394 | #define ULOG_TOKEN_AUX_1(log_buffer, color, _1, ...) \ 395 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 396 | log_buffer, color, __VA_ARGS__)) 397 | #define ULOG_TOKEN_AUX_2(log_buffer, color, _1, ...) \ 398 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 399 | log_buffer, color, __VA_ARGS__)) 400 | #define ULOG_TOKEN_AUX_3(log_buffer, color, _1, ...) \ 401 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 402 | log_buffer, color, __VA_ARGS__)) 403 | #define ULOG_TOKEN_AUX_4(log_buffer, color, _1, ...) \ 404 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 405 | log_buffer, color, __VA_ARGS__)) 406 | #define ULOG_TOKEN_AUX_5(log_buffer, color, _1, ...) \ 407 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 408 | log_buffer, color, __VA_ARGS__)) 409 | #define ULOG_TOKEN_AUX_6(log_buffer, color, _1, ...) \ 410 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 411 | log_buffer, color, __VA_ARGS__)) 412 | #define ULOG_TOKEN_AUX_7(log_buffer, color, _1, ...) \ 413 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 414 | log_buffer, color, __VA_ARGS__)) 415 | #define ULOG_TOKEN_AUX_8(log_buffer, color, _1, ...) \ 416 | ULOG_EXPAND(ULOG_LOG_TOKEN_AUX(log_buffer, color, _1, __VA_ARGS__)( \ 417 | log_buffer, color, __VA_ARGS__)) 418 | 419 | #define ULOG_FORMAT_FOR_TIME_CODE(logger, string_format, time_format, unit) \ 420 | logger_check_format(logger, ULOG_F_COLOR) ? ULOG_STR_GREEN \ 421 | "time " ULOG_STR_RED "{ " ULOG_STR_BLUE string_format ULOG_STR_RED \ 422 | " } => " ULOG_STR_GREEN time_format ULOG_STR_RED unit \ 423 | : "time { " string_format \ 424 | " } => " time_format unit 425 | 426 | #define ULOG_TIME_CODE(logger, ...) \ 427 | ({ \ 428 | uint64_t ULOG_UNIQUE(start) = logger_monotonic_time_us(); \ 429 | __VA_ARGS__; \ 430 | uint64_t ULOG_UNIQUE(diff) = \ 431 | logger_monotonic_time_us() - ULOG_UNIQUE(start); \ 432 | const char *ULOG_UNIQUE(code_str) = #__VA_ARGS__; \ 433 | LOGGER_DEBUG( \ 434 | ULOG_FORMAT_FOR_TIME_CODE(logger, "%.64s%s", "%" PRIu64, "us"), \ 435 | ULOG_UNIQUE(code_str), \ 436 | strlen(ULOG_UNIQUE(code_str)) > 64 ? "..." : "", ULOG_UNIQUE(diff)); \ 437 | ULOG_UNIQUE(diff); \ 438 | }) 439 | 440 | #define ULOG_GEN_COLOR_FORMAT_FOR_HEX_DUMP(place1, place2, place3, place4) \ 441 | ULOG_STR_RED place1 ULOG_STR_GREEN place2 ULOG_STR_RED place3 ULOG_STR_BLUE \ 442 | place4 443 | 444 | #define ULOG_HEX_DUMP_FORMAT(logger) \ 445 | logger_check_format(logger, ULOG_F_COLOR) \ 446 | ? ULOG_STR_GREEN \ 447 | "hex_dump" ULOG_GEN_COLOR_FORMAT_FOR_HEX_DUMP("(", "data", ":", "%s") \ 448 | ULOG_GEN_COLOR_FORMAT_FOR_HEX_DUMP(", ", "length", ":", "%" PRIuMAX) \ 449 | ULOG_GEN_COLOR_FORMAT_FOR_HEX_DUMP( \ 450 | ", ", "width", ":", "%" PRIuMAX ULOG_STR_RED ") =>") \ 451 | : "hex_dump(data:%s, length:%" PRIuMAX ", width:%" PRIuMAX ")" 452 | 453 | #define ULOG_HEX_DUMP(logger, data, length, width) \ 454 | ({ \ 455 | LOGGER_LOCAL_DEBUG(logger, ULOG_HEX_DUMP_FORMAT(logger), #data, \ 456 | (uintmax_t)(length), (uintmax_t)(width)); \ 457 | logger_hex_dump(logger, data, length, width, (uintptr_t)(data), true); \ 458 | }) 459 | --------------------------------------------------------------------------------