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