GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 85.2% 202 / 0 / 237
Functions: 100.0% 20 / 0 / 20
Branches: 43.8% 212 / 0 / 484

src/logger.cpp
Line Branch Exec Source
1 #include "DetourModKit/logger.hpp"
2 #include "DetourModKit/async_logger.hpp"
3 #include "DetourModKit/filesystem.hpp"
4 #include "DetourModKit/format.hpp"
5
6 #include <algorithm>
7 #include <cstdio>
8 #include <ctime>
9 #include <filesystem>
10 #include <chrono>
11 #include <iomanip>
12 #include <iostream>
13 #include <stdexcept>
14 #include <array>
15
16 namespace DetourModKit
17 {
18
19 namespace
20 {
21 198 std::atomic<std::shared_ptr<const Logger::StaticConfig>> &static_config_atom()
22 {
23 static std::atomic<std::shared_ptr<const Logger::StaticConfig>> instance{
24 1 std::make_shared<const Logger::StaticConfig>(
25
4/8
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 11 taken 197 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 11 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 13 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
198 DEFAULT_LOG_PREFIX, DEFAULT_LOG_FILE_NAME, DEFAULT_TIMESTAMP_FORMAT)};
26 198 return instance;
27 }
28 } // anonymous namespace
29
30 1 std::shared_ptr<const Logger::StaticConfig> Logger::get_static_config()
31 {
32 1 return static_config_atom().load(std::memory_order_acquire);
33 }
34
35 197 void Logger::set_static_config(std::shared_ptr<const StaticConfig> config)
36 {
37 394 static_config_atom().store(std::move(config), std::memory_order_release);
38 197 }
39
40 1314 LogLevel Logger::string_to_log_level(std::string_view level_str)
41 {
42
1/2
✓ Branch 4 → 5 taken 1314 times.
✗ Branch 4 → 35 not taken.
1314 std::string upper_level_str(level_str);
43 1314 std::transform(upper_level_str.begin(), upper_level_str.end(), upper_level_str.begin(),
44 16901 [](unsigned char c)
45 16901 { return static_cast<char>(std::toupper(c)); });
46
47
3/4
✓ Branch 10 → 11 taken 1314 times.
✗ Branch 10 → 38 not taken.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 1311 times.
1314 if (upper_level_str == "TRACE")
48 3 return LogLevel::Trace;
49
3/4
✓ Branch 13 → 14 taken 1311 times.
✗ Branch 13 → 38 not taken.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 1308 times.
1311 if (upper_level_str == "DEBUG")
50 3 return LogLevel::Debug;
51
3/4
✓ Branch 16 → 17 taken 1308 times.
✗ Branch 16 → 38 not taken.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 17 → 19 taken 1305 times.
1308 if (upper_level_str == "INFO")
52 3 return LogLevel::Info;
53
3/4
✓ Branch 19 → 20 taken 1305 times.
✗ Branch 19 → 38 not taken.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 20 → 22 taken 1302 times.
1305 if (upper_level_str == "WARNING")
54 3 return LogLevel::Warning;
55
3/4
✓ Branch 22 → 23 taken 1302 times.
✗ Branch 22 → 38 not taken.
✓ Branch 23 → 24 taken 3 times.
✓ Branch 23 → 25 taken 1299 times.
1302 if (upper_level_str == "ERROR")
56 3 return LogLevel::Error;
57
58 std::cerr << "[" << DEFAULT_LOG_PREFIX << " Logger WARNING] Unrecognized log level string '" << level_str
59
6/12
✓ Branch 25 → 26 taken 1299 times.
✗ Branch 25 → 38 not taken.
✓ Branch 26 → 27 taken 1299 times.
✗ Branch 26 → 38 not taken.
✓ Branch 27 → 28 taken 1299 times.
✗ Branch 27 → 38 not taken.
✓ Branch 28 → 29 taken 1299 times.
✗ Branch 28 → 38 not taken.
✓ Branch 29 → 30 taken 1299 times.
✗ Branch 29 → 38 not taken.
✓ Branch 30 → 31 taken 1299 times.
✗ Branch 30 → 38 not taken.
1299 << "'. Defaulting to INFO." << '\n';
60 1299 return LogLevel::Info;
61 1314 }
62
63 197 void Logger::configure(std::string_view prefix, std::string_view file_name, std::string_view timestamp_fmt)
64 {
65
5/10
✓ Branch 4 → 5 taken 197 times.
✗ Branch 4 → 40 not taken.
✓ Branch 7 → 8 taken 197 times.
✗ Branch 7 → 34 not taken.
✓ Branch 10 → 11 taken 197 times.
✗ Branch 10 → 28 not taken.
✓ Branch 11 → 12 taken 197 times.
✗ Branch 11 → 26 not taken.
✓ Branch 12 → 13 taken 197 times.
✗ Branch 12 → 24 not taken.
985 set_static_config(std::make_shared<const StaticConfig>(std::string(prefix), std::string(file_name), std::string(timestamp_fmt)));
66
67 197 Logger &instance = get_instance();
68
69 // configure() is the authoritative reset path — allow reconfiguration
70 // even after shutdown to support reuse (e.g., test fixtures).
71 197 instance.shutdown_called_.store(false, std::memory_order_release);
72
73 // Comparison is done inside reconfigure() under the lock to prevent
74 // reading string members while another thread is modifying them.
75 197 instance.reconfigure(prefix, file_name, timestamp_fmt);
76 197 }
77
78 200 void Logger::reconfigure(std::string_view prefix, std::string_view file_name, std::string_view timestamp_fmt)
79 {
80
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 200 times.
200 if (shutdown_called_.load(std::memory_order_acquire))
81 {
82 return;
83 }
84
85 // Acquire both async_mutex_ and log_mutex_ to prevent concurrent log() calls
86 // from reading partially-updated string members during reconfiguration
87
1/2
✓ Branch 6 → 7 taken 200 times.
✗ Branch 6 → 102 not taken.
200 std::scoped_lock lock(async_mutex_, *log_mutex_ptr_);
88
89 // Skip reconfiguration only when all parameters match AND the stream is usable.
90 // After shutdown or a prior open failure the stream may be closed, so we must
91 // fall through to reopen even if the strings are identical.
92
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 20 taken 193 times.
394 if (log_file_stream_ptr_->is_open() &&
93
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 20 taken 1 time.
195 log_prefix_ == prefix &&
94
3/6
✓ Branch 9 → 10 taken 194 times.
✓ Branch 9 → 20 taken 6 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 200 times.
395 log_file_name_ == file_name &&
95 timestamp_format_ == timestamp_fmt)
96 {
97 return;
98 }
99
100
6/8
✓ Branch 25 → 26 taken 194 times.
✓ Branch 25 → 30 taken 6 times.
✓ Branch 27 → 28 taken 194 times.
✗ Branch 27 → 100 not taken.
✓ Branch 28 → 29 taken 194 times.
✗ Branch 28 → 30 not taken.
✓ Branch 31 → 32 taken 194 times.
✓ Branch 31 → 51 taken 6 times.
200 if (log_file_stream_ptr_->is_open() && log_file_stream_ptr_->good())
101 {
102 388 *log_file_stream_ptr_ << "[" << get_timestamp() << "] "
103
6/12
✓ Branch 33 → 34 taken 194 times.
✗ Branch 33 → 100 not taken.
✓ Branch 34 → 35 taken 194 times.
✗ Branch 34 → 94 not taken.
✓ Branch 35 → 36 taken 194 times.
✗ Branch 35 → 92 not taken.
✓ Branch 36 → 37 taken 194 times.
✗ Branch 36 → 92 not taken.
✓ Branch 37 → 38 taken 194 times.
✗ Branch 37 → 92 not taken.
✓ Branch 40 → 41 taken 194 times.
✗ Branch 40 → 92 not taken.
194 << "[" << std::setw(7) << std::left << "INFO" << "] :: "
104
5/10
✓ Branch 41 → 42 taken 194 times.
✗ Branch 41 → 92 not taken.
✓ Branch 42 → 43 taken 194 times.
✗ Branch 42 → 92 not taken.
✓ Branch 43 → 44 taken 194 times.
✗ Branch 43 → 92 not taken.
✓ Branch 44 → 45 taken 194 times.
✗ Branch 44 → 92 not taken.
✓ Branch 45 → 46 taken 194 times.
✗ Branch 45 → 92 not taken.
194 << "Logger reconfiguring. New file: " << file_name << '\n';
105
1/2
✓ Branch 48 → 49 taken 194 times.
✗ Branch 48 → 100 not taken.
194 log_file_stream_ptr_->flush();
106
1/2
✓ Branch 50 → 51 taken 194 times.
✗ Branch 50 → 100 not taken.
194 log_file_stream_ptr_->close();
107 }
108
109
1/2
✓ Branch 51 → 52 taken 200 times.
✗ Branch 51 → 100 not taken.
200 log_prefix_ = prefix;
110
1/2
✓ Branch 52 → 53 taken 200 times.
✗ Branch 52 → 100 not taken.
200 log_file_name_ = file_name;
111
1/2
✓ Branch 53 → 54 taken 200 times.
✗ Branch 53 → 100 not taken.
200 timestamp_format_ = timestamp_fmt;
112
113
1/2
✓ Branch 54 → 55 taken 200 times.
✗ Branch 54 → 100 not taken.
200 std::string log_file_full_path = generate_log_file_path();
114
1/2
✓ Branch 57 → 58 taken 200 times.
✗ Branch 57 → 98 not taken.
200 log_file_stream_ptr_->open(log_file_full_path, std::ios::out | std::ios::trunc);
115
116
2/2
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 68 taken 199 times.
200 if (!log_file_stream_ptr_->is_open())
117 {
118 1 std::cerr << "[" << log_prefix_ << " Logger CRITICAL ERROR] "
119 << "Failed to open log file at: " << log_file_full_path
120
7/14
✓ Branch 61 → 62 taken 1 time.
✗ Branch 61 → 98 not taken.
✓ Branch 62 → 63 taken 1 time.
✗ Branch 62 → 98 not taken.
✓ Branch 63 → 64 taken 1 time.
✗ Branch 63 → 98 not taken.
✓ Branch 64 → 65 taken 1 time.
✗ Branch 64 → 98 not taken.
✓ Branch 65 → 66 taken 1 time.
✗ Branch 65 → 98 not taken.
✓ Branch 66 → 67 taken 1 time.
✗ Branch 66 → 98 not taken.
✓ Branch 67 → 84 taken 1 time.
✗ Branch 67 → 98 not taken.
1 << ". Subsequent logs to file will fail." << '\n';
121 }
122 else
123 {
124 398 *log_file_stream_ptr_ << "[" << get_timestamp() << "] "
125
6/12
✓ Branch 69 → 70 taken 199 times.
✗ Branch 69 → 98 not taken.
✓ Branch 70 → 71 taken 199 times.
✗ Branch 70 → 97 not taken.
✓ Branch 71 → 72 taken 199 times.
✗ Branch 71 → 95 not taken.
✓ Branch 72 → 73 taken 199 times.
✗ Branch 72 → 95 not taken.
✓ Branch 73 → 74 taken 199 times.
✗ Branch 73 → 95 not taken.
✓ Branch 76 → 77 taken 199 times.
✗ Branch 76 → 95 not taken.
199 << "[" << std::setw(7) << std::left << "INFO" << "] :: "
126
5/10
✓ Branch 77 → 78 taken 199 times.
✗ Branch 77 → 95 not taken.
✓ Branch 78 → 79 taken 199 times.
✗ Branch 78 → 95 not taken.
✓ Branch 79 → 80 taken 199 times.
✗ Branch 79 → 95 not taken.
✓ Branch 80 → 81 taken 199 times.
✗ Branch 80 → 95 not taken.
✓ Branch 81 → 82 taken 199 times.
✗ Branch 81 → 95 not taken.
199 << "Logger reconfigured. Now logging to: " << log_file_full_path << '\n';
127 }
128
1/2
✓ Branch 87 → 88 taken 200 times.
✗ Branch 87 → 90 not taken.
200 }
129
130 1 Logger::Logger()
131
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 67 not taken.
1 : log_file_stream_ptr_(std::make_shared<WinFileStream>()),
132
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 65 not taken.
2 log_mutex_ptr_(std::make_shared<std::mutex>())
133 {
134 {
135
1/2
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 52 not taken.
1 auto config = get_static_config();
136
1/2
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 50 not taken.
1 log_prefix_ = config->log_prefix;
137
1/2
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 50 not taken.
1 log_file_name_ = config->log_file_name;
138
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 50 not taken.
1 timestamp_format_ = config->timestamp_format;
139 1 }
140
141
1/2
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 58 not taken.
1 const std::string log_file_full_path = generate_log_file_path();
142
1/2
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 56 not taken.
1 log_file_stream_ptr_->open(log_file_full_path, std::ios::out | std::ios::trunc);
143
144
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 34 taken 1 time.
1 if (!log_file_stream_ptr_->is_open())
145 {
146 std::cerr << "[" << log_prefix_ << " Logger CRITICAL ERROR] "
147 << "Failed to open log file at: " << log_file_full_path
148 << ". Subsequent logs to file will fail." << '\n';
149 }
150 else
151 {
152 2 *log_file_stream_ptr_ << "[" << get_timestamp() << "] ["
153
5/10
✓ Branch 35 → 36 taken 1 time.
✗ Branch 35 → 56 not taken.
✓ Branch 36 → 37 taken 1 time.
✗ Branch 36 → 55 not taken.
✓ Branch 37 → 38 taken 1 time.
✗ Branch 37 → 53 not taken.
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 53 not taken.
✓ Branch 41 → 42 taken 1 time.
✗ Branch 41 → 53 not taken.
1 << std::setw(7) << std::left << "INFO"
154
4/8
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 53 not taken.
✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 53 not taken.
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 53 not taken.
✓ Branch 45 → 46 taken 1 time.
✗ Branch 45 → 53 not taken.
1 << "] :: Logger initialized. Logging to: " << log_file_full_path << '\n';
155 }
156 1 }
157
158 2 Logger::~Logger() noexcept
159 {
160 1 bool expected = false;
161
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (!shutdown_called_.compare_exchange_strong(expected, true,
162 std::memory_order_acq_rel))
163 {
164 return;
165 }
166 1 shutdown_internal();
167
7/14
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 22 not taken.
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 26 not taken.
✓ Branch 28 → 29 taken 1 time.
✗ Branch 28 → 30 not taken.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 32 → 34 not taken.
1 }
168
169 10 void Logger::shutdown()
170 {
171 10 bool expected = false;
172
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 5 times.
10 if (!shutdown_called_.compare_exchange_strong(expected, true,
173 std::memory_order_acq_rel))
174 {
175 5 return;
176 }
177
1/2
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 8 not taken.
5 shutdown_internal();
178 }
179
180 6 void Logger::shutdown_internal()
181 {
182 6 std::shared_ptr<AsyncLogger> local_logger;
183
184 {
185
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 33 not taken.
6 std::lock_guard<std::mutex> lock(async_mutex_);
186
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 15 taken 3 times.
6 if (async_mode_enabled_.load(std::memory_order_acquire))
187 {
188 3 local_logger = async_logger_.exchange(nullptr, std::memory_order_acq_rel);
189 3 async_mode_enabled_.store(false, std::memory_order_release);
190
1/2
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 15 not taken.
3 if (local_logger)
191 {
192 3 local_logger->shutdown();
193 }
194 }
195 6 }
196
197 {
198 // Acquire both mutexes to prevent configure()/reconfigure() from
199 // opening a new stream in the gap after BLOCK 1 releases async_mutex_.
200
1/2
✓ Branch 17 → 18 taken 6 times.
✗ Branch 17 → 36 not taken.
6 std::scoped_lock lock(async_mutex_, *log_mutex_ptr_);
201
3/6
✓ Branch 19 → 20 taken 6 times.
✗ Branch 19 → 24 not taken.
✓ Branch 22 → 23 taken 6 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 6 times.
✗ Branch 25 → 30 not taken.
6 if (log_file_stream_ptr_ && log_file_stream_ptr_->is_open())
202 {
203
1/2
✓ Branch 27 → 28 taken 6 times.
✗ Branch 27 → 34 not taken.
6 log_file_stream_ptr_->flush();
204
1/2
✓ Branch 29 → 30 taken 6 times.
✗ Branch 29 → 34 not taken.
6 log_file_stream_ptr_->close();
205 }
206 6 }
207 6 }
208
209 46 void Logger::set_log_level(LogLevel level)
210 {
211 46 auto level_int = static_cast<std::underlying_type_t<LogLevel>>(level);
212
3/4
✓ Branch 2 → 3 taken 46 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 6 taken 44 times.
46 if (level_int < 0 || level_int > static_cast<std::underlying_type_t<LogLevel>>(LogLevel::Error))
213 {
214
1/2
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 16 not taken.
2 log(LogLevel::Warning, "Attempted to set an invalid log level value ({}). Keeping current level.", level_int);
215 14 return;
216 }
217
218 44 auto old_level = current_log_level_.load(std::memory_order_acquire);
219
2/2
✓ Branch 7 → 8 taken 12 times.
✓ Branch 7 → 9 taken 32 times.
44 if (old_level == level)
220 {
221 12 return;
222 }
223
224 32 current_log_level_.store(level, std::memory_order_release);
225
226
1/2
✓ Branch 12 → 13 taken 32 times.
✗ Branch 12 → 17 not taken.
32 log(LogLevel::Info, "Log level changed from {} to {}",
227 64 log_level_to_string(old_level), log_level_to_string(level));
228 }
229
230 1646 void Logger::log(LogLevel level, std::string_view message)
231 {
232
2/2
✓ Branch 3 → 4 taken 1601 times.
✓ Branch 3 → 65 taken 60 times.
1646 if (level >= current_log_level_.load(std::memory_order_acquire))
233 {
234 // Fast path: lock-free check via atomic shared_ptr
235
2/2
✓ Branch 5 → 6 taken 841 times.
✓ Branch 5 → 18 taken 759 times.
1601 if (async_mode_enabled_.load(std::memory_order_acquire))
236 {
237 841 auto local_logger = async_logger_.load(std::memory_order_acquire);
238
1/2
✓ Branch 8 → 9 taken 859 times.
✗ Branch 8 → 12 not taken.
859 if (local_logger)
239 {
240 859 static_cast<void>(local_logger->enqueue(level, message));
241 851 return;
242 }
243
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 17 taken 858 times.
851 }
244
245 759 const auto level_str = log_level_to_string(level);
246
1/2
✓ Branch 20 → 21 taken 752 times.
✗ Branch 20 → 74 not taken.
752 std::lock_guard<std::mutex> lock(*log_mutex_ptr_);
247
248
6/8
✓ Branch 23 → 24 taken 678 times.
✓ Branch 23 → 28 taken 74 times.
✓ Branch 25 → 26 taken 678 times.
✗ Branch 25 → 72 not taken.
✓ Branch 26 → 27 taken 678 times.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 678 times.
✓ Branch 29 → 47 taken 74 times.
752 if (log_file_stream_ptr_->is_open() && log_file_stream_ptr_->good())
249 {
250 1356 *log_file_stream_ptr_ << "[" << get_timestamp() << "] "
251
6/12
✓ Branch 31 → 32 taken 678 times.
✗ Branch 31 → 72 not taken.
✓ Branch 32 → 33 taken 678 times.
✗ Branch 32 → 68 not taken.
✓ Branch 33 → 34 taken 678 times.
✗ Branch 33 → 66 not taken.
✓ Branch 34 → 35 taken 678 times.
✗ Branch 34 → 66 not taken.
✓ Branch 35 → 36 taken 678 times.
✗ Branch 35 → 66 not taken.
✓ Branch 38 → 39 taken 678 times.
✗ Branch 38 → 66 not taken.
678 << "[" << std::setw(7) << std::left << level_str << "] :: "
252
4/8
✓ Branch 39 → 40 taken 678 times.
✗ Branch 39 → 66 not taken.
✓ Branch 40 → 41 taken 678 times.
✗ Branch 40 → 66 not taken.
✓ Branch 41 → 42 taken 678 times.
✗ Branch 41 → 66 not taken.
✓ Branch 42 → 43 taken 678 times.
✗ Branch 42 → 66 not taken.
678 << message << '\n';
253
254 // Flush on warnings/errors to ensure critical messages survive crashes
255
2/2
✓ Branch 44 → 45 taken 99 times.
✓ Branch 44 → 63 taken 579 times.
678 if (level >= LogLevel::Warning)
256 {
257
1/2
✓ Branch 46 → 63 taken 99 times.
✗ Branch 46 → 72 not taken.
99 log_file_stream_ptr_->flush();
258 }
259 }
260
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 63 taken 74 times.
74 else if (level >= LogLevel::Error)
261 {
262 std::cerr << "[" << log_prefix_ << " LOG_FILE_WRITE_ERROR] [" << get_timestamp() << "] ["
263 << std::setw(7) << std::left << level_str << "] :: "
264 << message << '\n';
265 }
266 752 }
267 }
268
269 1072 std::string Logger::get_timestamp() const
270 {
271 try
272 {
273 1072 const auto now = std::chrono::system_clock::now();
274 1072 const auto in_time_t = std::chrono::system_clock::to_time_t(now);
275 1072 std::tm timeinfo_struct = {};
276
277 #if defined(_MSC_VER)
278 if (localtime_s(&timeinfo_struct, &in_time_t) != 0)
279 {
280 throw std::runtime_error("localtime_s failed to convert time.");
281 }
282 #elif defined(__MINGW32__) || defined(__MINGW64__)
283 // MinGW: localtime_s has reversed parameter order (ISO C11 Annex K)
284
2/4
✓ Branch 8 → 9 taken 1072 times.
✗ Branch 8 → 43 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 1072 times.
1072 if (localtime_s(&timeinfo_struct, &in_time_t) != 0)
285 {
286 throw std::runtime_error("localtime_s failed to convert time.");
287 }
288 #else
289 if (localtime_r(&in_time_t, &timeinfo_struct) == nullptr)
290 {
291 throw std::runtime_error("localtime_r failed to convert time.");
292 }
293 #endif
294 // Single stack buffer for timestamp + milliseconds, no heap allocation
295 char buf[134];
296 1072 const size_t len = std::strftime(buf, sizeof(buf) - 5, timestamp_format_.c_str(), &timeinfo_struct);
297
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 20 taken 1072 times.
1072 if (len == 0)
298 {
299 return "TIMESTAMP_FORMAT_ERROR";
300 }
301
302
1/2
✓ Branch 21 → 22 taken 1072 times.
✗ Branch 21 → 37 not taken.
1072 const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
303 now.time_since_epoch()) %
304
1/2
✓ Branch 22 → 23 taken 1072 times.
✗ Branch 22 → 37 not taken.
2144 1000;
305
1/2
✓ Branch 24 → 25 taken 1072 times.
✗ Branch 24 → 43 not taken.
1072 const int ms_len = std::snprintf(buf + len, 5, ".%03d", static_cast<int>(ms.count()));
306
1/2
✓ Branch 27 → 28 taken 1072 times.
✗ Branch 27 → 40 not taken.
2144 return std::string(buf, len + static_cast<size_t>(ms_len));
307 }
308 catch (const std::exception &e)
309 {
310 std::cerr << "[" << log_prefix_ << " Logger TIMESTAMP_ERROR] Failed to generate timestamp: " << e.what() << '\n';
311 return "TIMESTAMP_GENERATION_ERROR";
312 }
313 catch (...)
314 {
315 std::cerr << "[" << log_prefix_ << " Logger TIMESTAMP_ERROR] Unknown exception during timestamp generation." << '\n';
316 return "TIMESTAMP_GENERATION_ERROR";
317 }
318 }
319
320 201 std::string Logger::generate_log_file_path() const
321 {
322
1/2
✓ Branch 2 → 3 taken 201 times.
✗ Branch 2 → 81 not taken.
201 std::filesystem::path log_file_path_obj(log_file_name_);
323
2/2
✓ Branch 4 → 5 taken 199 times.
✓ Branch 4 → 7 taken 2 times.
201 if (log_file_path_obj.is_absolute())
324 {
325
1/2
✓ Branch 5 → 6 taken 199 times.
✗ Branch 5 → 79 not taken.
199 return log_file_name_;
326 }
327
328 try
329 {
330
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 51 not taken.
2 std::string module_dir = Filesystem::get_runtime_directory();
331
4/8
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 12 not taken.
✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 49 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 2 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 23 taken 2 times.
2 if (module_dir.empty() || module_dir == ".")
332 {
333 std::cerr << "[" << log_prefix_ << " Logger PATH_WARNING] "
334 << "Could not determine module directory. Using relative path: " << log_file_name_ << '\n';
335 return log_file_name_;
336 }
337
338
3/6
✓ Branch 23 → 24 taken 2 times.
✗ Branch 23 → 43 not taken.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 40 not taken.
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 38 not taken.
2 const std::filesystem::path final_log_path = std::filesystem::path(module_dir) / log_file_name_;
339
2/4
✓ Branch 28 → 29 taken 2 times.
✗ Branch 28 → 46 not taken.
✓ Branch 29 → 30 taken 2 times.
✗ Branch 29 → 44 not taken.
2 return final_log_path.lexically_normal().string();
340 2 }
341 catch (const std::exception &e)
342 {
343 std::cerr << "[" << log_prefix_ << " Logger PATH_WARNING] Failed to determine module directory for log file: "
344 << e.what() << ". Using relative path for log file: " << log_file_name_ << '\n';
345 return log_file_name_;
346 }
347 catch (...)
348 {
349 std::cerr << "[" << log_prefix_ << " Logger PATH_WARNING] Unknown exception while determining module directory for log file."
350 << " Using relative path: " << log_file_name_ << '\n';
351 return log_file_name_;
352 }
353 201 }
354
355 22 void Logger::enable_async_mode(const AsyncLoggerConfig &config)
356 {
357 22 bool should_log_error = false;
358 22 bool should_log_success = false;
359 22 std::string error_msg;
360 22 size_t queue_cap = 0;
361 22 size_t batch_sz = 0;
362
363 {
364
1/2
✓ Branch 3 → 4 taken 22 times.
✗ Branch 3 → 59 not taken.
22 std::lock_guard<std::mutex> lock(async_mutex_);
365
366
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 21 times.
22 if (async_mode_enabled_.load(std::memory_order_acquire))
367 {
368 1 return;
369 }
370
371
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 21 times.
21 if (!log_file_stream_ptr_->is_open())
372 {
373 should_log_error = true;
374 error_msg = "Cannot enable async mode: log file is not open.";
375 }
376 else
377 {
378 try
379 {
380 19 async_logger_.store(
381
2/2
✓ Branch 11 → 12 taken 19 times.
✓ Branch 11 → 34 taken 2 times.
40 std::make_shared<AsyncLogger>(config, log_file_stream_ptr_, log_mutex_ptr_),
382 std::memory_order_release);
383 19 async_mode_enabled_.store(true, std::memory_order_release);
384 19 should_log_success = true;
385 19 queue_cap = config.queue_capacity;
386 19 batch_sz = config.batch_size;
387 }
388
1/2
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 2 times.
2 catch (const std::exception &e)
389 {
390 2 should_log_error = true;
391
2/4
✓ Branch 41 → 42 taken 2 times.
✗ Branch 41 → 50 not taken.
✓ Branch 42 → 43 taken 2 times.
✗ Branch 42 → 48 not taken.
4 error_msg = std::string("Failed to enable async mode: ") + e.what();
392 2 }
393 }
394
2/2
✓ Branch 18 → 19 taken 21 times.
✓ Branch 18 → 21 taken 1 time.
22 }
395
396
2/2
✓ Branch 20 → 22 taken 2 times.
✓ Branch 20 → 24 taken 19 times.
21 if (should_log_error)
397 {
398
1/2
✓ Branch 22 → 23 taken 2 times.
✗ Branch 22 → 60 not taken.
2 log(LogLevel::Error, "{}", error_msg);
399 }
400
1/2
✓ Branch 24 → 25 taken 19 times.
✗ Branch 24 → 27 not taken.
19 else if (should_log_success)
401 {
402
1/2
✓ Branch 25 → 26 taken 19 times.
✗ Branch 25 → 61 not taken.
19 log(LogLevel::Info, "Async logging mode enabled. Queue capacity: {}, Batch size: {}",
403 queue_cap, batch_sz);
404 }
405
2/2
✓ Branch 29 → 30 taken 21 times.
✓ Branch 29 → 32 taken 1 time.
22 }
406
407 17 void Logger::enable_async_mode()
408 {
409
1/2
✓ Branch 3 → 4 taken 17 times.
✗ Branch 3 → 5 not taken.
17 enable_async_mode(AsyncLoggerConfig{});
410 17 }
411
412 18 void Logger::disable_async_mode()
413 {
414 18 bool should_log = false;
415
416 {
417
1/2
✓ Branch 2 → 3 taken 18 times.
✗ Branch 2 → 25 not taken.
18 std::lock_guard<std::mutex> lock(async_mutex_);
418
419
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 16 times.
18 if (!async_mode_enabled_.load(std::memory_order_acquire))
420 {
421 2 return;
422 }
423
424 16 auto local_async = async_logger_.exchange(nullptr, std::memory_order_acq_rel);
425
1/2
✓ Branch 10 → 11 taken 16 times.
✗ Branch 10 → 13 not taken.
16 if (local_async)
426 {
427 16 local_async->shutdown();
428 }
429
430 16 async_mode_enabled_.store(false, std::memory_order_release);
431 16 should_log = true;
432
2/2
✓ Branch 17 → 18 taken 16 times.
✓ Branch 17 → 20 taken 2 times.
18 }
433
434
1/2
✓ Branch 19 → 21 taken 16 times.
✗ Branch 19 → 24 not taken.
16 if (should_log)
435 {
436
1/2
✓ Branch 22 → 23 taken 16 times.
✗ Branch 22 → 26 not taken.
16 log(LogLevel::Info, "Async logging mode disabled. Switched to synchronous mode.");
437 }
438 }
439
440 19 bool Logger::is_async_mode_enabled() const
441 {
442 19 return async_mode_enabled_.load(std::memory_order_acquire);
443 }
444
445 26 void Logger::flush()
446 {
447
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 16 taken 22 times.
26 if (async_mode_enabled_.load(std::memory_order_acquire))
448 {
449 4 auto local_logger = async_logger_.load(std::memory_order_acquire);
450
1/2
✓ Branch 6 → 7 taken 4 times.
✗ Branch 6 → 10 not taken.
4 if (local_logger)
451 {
452 4 local_logger->flush();
453 4 return;
454 }
455
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 4 times.
4 }
456
457
1/2
✓ Branch 17 → 18 taken 22 times.
✗ Branch 17 → 28 not taken.
22 std::lock_guard<std::mutex> lock(*log_mutex_ptr_);
458
1/2
✓ Branch 20 → 21 taken 22 times.
✗ Branch 20 → 23 not taken.
22 if (log_file_stream_ptr_->is_open())
459 {
460
1/2
✓ Branch 22 → 23 taken 22 times.
✗ Branch 22 → 26 not taken.
22 log_file_stream_ptr_->flush();
461 }
462 22 }
463
464
465 } // namespace DetourModKit
466