qxLib
file_logger_stream_mapping.h
Go to the documentation of this file.
1 /**
2 
3  @file file_logger_stream_mapping.h
4  @author Khrapov
5  @date 14.01.2026
6  @copyright © Nick Khrapov, 2026. All right reserved.
7 
8 **/
9 #pragma once
10 
12 #include <qx/math/units/data.h>
13 #include <qx/windows.h>
14 
15 #if QX_CLANG || QX_APPLE_CLANG || QX_GNU
16  #include <fcntl.h>
17  #include <sys/mman.h>
18  #include <sys/stat.h>
19  #include <time.h>
20  #include <unistd.h>
21 #endif
22 
23 namespace qx
24 {
25 
26 /**
27 
28  @class file_logger_stream_mapping
29  @brief High-performance file logger based on memory-mapped I/O.
30  @details This logger implementation writes log data directly into a
31  memory-mapped file region (mmap / CreateFileMapping).
32  Instead of using traditional buffered I/O (fopen / WriteFile),
33  the file is mapped into the process address space and written
34  via simple memory copies.
35 
36  UTF-8 LE (char) or UTF-16 LE (wchar_t).
37 
38  While the file is open for writing, a sequence of NUL characters appears at the end of the file.
39  This is expected and follows the logic of how memory-mapped files work.
40  These characters will be truncated when the object is destroyed, usually when an application is closed.
41  @author Khrapov
42  @date 15.01.2026
43 
44 **/
46 {
48 
49 public:
50  /**
51  @brief file_logger_stream_mapping object constructor
52  @param streamConfig - file logger configuration
53  @param initialMapSize - initial mapping size (outer capacity). mapping grows when needed
54  **/
56  const config& streamConfig = config(),
57  unit<size_t, units::data> initialMapSize = { 1, units::data::mebibytes }) noexcept;
58 
60 
61  virtual ~file_logger_stream_mapping() noexcept override;
62 
63  // base_logger_stream
64  //
65  virtual void do_log(
66  const category& category,
67  verbosity eVerbosity,
68  std::thread::id threadId,
69  std::chrono::system_clock::time_point messageTime,
70  string_view svFile,
71  string_view svFunction,
72  int nLine,
73  string_view svMessage) override;
74  virtual void do_flush() override;
75 
76 private:
77  /**
78  @brief Remap file to a new capacity.
79 
80  @details This function performs a full remapping sequence:
81 
82  1) Unmap the old memory view (if any)
83  2) Destroy the old mapping object (Windows)
84  3) Resize the file to `nNewCapacity`
85  4) Create a new memory mapping with the new size
86  5) Map the file into the process address space
87 
88  The file is always resized *before* mapping, because the OS
89  cannot map memory beyond the current end of file.
90 
91  All sizes are expected to be aligned to the system granularity.
92 
93  This operation is relatively expensive and should not be performed
94  frequently. It is typically triggered only when the log buffer
95  runs out of space.
96 
97  @param nNewCapacity - new capacity in bytes (must be >= current size)
98  @retval - true - remapping succeeded
99  **/
100  bool remap_to_capacity(size_t nNewCapacity) noexcept;
101 
102  /**
103  @brief Ensure that mapped file has enough space for additional data.
104 
105  @details Checks whether the current mapped capacity is sufficient to store `nAdditionalBytes` more bytes.
106 
107  If there is not enough space, the file is grown (usually doubled), aligned to the system granularity,
108  and remapped using remap_to_capacity().
109 
110  This function guarantees that subsequent writes will not overflow the mapped memory region.
111 
112  @param nAdditionalBytes - Number of bytes that need to be written
113  @retval - true - enough space is available or remapping succeeded
114  **/
115  bool ensure_capacity(size_t nAdditionalBytes) noexcept;
116 
117  /**
118  @brief Align value up to the nearest multiple of alignment.
119 
120  @details This function rounds the given value `nValue` *upwards* to the nearest
121  multiple of `nAlignment` (alignment / granularity / page size).
122 
123  If `nValue` is already aligned, it is returned unchanged.
124 
125  This is required for memory mapping and file resizing because operating systems work with fixed-size pages:
126 
127  - Windows: Allocation Granularity (usually 64 KB)
128  - Linux: Page Size (usually 4 KB)
129  - macOS: Page Size (usually 16 KB)
130 
131  Mapping or resizing a file to a non-aligned size may fail or cause undefined behavior.
132 
133  @param nValue - value to align
134  @param nAlignment - alignment / granularity / page size
135  @retval - aligned value >= value
136  **/
137  static size_t align_up_u64(size_t nValue, size_t nAlignment) noexcept;
138 
139 private:
140 #if QX_WIN
141  HANDLE m_hFile = INVALID_HANDLE_VALUE;
142  HANDLE m_hMap = nullptr;
143 #else
144  int m_Fd = -1;
145 #endif
146 
147  std::byte* m_pData = nullptr;
148 
149  size_t m_nSize = 0; // bytes written
150  size_t m_nCapacity = 0; // mapped capacity in bytes
151  size_t m_nGranularity = 0; // allocation granularity
152 };
153 
154 } // namespace qx
155 
Base class for all file logger streams.
A category is a class that identifies a particular piece of code. This code can be located in differe...
Definition: category.h:59
High-performance file logger based on memory-mapped I/O.
virtual void do_log(const category &category, verbosity eVerbosity, std::thread::id threadId, std::chrono::system_clock::time_point messageTime, string_view svFile, string_view svFunction, int nLine, string_view svMessage) override
Proceed stream logging.
virtual void do_flush() override
Flush the stream.
file_logger_stream_mapping(const config &streamConfig=config(), unit< size_t, units::data > initialMapSize={ 1, units::data::mebibytes }) noexcept
file_logger_stream_mapping object constructor
Definition: base.h:52