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