oak - a modern logger 1.1.0
Loading...
Searching...
No Matches
oak.hpp
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#pragma once
7
8#include <unistd.h>
9#include <iostream>
10#include <vector>
11#include <thread>
12#include <mutex>
13#include <fstream>
14#include <queue>
15#include <condition_variable>
16#include <filesystem>
17#include <print>
18#include <memory>
19#include <functional>
20#include <expected>
21
22namespace oak
23{
24
25//
26// Macros
27//
28
29#define OAK_LOG(level, ...) oak::log2(level, __FILE__, __LINE__, __VA_ARGS__)
30#define OAK_DEBUG(...) OAK_LOG(oak::Level::Debug, __VA_ARGS__)
31#define OAK_INFO(...) OAK_LOG(oak::Level::Info, __VA_ARGS__)
32#define OAK_WARN(...) OAK_LOG(oak::Level::Warn, __VA_ARGS__)
33#define OAK_ERROR(...) OAK_LOG(oak::Level::Error, __VA_ARGS__)
34
35#define OAK_LOG2(logger, level, ...) logger->log2(level, __FILE__, __LINE__, __VA_ARGS__)
36#define OAK_DEBUG2(logger, ...) OAK_LOG2(logger, oak::Level::Debug, __VA_ARGS__)
37#define OAK_INFO2(logger, ...) OAK_LOG2(logger, oak::Level::Info, __VA_ARGS__)
38#define OAK_WARN2(logger, ...) OAK_LOG2(logger, oak::Level::Warn, __VA_ARGS__)
39#define OAK_ERROR2(logger, ...) OAK_LOG2(logger, oak::Level::Error, __VA_ARGS__)
40
41enum class Level
42{
43 Debug = 0,
44 Info = 1,
45 Warn = 2,
46 Error = 3,
47 Disabled = 4,
48 Default = Info,
49};
50
51std::string level_to_string(enum Level level);
52
53enum class Flags : unsigned int
54{
55 None = 0,
56 Level = 1,
57 Date = 1 << 1,
58 Time = 1 << 2,
59 Pid = 1 << 3,
60 Tid = 1 << 4,
61 Json = 1 << 5,
62 Color = 1 << 6,
63 File = 1 << 7,
64 Line = 1 << 8,
65 Default = Level,
66};
67
68//
69// Global logger
70//
71
72// Consumes events and logs and outputs them
73class Writer
74{
75public:
76
77 Writer() = default;
78 virtual ~Writer();
79
80 // Derived classes just have to implement this
81 // No locking is required here
82 virtual void write(const std::string& str) = 0;
83 // A name that identifies the writer
84 virtual std::string get_name() = 0;
85
86 void submit(const std::string &log);
87 void write_loop();
88 void stop();
89
90private:
91
92 bool should_stop = false;
93 std::queue<std::string> logs;
94 // Guards push / pop to the logs queue
95 std::mutex logs_mutex;
96 // This notification is activated when new logs arrive, and
97 // wakes up the consumer
98 std::condition_variable cv;
99
100};
101
102class FileWriter : public Writer
103{
104public:
105
106 static const std::string name; // set the name as static to make it
107 // easily accessible
108
109 FileWriter(const std::filesystem::path &path);
110
111 void write(const std::string& str) override;
112 std::string get_name() override;
113
114private:
115
116 std::ofstream out;
117
118};
119
120class StdoutWriter : public Writer
121{
122public:
123
124 static const std::string name;
125
126 StdoutWriter() = default;
127
128 void write(const std::string& str) override;
129 std::string get_name() override;
130
131};
132
133// Logger object
135{
136public:
137
138 using Formatter = std::function<std::string(enum Level, int flags,
139 const char* file,
140 int line,
141 const std::string& log)>;
142
143 Logger();
144 ~Logger() = default;
145
146 // Logging functions
147
148 template<typename... Args>
149 inline void debug(const char *fmt, Args &&...args);
150 template<typename... Args>
151 inline void debug2(const char* file, int line,
152 const char *fmt, Args &&...args);
153
154 template<typename... Args>
155 inline void info(const char *fmt, Args &&...args);
156 template<typename... Args>
157 inline void info2(const char* file, int line,
158 const char *fmt, Args &&...args);
159
160 template<typename... Args>
161 inline void warn(const char *fmt, Args &&...args);
162 template<typename... Args>
163 inline void warn2(const char* file, int line,
164 const char *fmt, Args &&...args);
165
166 template<typename... Args>
167 inline void error(const char *fmt, Args &&...args);
168 template<typename... Args>
169 inline void error2(const char* file, int line,
170 const char *fmt, Args &&...args);
171
172 template<typename... Args>
173 void log(enum Level l, const char *fmt, Args &&...args);
174 template<typename... Args>
175 void log2(enum Level level, const char* file, int line,
176 const char *fmt, Args &&...args);
177
178 template<typename W, typename ...Args>
179 void add_writer(Args &&...init_args);
180
181 // Returns false if the writer was not found
182 bool remove_writer(const std::string &name);
183
184 enum Level get_level() const;
185 void set_level(enum Level level);
186
187 unsigned int get_flags() const;
188 template<typename ...F>
189 void set_flags(F&&... flags);
190 template<typename ...F>
191 void add_flags(Flags flag, F&&... flags);
192 // Base case
193 inline void add_flags(Flags flag);
194
195 std::expected<int, std::string>
196 load_config_file(const std::filesystem::path& file);
197
198 void set_formatter(Formatter formatter);
199
200 // Event api
201
202 // Only enabled events will be logged
203 void enable_event(unsigned int id, const std::string& name);
204 void disable_event(unsigned int id);
205
206 template<typename... Args>
207 void event2(const char* file, int line, unsigned int id,
208 const char *fmt, Args &&...args);
209 template<typename... Args>
210 void event(unsigned int id, const char *fmt, Args &&...args);
211
212private:
213
214 static std::string colorize(enum Level level, const std::string &str);
215 static std::string json_formatter(enum Level level, int flags,
216 const char* file, int line,
217 const std::string& log);
218
219 enum Level level = Level::Info;
220 unsigned long int flags = (int)Flags::Default;
221 std::mutex logger_mutex;
222 std::unordered_map<unsigned int, std::string> events;
223
224 static const Formatter default_formatter;
225 Formatter formatter = default_formatter;
226 std::vector<std::shared_ptr<Writer>> writers;
227};
228
229//
230// Global logger
231//
232
234
235template<typename... Args>
236static inline void log2(enum oak::Level level, const char* file, int line, const char* fmt, Args &&...args);
237
238template<typename... Args>
239static inline void log(enum oak::Level level, const char* fmt, Args &&...args);
240
241template<typename W, typename ...Args>
242static inline void add_writer(Args &&...init_args);
243
244static inline bool remove_writer(const std::string &name);
245
246static inline enum Level get_level();
247static inline void set_level(enum Level level);
248
249static inline unsigned int get_flags();
250template<typename ...F>
251static inline void set_flags(F&&...flags);
252template<typename ...F>
253static inline void add_flags(F&&...flags);
254static inline std::expected<int, std::string>
255load_config_file(const std::filesystem::path& file);
256
257static inline void enable_event(unsigned int id,
258 const std::string& name);
259
260static inline void disable_event(unsigned int id);
261
262#include "oak.impl"
263
264} // namespace oak
265
266template <> struct std::formatter<oak::Level>
267{
268 constexpr auto parse(format_parse_context &ctx)
269 {
270 return ctx.begin();
271 }
272
273 template <typename FormatContext>
274 auto format(const oak::Level &level, FormatContext &ctx) const
275 {
276 switch (level)
277 {
279 return format_to(ctx.out(), "error");
280 case oak::Level::Warn:
281 return format_to(ctx.out(), "warn");
282 case oak::Level::Info:
283 return format_to(ctx.out(), "info");
285 return format_to(ctx.out(), "debug");
286 default:
287 return format_to(ctx.out(), "unknown");
288 }
289 }
290};
291
292template <> struct std::formatter<oak::Flags>
293{
294 constexpr auto parse(format_parse_context &ctx)
295 {
296 return ctx.begin();
297 }
298
299 template <typename FormatContext>
300 auto format(const oak::Flags &flags, FormatContext &ctx) const
301 {
302 switch (flags)
303 {
304 case oak::Flags::None:
305 return format_to(ctx.out(), "none");
307 return format_to(ctx.out(), "level");
308 case oak::Flags::Date:
309 return format_to(ctx.out(), "date");
310 case oak::Flags::Time:
311 return format_to(ctx.out(), "time");
312 case oak::Flags::Tid:
313 return format_to(ctx.out(), "pid");
314 case oak::Flags::Tid:
315 return format_to(ctx.out(), "tid");
316 default:
317 return format_to(ctx.out(), "unknown");
318 }
319 }
320};
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
void event(unsigned int id, const char *fmt, Args &&...args)
void debug(const char *fmt, Args &&...args)
std::function< std::string(enum Level, int flags, const char *file, int line, const std::string &log)> Formatter
Definition oak.hpp:141
void error(const char *fmt, Args &&...args)
void info2(const char *file, int line, const char *fmt, Args &&...args)
void event2(const char *file, int line, unsigned int id, const char *fmt, Args &&...args)
bool remove_writer(const std::string &name)
Definition oak.cpp:127
void warn(const char *fmt, Args &&...args)
void add_flags(Flags flag, F &&... flags)
void debug2(const char *file, int line, const char *fmt, Args &&...args)
void error2(const char *file, int line, const char *fmt, Args &&...args)
void set_formatter(Formatter formatter)
Definition oak.cpp:154
void add_flags(Flags flag)
void info(const char *fmt, Args &&...args)
~Logger()=default
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 log2(enum Level level, const char *file, int line, const char *fmt, Args &&...args)
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)
void warn2(const char *file, int line, const char *fmt, Args &&...args)
StdoutWriter()=default
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
Writer()=default
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
virtual std::string get_name()=0
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 std::expected< int, std::string > load_config_file(const std::filesystem::path &file)
static void add_writer(Args &&...init_args)
static void log2(enum oak::Level level, const char *file, int line, const char *fmt, Args &&...args)
Level
Definition oak.hpp:42
static void disable_event(unsigned int id)
static void log(enum oak::Level level, const char *fmt, Args &&...args)
Flags
Definition oak.hpp:54
static void add_flags(F &&...flags)
static unsigned int get_flags()
static void enable_event(unsigned int id, const std::string &name)
static void set_flags(F &&...flags)
static enum Level get_level()
static bool remove_writer(const std::string &name)
static void set_level(enum Level level)
constexpr auto parse(format_parse_context &ctx)
Definition oak.hpp:294
auto format(const oak::Flags &flags, FormatContext &ctx) const
Definition oak.hpp:300
constexpr auto parse(format_parse_context &ctx)
Definition oak.hpp:268
auto format(const oak::Level &level, FormatContext &ctx) const
Definition oak.hpp:274