oak - a modern logger 1.0
Loading...
Searching...
No Matches
oak.hpp
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) 2024 Giovanni Santini
5
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 all
15 * copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 */
26
27#pragma once
28
29#include <atomic>
30#include <chrono>
31#include <condition_variable>
32#include <ctime>
33#include <deque>
34#include <expected>
35#include <filesystem>
36#include <format>
37#include <fstream>
38#include <future>
39#include <iomanip>
40#include <iostream>
41#include <ostream>
42#include <string>
43#include <thread>
44#include <unistd.h>
45
46#ifdef OAK_USE_SOCKETS
47#include <cstring>
48#include <netinet/ip.h>
49#include <unistd.h>
50#ifdef __unix__
51#include <arpa/inet.h>
52#include <sys/socket.h>
53#include <sys/un.h>
54#endif
55#ifdef _WIN32
56#include <winsock2.h>
57#endif
58#endif
59
60#define OAK_DEBUG(...) oak::log(oak::level::debug, __VA_ARGS__)
61#define OAK_INFO(...) oak::log(oak::level::info, __VA_ARGS__)
62#define OAK_WARN(...) oak::log(oak::level::warn, __VA_ARGS__)
63#define OAK_ERROR(...) oak::log(oak::level::error, __VA_ARGS__)
64#define OAK_OUTPUT(...) oak::log(oak::level::output, __VA_ARGS__)
65
66namespace oak
67{
68
69enum class level
70{
71 debug = 0,
72 info,
73 warn,
74 error,
75 output,
78};
79
80enum class protocol_t
81{
82 tcp = 0,
83 udp,
85};
86
87enum class flags
88{
89 none = 0,
90 level = 1,
91 date = 2,
92 time = 4,
93 pid = 8,
94 tid = 16,
95 json = 32
96};
97
98enum class destination
99{
100 std_out = 0,
101 file,
102 socket,
104};
105
107{
108 std::string message;
110 inline queue_element(const std::string &msg, const oak::destination &d)
111 : message(std::move(msg)), dest(d)
112 {
113 }
114};
115
116struct logger
117{
118 static long unsigned int flag_bits;
120 static std::ofstream log_file;
121 static std::deque<queue_element> log_queue;
122 static std::mutex log_mutex;
123 static std::condition_variable log_cv;
124 static std::atomic<bool> close_writer;
125 static std::optional<std::jthread> writer_thread;
126#ifdef OAK_USE_SOCKETS
127 static int log_socket;
128#endif
129};
130
132{
133 std::lock_guard<std::mutex> lock(logger::log_mutex);
134 return logger::log_level;
135}
136
137inline long unsigned int get_flags()
138{
139 std::lock_guard<std::mutex> lock(logger::log_mutex);
140 return logger::flag_bits;
141}
142
143inline bool is_file_open()
144{
145 std::lock_guard<std::mutex> lock(logger::log_mutex);
146 return logger::log_file.is_open();
147}
148
149inline void set_level(const oak::level &lvl)
150{
151 std::lock_guard<std::mutex> lock(logger::log_mutex);
152 logger::log_level = lvl;
153}
154
155[[nodiscard]]
156std::expected<int, std::string> set_file(const std::string &file);
158
159#ifdef OAK_USE_SOCKETS
160void close_socket();
161#endif
162
163void add_to_queue(const std::string &str, const destination &d);
164
165template <typename... Args> void add_flags(flags flg, Args &&...args)
166{
167 {
168 std::lock_guard<std::mutex> lock(logger::log_mutex);
169 logger::flag_bits |= static_cast<long unsigned int>(flg);
170 }
171 if (sizeof...(args) > 0)
172 {
173 add_flags(args...);
174 }
175}
176
177// Base case
178template <typename... Args> void add_flags(flags flg)
179{
180 {
181 std::lock_guard<std::mutex> lock(logger::log_mutex);
182 logger::flag_bits |= static_cast<long unsigned int>(flg);
183 }
184}
185
186template <typename... Args> void set_flags(flags flg, Args &&...args)
187{
188 {
189 std::lock_guard<std::mutex> lock(logger::log_mutex);
191 }
192 add_flags(flg, args...);
193}
194
195void writer();
198
199[[nodiscard]] std::expected<int, std::string> settings_file(
200 const std::string &file);
201
202template <typename... Args>
203std::string constexpr log_to_string(const level &lvl, const std::string &fmt,
204 Args &&...args)
205{
206 auto flags = get_flags();
207 bool json = false;
208 if (flags & static_cast<long unsigned int>(flags::json))
209 json = true;
210 std::string prefix = "";
211 if (flags > 0 && !json)
212 {
213 prefix += "[ ";
214 }
215 if (json)
216 {
217 prefix += "{ ";
218 }
219 if (flags & static_cast<long unsigned int>(flags::level))
220 {
221 if (json)
222 prefix +=
223 std::vformat("\"level\": \"{}\"", std::make_format_args(lvl));
224 else
225 prefix += std::vformat("level={} ", std::make_format_args(lvl));
226 }
227 if (flags & static_cast<long unsigned int>(flags::date))
228 {
229 auto now = std::chrono::system_clock::now();
230 auto now_time_t = std::chrono::system_clock::to_time_t(now);
231 std::tm now_tm = *std::localtime(&now_time_t);
232 std::ostringstream oss;
233 if (json)
234 oss << ", \"date\": \"" << std::put_time(&now_tm, "%Y-%m-%d")
235 << "\"";
236 else
237 oss << "date=" << std::put_time(&now_tm, "%Y-%m-%d") << " ";
238 prefix += oss.str();
239 }
240 if (flags & static_cast<long unsigned int>(flags::time))
241 {
242 auto now = std::chrono::system_clock::now();
243 auto now_time_t = std::chrono::system_clock::to_time_t(now);
244 std::tm now_tm = *std::localtime(&now_time_t);
245 std::ostringstream oss;
246 if (json)
247 oss << ", \"time\": \"" << std::put_time(&now_tm, "%H:%M:%S")
248 << "\"";
249 else
250 oss << "time=" << std::put_time(&now_tm, "%H:%M:%S") << " ";
251 prefix += oss.str();
252 }
253 if (flags & static_cast<long unsigned int>(flags::pid))
254 {
255 std::string pid = std::to_string(getpid());
256 if (json)
257 prefix += ", \"pid\": " + pid;
258 else
259 prefix += std::vformat("pid={} ", std::make_format_args(pid));
260 }
261 if (flags & static_cast<long unsigned int>(flags::tid))
262 {
263 std::ostringstream oss;
264 oss << std::this_thread::get_id();
265 std::string tid = oss.str();
266 if (json)
267 prefix += ", \"tid\": " + tid;
268 else
269 prefix += std::vformat("tid={} ", std::make_format_args(tid));
270 }
271
272 if (flags - static_cast<long unsigned int>(flags::json) > 0 && !json)
273 prefix += "] ";
274 if (flags - static_cast<long unsigned int>(flags::json) > 0 && json)
275 prefix += ", ";
276 try {
277 std::string formatted_string =
278 std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
279 if (json)
280 return std::vformat("{}\"message\": \"{}\" }}\n",
281 std::make_format_args(prefix, formatted_string));
282 return std::vformat("{}{}\n",
283 std::make_format_args(prefix, formatted_string));
284 } catch (const std::exception &e) {
285 return "";
286 }
287}
288
289template <typename... Args>
290void log_to_stdout(const level &lvl, const std::string &fmt, Args &&...args)
291{
292 if (get_level() > lvl)
293 return;
294 std::string message = log_to_string(lvl, fmt, args...);
296}
297
298inline void log_to_stdout(const std::string &str)
299{
301}
302
303template <typename... Args>
304void log_to_file(const level &lvl, const std::string &fmt, Args &&...args)
305{
306 if (get_level() > lvl && !is_file_open())
307 return;
308 std::string message = log_to_string(lvl, fmt, args...);
310}
311
312void log_to_file(const std::string &str);
313
314#ifdef OAK_USE_SOCKETS
315template <typename... Args>
316void log_to_socket(const level &lvl, const std::string &fmt, Args &&...args)
317{
318 if (get_level() > lvl || logger::log_socket < 0)
319 return;
320 std::string formatted_string = log_to_string(lvl, fmt, args...);
321 add_to_queue(formatted_string, oak::destination::socket);
322}
323
324void log_to_socket(const std::string &str);
325#endif
326
327template <typename... Args>
328void log(const level &lvl, const std::string &fmt, Args &&...args)
329{
330 if (get_level() > lvl)
331 return;
332 std::string formatted_string = log_to_string(lvl, fmt, args...);
333 log_to_stdout(formatted_string);
334 if (is_file_open())
335 log_to_file(formatted_string);
336#ifdef OAK_USE_SOCKETS
337 if (logger::log_socket > 0)
338 log_to_socket(formatted_string);
339#endif
340}
341
342#ifdef OAK_USE_SOCKETS
343#ifdef __unix__
344[[nodiscard]] std::expected<int, std::string>
345set_socket(const std::string &sock_addr);
346
347[[nodiscard]] std::expected<int, std::string>
348set_socket(const std::string &addr, short unsigned int port,
349 const protocol_t &protocol = protocol_t::tcp);
350#endif
351#endif
352
353template <typename... Args>
354inline void out(const std::string &fmt, Args &&...args)
355{
356 log(oak::level::output, fmt, args...);
357}
358
359template <typename... Args>
360inline void debug(const std::string &fmt, Args &&...args)
361{
362 log(oak::level::debug, fmt, args...);
363}
364
365template <typename... Args>
366inline void info(const std::string &fmt, Args &&...args)
367{
368 log(oak::level::info, fmt, args...);
369}
370
371template <typename... Args>
372inline void warn(const std::string &fmt, Args &&...args)
373{
374 log(oak::level::warn, fmt, args...);
375}
376
377template <typename... Args>
378inline void error(const std::string &fmt, Args &&...args)
379{
380 log(oak::level::error, fmt, args...);
381}
382
383template <typename... Args>
384inline void output(const std::string &fmt, Args &&...args)
385{
386 log(oak::level::output, fmt, args...);
387}
388
389template <typename... Args>
390inline void async(const level &lvl, const std::string &fmt, Args &&...args)
391{
392 (void) std::async([lvl, fmt, args...]() { log(lvl, fmt, args...); });
393}
394
395void flush();
396
397} // namespace oak
398
399template <> struct std::formatter<oak::level>
400{
401 constexpr auto parse(format_parse_context &ctx)
402 {
403 return ctx.begin();
404 }
405
406 template <typename FormatContext>
407 auto format(const oak::level &level, FormatContext &ctx) const
408 {
409 switch (level)
410 {
412 return format_to(ctx.out(), "error");
413 case oak::level::warn:
414 return format_to(ctx.out(), "warn");
415 case oak::level::info:
416 return format_to(ctx.out(), "info");
418 return format_to(ctx.out(), "debug");
420 return format_to(ctx.out(), "output");
421 default:
422 return format_to(ctx.out(), "unknown");
423 }
424 }
425};
426
427template <> struct std::formatter<oak::flags>
428{
429 constexpr auto parse(format_parse_context &ctx)
430 {
431 return ctx.begin();
432 }
433
434 template <typename FormatContext>
435 auto format(const oak::flags &flags, FormatContext &ctx) const
436 {
437 switch (flags)
438 {
439 case oak::flags::none:
440 return format_to(ctx.out(), "none");
442 return format_to(ctx.out(), "level");
443 case oak::flags::date:
444 return format_to(ctx.out(), "date");
445 case oak::flags::time:
446 return format_to(ctx.out(), "time");
447 case oak::flags::pid:
448 return format_to(ctx.out(), "pid");
449 case oak::flags::tid:
450 return format_to(ctx.out(), "tid");
451 default:
452 return format_to(ctx.out(), "unknown");
453 }
454 }
455};
Definition oak.hpp:67
void flush()
protocol_t
Definition oak.hpp:81
void add_to_queue(const std::string &str, const destination &d)
void out(const std::string &fmt, Args &&...args)
Definition oak.hpp:354
flags
Definition oak.hpp:88
void init_writer()
void log_to_stdout(const level &lvl, const std::string &fmt, Args &&...args)
Definition oak.hpp:290
void writer()
void close_file()
void log_to_file(const level &lvl, const std::string &fmt, Args &&...args)
Definition oak.hpp:304
void stop_writer()
destination
Definition oak.hpp:99
std::expected< int, std::string > set_file(const std::string &file)
std::expected< int, std::string > settings_file(const std::string &file)
void add_flags(flags flg, Args &&...args)
Definition oak.hpp:165
bool is_file_open()
Definition oak.hpp:143
void set_flags(flags flg, Args &&...args)
Definition oak.hpp:186
void set_level(const oak::level &lvl)
Definition oak.hpp:149
std::string constexpr log_to_string(const level &lvl, const std::string &fmt, Args &&...args)
Definition oak.hpp:203
level get_level()
Definition oak.hpp:131
void async(const level &lvl, const std::string &fmt, Args &&...args)
Definition oak.hpp:390
level
Definition oak.hpp:70
void log(const level &lvl, const std::string &fmt, Args &&...args)
Definition oak.hpp:328
long unsigned int get_flags()
Definition oak.hpp:137
static std::ofstream log_file
Definition oak.hpp:120
static std::condition_variable log_cv
Definition oak.hpp:123
static level log_level
Definition oak.hpp:119
static std::deque< queue_element > log_queue
Definition oak.hpp:121
static std::optional< std::jthread > writer_thread
Definition oak.hpp:125
static long unsigned int flag_bits
Definition oak.hpp:118
static std::mutex log_mutex
Definition oak.hpp:122
static std::atomic< bool > close_writer
Definition oak.hpp:124
std::string message
Definition oak.hpp:108
queue_element(const std::string &msg, const oak::destination &d)
Definition oak.hpp:110
oak::destination dest
Definition oak.hpp:109
constexpr auto parse(format_parse_context &ctx)
Definition oak.hpp:429
auto format(const oak::flags &flags, FormatContext &ctx) const
Definition oak.hpp:435
auto format(const oak::level &level, FormatContext &ctx) const
Definition oak.hpp:407
constexpr auto parse(format_parse_context &ctx)
Definition oak.hpp:401