qxLib
file_logger_stream_mapping.inl
Go to the documentation of this file.
1 /**
2 
3  @file file_logger_stream_mapping.inl
4  @author Khrapov
5  @date 15.01.2026
6  @copyright © Nick Khrapov, 2026. All right reserved.
7 
8 **/
9 
10 namespace qx
11 {
12 
14  const config& streamConfig,
15  unit<size_t, units::data> initialMapSize) noexcept
16  : base_file_logger_stream(streamConfig)
17 {
18  const std::filesystem::path path = prepare_folder_and_get_log_file_path(streamConfig);
19 
20 #if QX_WIN
21  SYSTEM_INFO si {};
22  GetSystemInfo(&si);
23  m_nGranularity = si.dwAllocationGranularity ? si.dwAllocationGranularity : 65536;
24 
25  m_hFile = CreateFileW(
26  path.c_str(),
27  GENERIC_READ | GENERIC_WRITE,
28  FILE_SHARE_READ,
29  nullptr,
30  streamConfig.eLogFilePolicy == log_file_policy::clear_then_upend ? CREATE_ALWAYS : OPEN_ALWAYS,
31  FILE_ATTRIBUTE_NORMAL,
32  nullptr);
33 
34  if (m_hFile == INVALID_HANDLE_VALUE)
35  return;
36 
37  LARGE_INTEGER sz {};
38  if (!GetFileSizeEx(m_hFile, &sz))
39  return;
40 
41  m_nSize = static_cast<size_t>(sz.QuadPart);
42 #else
43  const long ps = ::sysconf(_SC_PAGESIZE);
44  m_nGranularity = ps > 0 ? static_cast<size_t>(ps) : 4096ull;
45 
46  int flags = O_RDWR | O_CREAT;
47  if (streamConfig.eLogFilePolicy == log_file_policy::clear_then_upend)
48  flags |= O_TRUNC;
49 
50  m_Fd = ::open(path.c_str(), flags, 0644);
51  if (m_Fd < 0)
52  return;
53 
54  struct stat st {};
55  if (::fstat(m_Fd, &st) != 0)
56  return;
57 
58  m_nSize = static_cast<size_t>(st.st_size);
59 #endif
60 
61  const size_t nDefaultStartSize = initialMapSize ? convert(initialMapSize).to(units::data::bytes) : m_nGranularity;
62  size_t nInitialSize = m_nSize < nDefaultStartSize ? nDefaultStartSize : m_nSize;
63  nInitialSize = align_up_u64(nInitialSize, m_nGranularity);
64 
65  remap_to_capacity(nInitialSize);
66 }
67 
69  : base_file_logger_stream(std::move(other))
70 {
71 #if QX_WIN
72  std::swap(m_hFile, other.m_hFile);
73  std::swap(m_hMap, other.m_hMap);
74 #else
75  std::swap(m_Fd, other.m_Fd);
76 #endif
77  std::swap(m_pData, other.m_pData);
78  std::swap(m_nSize, other.m_nSize);
79  std::swap(m_nCapacity, other.m_nCapacity);
80  std::swap(m_nGranularity, other.m_nGranularity);
81 }
82 
83 inline file_logger_stream_mapping::~file_logger_stream_mapping() noexcept
84 {
85 #if QX_WIN
86  if (m_hFile == INVALID_HANDLE_VALUE)
87  return;
88 #else
89  if (m_Fd < 0)
90  return;
91 #endif
92 
94 
95  if (m_pData)
96  {
97 #if QX_WIN
98  UnmapViewOfFile(m_pData);
99 #else
100  ::munmap(m_pData, static_cast<size_t>(m_nCapacity));
101 #endif
102  m_pData = nullptr;
103  }
104 
105 #if QX_WIN
106  if (m_hMap)
107  {
108  CloseHandle(m_hMap);
109  m_hMap = nullptr;
110  }
111 #endif
112 
113 #if QX_WIN
114  LARGE_INTEGER li {};
115  li.QuadPart = static_cast<LONGLONG>(m_nSize);
116 
117  if (!SetFilePointerEx(m_hFile, li, nullptr, FILE_BEGIN))
118  return;
119 
120  if (!SetEndOfFile(m_hFile))
121  return;
122 
123  if (!FlushFileBuffers(m_hFile))
124  return;
125 
126  CloseHandle(m_hFile);
127  m_hFile = INVALID_HANDLE_VALUE;
128 #else
129  ::ftruncate(m_Fd, static_cast<off_t>(m_nSize));
130  ::fsync(m_Fd);
131  ::close(m_Fd);
132  m_Fd = -1;
133 #endif
134 
135  m_nCapacity = 0;
136  m_nSize = 0;
137 }
138 
140  const category& category,
141  verbosity eVerbosity,
142  std::thread::id threadId,
143  std::chrono::system_clock::time_point messageTime,
144  string_view svFile,
145  string_view svFunction,
146  int nLine,
147  string_view svMessage)
148 {
149  const size_t nRequiredSize = svMessage.size() * std::min(sizeof(char_type), sizeof(char16_t));
150  if (!ensure_capacity(nRequiredSize))
151  return;
152 
153 #if QX_WIN || QX_CONF_USE_CHAR
154  std::memcpy(m_pData + m_nSize, svMessage.data(), nRequiredSize);
155  m_nSize += nRequiredSize;
156 #else
157  std::array<char16_t, 2048> chunk;
158 
159  const char_type* pData = svMessage.data();
160  size_t nCharsRemaining = svMessage.size();
161 
162  while (nCharsRemaining > 0)
163  {
164  size_t nCharsToTake = std::min(nCharsRemaining, chunk.size());
165  for (size_t i = 0; i < nCharsToTake; ++i)
166  {
167  chunk[i] = static_cast<char16_t>(static_cast<char16_t>(pData[i]) & 0xFFFFu);
168  }
169 
170  std::memcpy(m_pData + m_nSize, chunk.data(), nCharsToTake * sizeof(char16_t));
171 
172  m_nSize += nCharsToTake * sizeof(char16_t);
173  pData += nCharsToTake;
174  nCharsRemaining -= nCharsToTake;
175  }
176 #endif
177 }
178 
180 {
181  if (!m_pData || m_nSize == 0)
182  return;
183 
184  // flush memory mapped data to the file
185 #if QX_WIN
186  if (!FlushViewOfFile(m_pData, m_nSize))
187  return;
188 #else
189  ::msync(m_pData, static_cast<size_t>(m_nSize), MS_SYNC);
190 #endif
191 
192 #if QX_WIN
193  if (m_hFile != INVALID_HANDLE_VALUE)
194 #else
195  if (m_Fd >= 0)
196 #endif
197  {
198  // update the file on disc
199 #if QX_WIN
200  FlushFileBuffers(m_hFile);
201 #else
202  ::fsync(m_Fd);
203 #endif
204 
205  // notify the OS that file changed (makes editors such as VS Code to reload it)
206 #if QX_WIN
207  FILETIME ft;
208  GetSystemTimeAsFileTime(&ft);
209  SetFileTime(m_hFile, nullptr, nullptr, &ft);
210 #else
211  struct timespec ts[2];
212  clock_gettime(CLOCK_REALTIME, &ts[0]);
213  ts[1] = ts[0];
214 
215  futimens(m_Fd, ts);
216 #endif
217  }
218 }
219 
220 inline bool file_logger_stream_mapping::remap_to_capacity(size_t nNewCapacity) noexcept
221 {
222  if (m_pData)
223  {
224 #if QX_WIN
225  UnmapViewOfFile(m_pData);
226 #else
227  ::munmap(m_pData, static_cast<size_t>(m_nCapacity));
228 #endif
229  m_pData = nullptr;
230  }
231 
232 #if QX_WIN
233  if (m_hMap)
234  {
235  CloseHandle(m_hMap);
236  m_hMap = nullptr;
237  }
238 #endif
239 
240 #if QX_WIN
241  LARGE_INTEGER li {};
242  li.QuadPart = static_cast<LONGLONG>(nNewCapacity);
243  if (!SetFilePointerEx(m_hFile, li, nullptr, FILE_BEGIN))
244  return false;
245  if (!SetEndOfFile(m_hFile))
246  return false;
247 #else
248  if (::ftruncate(m_Fd, static_cast<off_t>(nNewCapacity)) != 0)
249  return false;
250 #endif
251 
252 #if QX_WIN
253  DWORD hi = static_cast<DWORD>((nNewCapacity >> 32) & 0xFFFFFFFFu);
254  DWORD lo = static_cast<DWORD>(nNewCapacity & 0xFFFFFFFFu);
255 
256  m_hMap = CreateFileMappingW(m_hFile, nullptr, PAGE_READWRITE, hi, lo, nullptr);
257  if (!m_hMap)
258  return false;
259 
260  void* p = MapViewOfFile(m_hMap, FILE_MAP_ALL_ACCESS, 0, 0, nNewCapacity);
261  if (!p)
262  return false;
263 #else
264  void* p = ::mmap(nullptr, static_cast<size_t>(nNewCapacity), PROT_READ | PROT_WRITE, MAP_SHARED, m_Fd, 0);
265  if (p == MAP_FAILED)
266  return false;
267 #endif
268 
269  m_pData = static_cast<std::byte*>(p);
270  m_nCapacity = nNewCapacity;
271 
272  return true;
273 }
274 
275 inline bool file_logger_stream_mapping::ensure_capacity(size_t nAdditionalBytes) noexcept
276 {
277  if (m_nSize + nAdditionalBytes <= m_nCapacity)
278  return true;
279 
280  // grow: double until enough (with alignment)
281  size_t nRequiredSize = m_nSize + nAdditionalBytes;
282  size_t nNewCapacity = m_nCapacity ? m_nCapacity : m_nGranularity;
283  while (nNewCapacity < nRequiredSize)
284  nNewCapacity *= 2;
285 
286  nNewCapacity = align_up_u64(nNewCapacity, m_nGranularity);
287  return remap_to_capacity(nNewCapacity);
288 }
289 
290 inline size_t file_logger_stream_mapping::align_up_u64(size_t nValue, size_t nAlignment) noexcept
291 {
292  if (nAlignment == 0)
293  return nValue;
294 
295  const size_t nResult = nValue % nAlignment;
296  return nResult ? (nValue + (nAlignment - nResult)) : nValue;
297 }
298 
299 } // namespace qx
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
A conversion class.
Definition: base.h:77
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
Wrapper for enumerations to be used as a list of flags.
Definition: flags.h:62
Definition: base.h:52