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 (c) 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 ? static_cast<size_t>(si.dwAllocationGranularity) : size_t { 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  // No extended unmapping flags are needed.
99  UnmapViewOfFile(m_pData); //-V2001
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  // No extended unmapping flags are needed.
227  UnmapViewOfFile(m_pData); //-V2001
228 #else
229  ::munmap(m_pData, static_cast<size_t>(m_nCapacity));
230 #endif
231  m_pData = nullptr;
232  }
233 
234 #if QX_WIN
235  if (m_hMap)
236  {
237  CloseHandle(m_hMap);
238  m_hMap = nullptr;
239  }
240 #endif
241 
242 #if QX_WIN
243  LARGE_INTEGER li {};
244  li.QuadPart = static_cast<LONGLONG>(nNewCapacity);
245  if (!SetFilePointerEx(m_hFile, li, nullptr, FILE_BEGIN))
246  return false;
247  if (!SetEndOfFile(m_hFile))
248  return false;
249 #else
250  if (::ftruncate(m_Fd, static_cast<off_t>(nNewCapacity)) != 0)
251  return false;
252 #endif
253 
254 #if QX_WIN
255  const u64 nMappingSize = static_cast<u64>(nNewCapacity);
256  const DWORD hi = static_cast<DWORD>(nMappingSize >> std::numeric_limits<DWORD>::digits);
257  const DWORD lo = static_cast<DWORD>(nMappingSize);
258 
259  m_hMap = CreateFileMappingW(m_hFile, nullptr, PAGE_READWRITE, hi, lo, nullptr);
260  if (!m_hMap)
261  return false;
262 
263  // Let the OS choose the address; MapViewOfFileEx with nullptr is equivalent.
264  void* p = MapViewOfFile(m_hMap, FILE_MAP_ALL_ACCESS, 0, 0, nNewCapacity); //-V2001
265  if (!p)
266  return false;
267 #else
268  void* p = ::mmap(nullptr, static_cast<size_t>(nNewCapacity), PROT_READ | PROT_WRITE, MAP_SHARED, m_Fd, 0);
269  if (p == MAP_FAILED)
270  return false;
271 #endif
272 
273  m_pData = static_cast<std::byte*>(p);
274  m_nCapacity = nNewCapacity;
275 
276  return true;
277 }
278 
279 inline bool file_logger_stream_mapping::ensure_capacity(size_t nAdditionalBytes) noexcept
280 {
281  if (m_nSize + nAdditionalBytes <= m_nCapacity)
282  return true;
283 
284  // grow: double until enough (with alignment)
285  size_t nRequiredSize = m_nSize + nAdditionalBytes;
286  size_t nNewCapacity = m_nCapacity ? m_nCapacity : m_nGranularity;
287  while (nNewCapacity < nRequiredSize)
288  nNewCapacity *= 2;
289 
290  nNewCapacity = align_up_u64(nNewCapacity, m_nGranularity);
291  return remap_to_capacity(nNewCapacity);
292 }
293 
294 inline size_t file_logger_stream_mapping::align_up_u64(size_t nValue, size_t nAlignment) noexcept
295 {
296  if (nAlignment == 0)
297  return nValue;
298 
299  const size_t nResult = nValue % nAlignment;
300  return nResult ? (nValue + (nAlignment - nResult)) : nValue;
301 }
302 
303 } // 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:62
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:64
Definition: base.h:52
std::uint64_t u64
Definition: typedefs.h:26