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 {
47 public:
48  /**
49  @brief file_logger_stream_mapping object constructor
50  @param streamConfig - file logger configuration
51  @param initialMapSize - initial mapping size (outer capacity). mapping grows when needed
52  **/
54  const config& streamConfig = config(),
55  unit<size_t, units::data> initialMapSize = { 1, units::data::mebibytes }) noexcept;
56 
58 
59  virtual ~file_logger_stream_mapping() noexcept override;
60 
61  // base_logger_stream
62  //
63  virtual void do_log(const category& category, verbosity eVerbosity, string_view svMessage) override;
64  virtual void do_flush() override;
65 
66 private:
67  /**
68  @brief Remap file to a new capacity.
69 
70  @details This function performs a full remapping sequence:
71 
72  1) Unmap the old memory view (if any)
73  2) Destroy the old mapping object (Windows)
74  3) Resize the file to `nNewCapacity`
75  4) Create a new memory mapping with the new size
76  5) Map the file into the process address space
77 
78  The file is always resized *before* mapping, because the OS
79  cannot map memory beyond the current end of file.
80 
81  All sizes are expected to be aligned to the system granularity.
82 
83  This operation is relatively expensive and should not be performed
84  frequently. It is typically triggered only when the log buffer
85  runs out of space.
86 
87  @param nNewCapacity - new capacity in bytes (must be >= current size)
88  @retval - true - remapping succeeded
89  **/
90  bool remap_to_capacity(size_t nNewCapacity) noexcept;
91 
92  /**
93  @brief Ensure that mapped file has enough space for additional data.
94 
95  @details Checks whether the current mapped capacity is sufficient to store `nAdditionalBytes` more bytes.
96 
97  If there is not enough space, the file is grown (usually doubled), aligned to the system granularity,
98  and remapped using remap_to_capacity().
99 
100  This function guarantees that subsequent writes will not overflow the mapped memory region.
101 
102  @param nAdditionalBytes - Number of bytes that need to be written
103  @retval - true - enough space is available or remapping succeeded
104  **/
105  bool ensure_capacity(size_t nAdditionalBytes) noexcept;
106 
107  /**
108  @brief Align value up to the nearest multiple of alignment.
109 
110  @details This function rounds the given value `nValue` *upwards* to the nearest
111  multiple of `nAlignment` (alignment / granularity / page size).
112 
113  If `nValue` is already aligned, it is returned unchanged.
114 
115  This is required for memory mapping and file resizing because operating systems work with fixed-size pages:
116 
117  - Windows: Allocation Granularity (usually 64 KB)
118  - Linux: Page Size (usually 4 KB)
119  - macOS: Page Size (usually 16 KB)
120 
121  Mapping or resizing a file to a non-aligned size may fail or cause undefined behavior.
122 
123  @param nValue - value to align
124  @param nAlignment - alignment / granularity / page size
125  @retval - aligned value >= value
126  **/
127  static size_t align_up_u64(size_t nValue, size_t nAlignment) noexcept;
128 
129 private:
130 #if QX_WIN
131  HANDLE m_hFile = INVALID_HANDLE_VALUE;
132  HANDLE m_hMap = nullptr;
133 #else
134  int m_Fd = -1;
135 #endif
136 
137  std::byte* m_pData = nullptr;
138 
139  size_t m_nSize = 0; // bytes written
140  size_t m_nCapacity = 0; // mapped capacity in bytes
141  size_t m_nGranularity = 0; // allocation granularity
142 };
143 
144 } // namespace qx
145 
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, 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