oak - a modern logger 1.1.0
Loading...
Searching...
No Matches
oak.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2// Author: Giovanni Santini
3// Mail: giovanni.santini@proton.me
4// Github: @San7o
5
6#include <oak/oak.hpp>
7
8using namespace oak;
9
10std::string oak::level_to_string(enum Level level)
11{
12 switch (level)
13 {
14 case oak::Level::Debug: return "DEBUG";
15 case oak::Level::Info: return "INFO ";
16 case oak::Level::Warn: return "WARN ";
17 case oak::Level::Error: return "ERROR";
18 case oak::Level::Disabled: return "DISABLED";
19 default: return "UNKNOWN";
20 }
21}
22
23
24void Writer::submit(const std::string &log)
25{
26 {
27 std::lock_guard<std::mutex> lock(this->logs_mutex);
28 this->logs.push(log);
29 }
30 this->cv.notify_one(); // unlock the consumer
31}
32
34{
35 this->stop();
36}
37
39{
40 while (1)
41 {
42 std::queue<std::string> local_logs = {};
43 {
44 std::unique_lock<std::mutex> lock(this->logs_mutex);
45
46 this->cv.wait(lock, [this] {
47 // When this evaluates to true, this block stops waiting
48 // Note that this is checked before waiting in the
49 // first place, and is checked every time a notification
50 // is sent.
51 return !this->logs.empty() || this->should_stop;
52 });
53
54 if (this->should_stop && this->logs.empty()) return;
55
56 std::swap(local_logs, this->logs); // instant swap
57 } // logs_mutex released
58
59 while(!local_logs.empty())
60 {
61 this->write(local_logs.front());
62 local_logs.pop();
63 }
64 }
65}
66
68{
69 {
70 std::lock_guard<std::mutex> lock(this->logs_mutex);
71 this->should_stop = true;
72 }
73 this->cv.notify_all(); // Wake up the consumer so it sees should_stop is true
74 return;
75}
76
77FileWriter::FileWriter(const std::filesystem::path &path)
78{
79 auto file = std::ofstream(path);
80 if (!file.is_open())
81 {
82 std::print("[ERROR] [oak] Error creating writer for file {}", path.c_str());
83 return;
84 }
85
86 this->out = std::move(file);
87}
88
89void FileWriter::write(const std::string& str)
90{
91 if (this->out.is_open())
92 this->out << str;
93 return;
94}
95
96const std::string FileWriter::name = "file_writer";
97
99{
100 return FileWriter::name;
101}
102
103void StdoutWriter::write(const std::string& str)
104{
105 std::print("{}", str);
106 return;
107}
108
109const std::string StdoutWriter::name = "stdout_writer";
110
112{
113 return StdoutWriter::name;
114}
115
117{
118 auto writer = std::make_shared<StdoutWriter>();
119 this->writers.push_back(writer);
120
121 std::jthread t([writer] { writer->write_loop(); });
122 t.detach();
123
124 OAK_INFO2(this, "[ OAK ] Initialized writer {}", writer->get_name());
125}
126
127bool Logger::remove_writer(const std::string &name)
128{
129 std::lock_guard<std::mutex> lock(this->logger_mutex);
130 for (auto it = this->writers.begin(); it != this->writers.end(); ++it)
131 {
132 if ((*it)->get_name() == name)
133 {
134 this->writers.erase(it);
135 OAK_INFO2(this, "Removed writer {}", name);
136 return true;
137 }
138 }
139 return false;
140}
141
143{
144 return this->level;
145}
146
147void Logger::set_level(enum Level level)
148{
149 std::lock_guard<std::mutex> lock(this->logger_mutex);
150 this->level = level;
151 return;
152}
153
155{
156 std::lock_guard<std::mutex> lock(this->logger_mutex);
157 this->formatter = formatter;
158 return;
159}
160
161unsigned int Logger::get_flags() const
162{
163 return this->flags;
164}
165
166// Colors
167
168// Foregound
169#define RST "\x1B[0m"
170#define KRED "\x1B[31m"
171#define KGRN "\x1B[32m"
172#define KYEL "\x1B[33m"
173#define KBLU "\x1B[34m"
174#define KMAG "\x1B[35m"
175#define KCYN "\x1B[36m"
176#define KWHT "\x1B[37m"
177
178// for string literals
179#define FRED(x) KRED x RST
180#define FGRN(x) KGRN x RST
181#define FYEL(x) KYEL x RST
182#define FBLU(x) KBLU x RST
183#define FMAG(x) KMAG x RST
184#define FCYN(x) KCYN x RST
185#define FWHT(x) KWHT x RST
186
187#define FRED_S(x) std::string(KRED) + x + std::string(RST)
188#define FGRN_S(x) std::string(KGRN) + x + std::string(RST)
189#define FYEL_S(x) std::string(KYEL) + x + std::string(RST)
190#define FBLU_S(x) std::string(KBLU) + x + std::string(RST)
191#define FMAG_S(x) std::string(KMAG) + x + std::string(RST)
192#define FCYN_S(x) std::string(KCYN) + x + std::string(RST)
193#define FWHT_S(x) std::string(KWHT) + x + std::string(RST)
194
195std::string Logger::colorize(enum Level level, const std::string &str)
196{
197 switch (level)
198 {
199 case Level::Debug:
200 return FCYN_S(str);
201 case Level::Info:
202 return FBLU_S(str);
203 case Level::Warn:
204 return FYEL_S(str);
205 case Level::Error:
206 return FRED_S(str);
207 default:
208 return str;
209 }
210}
211
213{
214 static Logger instance;
215 return instance;
216}
217
218std::expected<int, std::string>
219Logger::load_config_file(const std::filesystem::path& file)
220{
221 if (!std::filesystem::exists(file))
222 {
223 return std::unexpected("Settings file does not exist");
224 }
225
226 std::ifstream settings(file);
227 while (!settings.eof())
228 {
229 std::string line;
230 std::getline(settings, line);
231 if (line.size() == 0)
232 continue;
233
234 std::string key = line.substr(0, line.find('='));
235 std::string value = line.substr(line.find('=') + 1);
236
237 key.erase(std::remove_if(key.begin(), key.end(), isspace), key.end());
238 value.erase(std::remove_if(value.begin(), value.end(), isspace),
239 value.end());
240
241 if (key == "level")
242 {
243 if (value == "debug")
245 else if (value == "info")
247 else if (value == "warn")
249 else if (value == "error")
251 else
252 return std::unexpected("Invalid log level in file");
253 }
254 else if (key == "flags")
255 {
257 while (value.find(',') != std::string::npos)
258 {
259 std::string flag = value.substr(0, value.find(','));
260 value = value.substr(value.find(',') + 1);
261 if (flag == "none")
263 else if (flag == "level")
265 else if (flag == "date")
267 else if (flag == "time")
269 else if (flag == "pid")
271 else if (flag == "tid")
273 else if (flag == "json")
275 else
276 return std::unexpected("Invalid flags in file");
277 }
278 // get last element
279 if (value == "none")
281 else if (value == "level")
283 else if (value == "date")
285 else if (value == "time")
287 else if (value == "pid")
289 else if (value == "tid")
291 else if (value == "json")
293 else
294 return std::unexpected("Invalid flags in file");
295 }
296 else if (key == "file")
297 {
299 }
300 else
301 {
302 return std::unexpected("Invalid key in file");
303 }
304 }
305
306 return 0;
307}
308
309
310
311void Logger::enable_event(unsigned int id, const std::string &name)
312{
313 std::lock_guard<std::mutex> lock(this->logger_mutex);
314 this->events[id] = name;
315}
316
317void Logger::disable_event(unsigned int id)
318{
319 std::lock_guard<std::mutex> lock(this->logger_mutex);
320 this->events.erase(id);
321}
322
323std::string Logger::json_formatter(enum Level level, int flags,
324 const char* file, int line,
325 const std::string& log)
326{
327 std::string output;
328 bool do_color = false;
329
330 if (flags & (unsigned int) Flags::Color) do_color = true;
331
332 output += "{ ";
333
334 if (flags & (unsigned int) Flags::Level)
335 {
336 output += "\"level\": \"" + level_to_string(level) + "\", ";
337 }
338 if (flags & (unsigned int) Flags::Date)
339 {
340 auto now = std::chrono::system_clock::now();
341 auto now_time_t = std::chrono::system_clock::to_time_t(now);
342 std::tm now_tm = *std::localtime(&now_time_t);
343 std::ostringstream oss;
344 oss << "\"date\": \"" << std::put_time(&now_tm, "%Y-%m-%d") << "\", ";
345 output += oss.str();
346 }
347 if (flags & (unsigned int) Flags::Time)
348 {
349 auto now = std::chrono::system_clock::now();
350 auto now_time_t = std::chrono::system_clock::to_time_t(now);
351 std::tm now_tm = *std::localtime(&now_time_t);
352 std::ostringstream oss;
353 oss << "\"time\": \"" << std::put_time(&now_tm, "%H:%M:%S") << "\", ";
354 output += oss.str();
355 }
356 if (flags & (unsigned int) Flags::Pid)
357 {
358 std::string pid = std::to_string(getpid());
359 output += "\"pid\": " + pid + ", ";
360 }
361 if (flags & (unsigned int) Flags::Tid)
362 {
363 std::ostringstream oss;
364 oss << std::this_thread::get_id();
365 std::string tid = oss.str();
366 output += "\"tid\": " + tid + ", ";
367 }
368 if (flags & (unsigned int) Flags::File)
369 {
370 output += "\"file\": \"" + std::string(file) + "\", ";
371 }
372
373 if (flags & (unsigned int) Flags::Line)
374 {
375 output += "\"line\": " + std::to_string(line) + ", ";
376 }
377
378 output += "\"log\": \"";
379 output += log;
380
381 output += "\" }";
382 output += '\n';
383
384 if (do_color)
385 return Logger::colorize(level, output);
386 return output;
387}
388
389const Logger::Formatter Logger::default_formatter =
390 [](enum Level level, int flags,
391 const char* file,
392 int line,
393 const std::string& log)
394{
395 bool json = false;
396 bool do_color = false;
397 if (flags & (unsigned int) Flags::Json) json = true;
398 if (json)
399 return json_formatter(level, flags, file, line, log);
400
401 std::string output;
402
403 if (flags & (unsigned int) Flags::Color) do_color = true;
404
405 if (flags & (unsigned int) Flags::Level)
406 {
407 output += level_to_string(level) + " ";
408 }
409 if (flags & (unsigned int) Flags::Date)
410 {
411 auto now = std::chrono::system_clock::now();
412 auto now_time_t = std::chrono::system_clock::to_time_t(now);
413 std::tm now_tm = *std::localtime(&now_time_t);
414 std::ostringstream oss;
415 oss << std::put_time(&now_tm, "%Y-%m-%d") << " ";
416 output += oss.str();
417 }
418 if (flags & (unsigned int) Flags::Time)
419 {
420 auto now = std::chrono::system_clock::now();
421 auto now_time_t = std::chrono::system_clock::to_time_t(now);
422 std::tm now_tm = *std::localtime(&now_time_t);
423 std::ostringstream oss;
424 oss << std::put_time(&now_tm, "%H:%M:%S") << " ";
425 output += oss.str();
426 }
427 if (flags & (unsigned int) Flags::Pid)
428 {
429 std::string pid = std::to_string(getpid());
430 output += pid + " ";
431 }
432 if (flags & (unsigned int) Flags::Tid)
433 {
434 std::ostringstream oss;
435 oss << std::this_thread::get_id();
436 std::string tid = oss.str();
437 output += tid + " ";
438 }
439 if (flags & (unsigned int) Flags::File)
440 {
441 output += std::string(file) + " ";
442 }
443
444 if (flags & (unsigned int) Flags::Line)
445 {
446 output += std::to_string(line) + " ";
447 }
448
449 output += "| ";
450 output += log;
451
452 output += '\n';
453
454 if (do_color)
455 return colorize(level, output);
456 return output;
457};
FileWriter(const std::filesystem::path &path)
Definition oak.cpp:77
std::string get_name() override
Definition oak.cpp:98
static const std::string name
Definition oak.hpp:106
void write(const std::string &str) override
Definition oak.cpp:89
void enable_event(unsigned int id, const std::string &name)
Definition oak.cpp:311
std::expected< int, std::string > load_config_file(const std::filesystem::path &file)
Definition oak.cpp:219
std::function< std::string(enum Level, int flags, const char *file, int line, const std::string &log)> Formatter
Definition oak.hpp:141
bool remove_writer(const std::string &name)
Definition oak.cpp:127
void add_flags(Flags flag, F &&... flags)
void set_formatter(Formatter formatter)
Definition oak.cpp:154
void disable_event(unsigned int id)
Definition oak.cpp:317
unsigned int get_flags() const
Definition oak.cpp:161
void set_level(enum Level level)
Definition oak.cpp:147
void add_writer(Args &&...init_args)
void log(enum Level l, const char *fmt, Args &&...args)
enum Level get_level() const
Definition oak.cpp:142
void set_flags(F &&... flags)
static const std::string name
Definition oak.hpp:124
void write(const std::string &str) override
Definition oak.cpp:103
std::string get_name() override
Definition oak.cpp:111
virtual ~Writer()
Definition oak.cpp:33
virtual void write(const std::string &str)=0
void stop()
Definition oak.cpp:67
void write_loop()
Definition oak.cpp:38
void submit(const std::string &log)
Definition oak.cpp:24
Definition oak.hpp:23
Logger & get_global()
Definition oak.cpp:212
std::string level_to_string(enum Level level)
Definition oak.cpp:10
static void add_writer(Args &&...init_args)
Level
Definition oak.hpp:42
static void log(enum oak::Level level, const char *fmt, Args &&...args)
#define FCYN_S(x)
Definition oak.cpp:192
#define FYEL_S(x)
Definition oak.cpp:189
#define FRED_S(x)
Definition oak.cpp:187
#define FBLU_S(x)
Definition oak.cpp:190
#define OAK_INFO2(logger,...)
Definition oak.hpp:37