yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
log.cc
Go to the documentation of this file.
1#include "util/log.h"
2
3#include <chrono>
4#include <cstdint>
5#include <ctime>
6#include <filesystem>
7#include <iomanip>
8#include <iostream>
9#include <mutex>
10#include <string>
11
12#include "absl/strings/str_format.h"
13#include "core/features.h"
14
15namespace {
16
17// Serializes writes: background loaders log from other threads.
18std::mutex& LogWriteMutex() {
19 static std::mutex mutex;
20 return mutex;
21}
22
23// Keep the log file bounded: at open, a file over 10 MB becomes <file>.1
24// (older copies shift to .2 and .3). The yaze.log on a daily-use machine had
25// grown to 100 MB across builds.
26void RotateLogFileIfLarge(const std::string& path) {
27 constexpr std::uintmax_t kMaxBytes = 10u * 1024u * 1024u;
28 constexpr int kKeep = 3;
29 std::error_code ec;
30 const auto size = std::filesystem::file_size(path, ec);
31 if (ec || size <= kMaxBytes) {
32 return;
33 }
34 std::filesystem::remove(path + "." + std::to_string(kKeep), ec);
35 for (int i = kKeep - 1; i >= 1; --i) {
36 std::filesystem::rename(path + "." + std::to_string(i),
37 path + "." + std::to_string(i + 1), ec);
38 }
39 std::filesystem::rename(path, path + ".1", ec);
40}
41
42} // namespace
43
44namespace yaze {
45namespace util {
46
47// Helper function to convert LogLevel enum to a string representation.
48static const char* LogLevelToString(LogLevel level) {
49 switch (level) {
51 return "YAZE_DEBUG";
52 case LogLevel::INFO:
53 return "INFO";
55 return "WARN";
56 case LogLevel::ERROR:
57 return "ERROR";
58 case LogLevel::FATAL:
59 return "FATAL";
60 }
61 return "UNKN";
62}
63
64// --- LogManager Implementation ---
65
70
72 : min_level_(LogLevel::INFO), all_categories_enabled_(true) {}
73
75 if (log_stream_.is_open()) {
76 log_stream_.close();
77 }
78}
79
80void LogManager::configure(LogLevel level, const std::string& file_path,
81 const std::set<std::string>& categories) {
82 min_level_.store(level);
83
84 enabled_categories_.clear();
86
87 for (const auto& cat : categories) {
88 if (!cat.empty() && cat[0] == '-') {
89 if (cat.length() > 1) {
90 disabled_categories_.insert(cat.substr(1));
91 }
92 } else {
93 enabled_categories_.insert(cat);
94 }
95 }
96
97 if (enabled_categories_.empty()) {
98 all_categories_enabled_.store(true);
99 } else {
100 all_categories_enabled_.store(false);
101 }
102
103 // Log configuration for debugging
104 if (!enabled_categories_.empty() || !disabled_categories_.empty()) {
105 std::cerr << "Log filtering enabled:" << std::endl;
106 if (!enabled_categories_.empty()) {
107 std::string list;
108 for (const auto& c : enabled_categories_)
109 list += (list.empty() ? "" : ", ") + c;
110 std::cerr << " Allow: " << list << std::endl;
111 }
112 if (!disabled_categories_.empty()) {
113 std::string list;
114 for (const auto& c : disabled_categories_)
115 list += (list.empty() ? "" : ", ") + c;
116 std::cerr << " Block: " << list << std::endl;
117 }
118 }
119
120 // If a file path is provided, close any existing stream and open the new
121 // file.
122 if (!file_path.empty() && file_path != log_file_path_) {
123 if (log_stream_.is_open()) {
124 log_stream_.close();
125 }
126 // Open in append mode to preserve history, after rotating a large file.
127 RotateLogFileIfLarge(file_path);
128 log_stream_.open(file_path, std::ios::out | std::ios::app);
129 log_file_path_ = file_path;
130 } else if (file_path.empty() && log_stream_.is_open()) {
131 // An empty path means "log to stderr", so release the previous file
132 // instead of quietly continuing to write to it. Leaving it open also kept
133 // a lock on Windows, where an open file cannot be deleted.
134 log_stream_.close();
135 log_file_path_.clear();
136 }
137}
138
140 std::lock_guard<std::mutex> lock(LogWriteMutex());
141 if (log_stream_.is_open()) {
142 log_stream_.flush();
143 }
144}
145
146bool LogManager::ShouldLog(LogLevel level, absl::string_view category) const {
147 // 1. Filter by log level.
148 if (level < min_level_.load(std::memory_order_relaxed)) {
149 return false;
150 }
151
152 // Fast path: no category filtering configured.
153 if (all_categories_enabled_.load(std::memory_order_relaxed) &&
154 disabled_categories_.empty()) {
155 return true;
156 }
157
158 // 2. Filter by disabled categories (Blocklist).
159 if (disabled_categories_.find(category) != disabled_categories_.end()) {
160 return false;
161 }
162
163 // 3. Filter by enabled categories (Allowlist).
164 if (!all_categories_enabled_.load(std::memory_order_relaxed)) {
165 if (enabled_categories_.find(category) == enabled_categories_.end()) {
166 return false;
167 }
168 }
169
170 return true;
171}
172
173void LogManager::log(LogLevel level, absl::string_view category,
174 absl::string_view message) {
175 if (!ShouldLog(level, category)) {
176 return;
177 }
178
179 // 3. Format the complete log message.
180 // [HH:MM:SS.ms] [LEVEL] [category] message
181 auto now = std::chrono::system_clock::now();
182 auto now_tt = std::chrono::system_clock::to_time_t(now);
183 std::tm now_tm{};
184#ifdef _WIN32
185 localtime_s(&now_tm, &now_tt);
186#else
187 localtime_r(&now_tt, &now_tm);
188#endif
189 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
190 now.time_since_epoch()) %
191 1000;
192
193 std::string final_message = absl::StrFormat(
194 "[%02d:%02d:%02d.%03d] [%-5s] [%s] %s\n", now_tm.tm_hour, now_tm.tm_min,
195 now_tm.tm_sec, ms.count(), LogLevelToString(level), category, message);
196
197 // 4. Write to the configured sink (file and/or stderr). Flush warnings and
198 // errors at once; batch other lines and flush at most once a second, so
199 // chatty INFO logging does not cost a disk write per line.
200 {
201 std::lock_guard<std::mutex> lock(LogWriteMutex());
202 if (log_stream_.is_open()) {
203 log_stream_ << final_message;
204 static auto last_flush = std::chrono::steady_clock::now();
205 const auto steady_now = std::chrono::steady_clock::now();
206 if (level >= LogLevel::WARNING ||
207 steady_now - last_flush >= std::chrono::seconds(1)) {
208 log_stream_.flush();
209 last_flush = steady_now;
210 }
211 }
212
213 // Also write to stderr if no file is open OR if console logging is enabled
215 std::cerr << final_message;
216 }
217 }
218
219 // 5. Abort on FATAL error.
220 if (level == LogLevel::FATAL) {
221 std::abort();
222 }
223}
224
225} // namespace util
226} // namespace yaze
static Flags & get()
Definition features.h:119
A singleton that manages all logging configuration and output.
Definition log.h:34
std::ofstream log_stream_
Definition log.h:107
std::set< std::string, std::less<> > enabled_categories_
Definition log.h:102
static LogManager & instance()
Definition log.cc:66
void configure(LogLevel level, const std::string &file_path, const std::set< std::string > &categories)
Configures the logging system.
Definition log.cc:80
std::string log_file_path_
Definition log.h:108
std::set< std::string, std::less<> > disabled_categories_
Definition log.h:103
std::atomic< bool > all_categories_enabled_
Definition log.h:104
std::atomic< LogLevel > min_level_
Definition log.h:100
void Flush()
Writes buffered file output now. log() flushes warnings and errors at once but batches other lines fo...
Definition log.cc:139
bool ShouldLog(LogLevel level) const
Fast inline check if logging is enabled for the given level.
Definition log.h:71
void log(LogLevel level, absl::string_view category, absl::string_view message)
The primary logging function.
Definition log.cc:173
void RotateLogFileIfLarge(const std::string &path)
Definition log.cc:26
std::mutex & LogWriteMutex()
Definition log.cc:18
const char * LogLevelToString(yaze::util::LogLevel level)
Definition main.cc:181
LogLevel
Defines the severity levels for log messages. This allows for filtering messages based on their impor...
Definition log.h:24