Branch data Line data Source code
1 : : /*
2 : : ** Copyright (C) 2020 Sylvain Fargier
3 : : **
4 : : ** This software is provided 'as-is', without any express or implied
5 : : ** warranty. In no event will the authors be held liable for any damages
6 : : ** arising from the use of this software.
7 : : **
8 : : ** Permission is granted to anyone to use this software for any purpose,
9 : : ** including commercial applications, and to alter it and redistribute it
10 : : ** freely, subject to the following restrictions:
11 : : **
12 : : ** 1. The origin of this software must not be misrepresented; you must not
13 : : ** claim that you wrote the original software. If you use this software
14 : : ** in a product, an acknowledgment in the product documentation would be
15 : : ** appreciated but is not required.
16 : : ** 2. Altered source versions must be plainly marked as such, and must not be
17 : : ** misrepresented as being the original software.
18 : : ** 3. This notice may not be removed or altered from any source distribution.
19 : : **
20 : : ** Created on: 2021-12-30T17:07:25+01:00
21 : : ** Author: Sylvain Fargier <fargie_s> <fargier.sylvain@gmail.com>
22 : : **
23 : : */
24 : :
25 : : #ifndef LOGGER_HPP__
26 : : #define LOGGER_HPP__
27 : :
28 : : #include <chrono>
29 : : #include <cstdint>
30 : : #include <ctime>
31 : : #include <functional>
32 : : #include <ios>
33 : : #include <memory>
34 : : #include <sstream>
35 : : #include <string>
36 : : #include <tuple>
37 : : #include <utility>
38 : :
39 : : namespace logger {
40 : :
41 : : class Logger;
42 : : class NullLogger;
43 : :
44 : : /**
45 : : * @brief available log levels
46 : : * @details enabling a level also enables all lower levels
47 : : */
48 : : enum class Level : uint8_t
49 : : {
50 : : EMERG,
51 : : ALERT,
52 : : CRIT,
53 : : ERROR,
54 : : WARNING,
55 : : NOTICE,
56 : : INFO,
57 : : DEBUG
58 : : };
59 : :
60 : : /**
61 : : * @brief logger features
62 : : */
63 : : enum class Feature : uint8_t
64 : : {
65 : : DATETIME = 0x01, /**< display dateTime prefix */
66 : : TIMEDELTA = 0x02, /**< display thread-based elapsed time since last log */
67 : : LEVEL = 0x04, /**< display level info */
68 : : CATEGORY = 0x08, /**< display category info */
69 : : SYSLOG = 0x10,
70 : : NOCOLOR = 0x20, /**< disable colored output (default: auto) */
71 : : THREAD = 0x40 /**< display current thread id (id is generated by logger) */
72 : : };
73 : :
74 : : /* UTILITIES */
75 : :
76 : : /**
77 : : * @brief utility class to log a time_point
78 : : */
79 : : class LocalTime
80 : : {
81 : : public:
82 : : typedef std::chrono::time_point<std::chrono::system_clock> time_point;
83 : :
84 : : explicit LocalTime(const time_point &time);
85 : : LocalTime();
86 : :
87 : : const time_point time; /**< associated time-point */
88 : : };
89 : : std::string to_string(const LocalTime &time);
90 : :
91 : : /**
92 : : * @brief utility class to log elapsed time
93 : : */
94 : : class Elapsed
95 : : {
96 : : public:
97 : : Elapsed();
98 : :
99 : : /**
100 : : * @brief ticking method
101 : : * @details call this method forwarding its output to the logger to
102 : : * tick the timer
103 : : */
104 : : std::chrono::milliseconds tick();
105 : :
106 : : std::chrono::time_point<std::chrono::steady_clock> start; /**< timer start
107 : : point */
108 : : };
109 : :
110 : : /**
111 : : * @brief basic array iterator
112 : : * @details also works on containers (to display part)
113 : : * @details use iterator function to instantiate it
114 : : * @details this class requires a LegacyForwardIterator
115 : : */
116 : : template<typename T>
117 : : class Iterator
118 : : {
119 : : public:
120 : : typedef typename std::decay<const T>::type Iter;
121 : :
122 : 4 : constexpr Iterator(const Iter &begin, const Iter &end) :
123 : 4 : _begin(begin), _end(end)
124 : 4 : {}
125 : :
126 : 4 : inline const Iter &begin() const { return _begin; }
127 : 4 : inline const Iter &end() const { return _end; }
128 : :
129 : : const Iter _begin;
130 : : const Iter _end;
131 : : };
132 : :
133 : : /**
134 : : * @brief array iterator function
135 : : * @details transforms basic array in range-iterable ones
136 : : * @details this function requires a LegacyRandomAccessIterator
137 : : * @param[in] begin start of array pointer or container iterator
138 : : * @param[in] len number of elements to display
139 : : */
140 : : template<typename T>
141 : 1 : Iterator<T> iterator(const T &begin, std::size_t len)
142 : : {
143 : 1 : return Iterator<T>(begin, begin + len);
144 : : }
145 : :
146 : : /**
147 : : * @brief array iterator function
148 : : * @details transforms basic array in range-iterable ones
149 : : * @param[in] begin start of array pointer or iterator
150 : : * @param[in] end end of array pointer or iterator
151 : : */
152 : : template<typename T>
153 : 3 : Iterator<T> iterator(const T &begin, const T &end)
154 : : {
155 : 3 : return Iterator<T>(begin, end);
156 : : }
157 : :
158 : : /**
159 : : * @brief main logger object
160 : : */
161 : : class Logger
162 : : {
163 : : public:
164 : : struct Writer;
165 : :
166 : : explicit Logger(Level level = Level::NOTICE,
167 : : Writer &writer = defaultWriter());
168 : : ~Logger();
169 : : Logger(const Logger &) = delete;
170 : : Logger &operator=(const Logger &) = delete;
171 : : Logger(Logger &&);
172 : :
173 : 322 : inline const bool isEnabled() const { return !writer.buf.eof(); }
174 : :
175 : : /**
176 : : * @brief Logger writer
177 : : * @details generaly thread_local scoped
178 : : */
179 : : struct Writer
180 : : {
181 : : /** @brief log flusing and writing function */
182 : : void flush(Level level);
183 : :
184 : : /** @brief log buffer preparation */
185 : : void prepare(Level level);
186 : : /** @brief get current log line */
187 : : std::string str();
188 : :
189 : : std::stringstream buf; /**< log buffer, can be safely accessed */
190 : : Elapsed last; /**< time elapsed since last log line */
191 : :
192 : : protected:
193 : : std::stringstream::pos_type prefixEnd; /**< end of "prepared" part */
194 : : } & writer;
195 : : const Level level; /**< level associated with this Logger */
196 : :
197 : : /**
198 : : * @brief get default writer for current thread
199 : : * @details mainly internally used
200 : : */
201 : : static Writer &defaultWriter();
202 : : };
203 : :
204 : : /**
205 : : * @brief external writer function type
206 : : *
207 : : * @param[in] level Level of line to print
208 : : * @param[in] line '\n' terminated line to print
209 : : */
210 : : typedef std::function<bool(Level level, const std::string &line)> write_func_t;
211 : :
212 : : // clang-format off
213 : : // always resolve as "void"
214 : : template<class, class T> struct type_sink { typedef T type; };
215 : : template<class E, class T = void> using type_sink_t = typename type_sink<E, T>::type;
216 : :
217 : : template<class T, class = void>
218 : : struct has_toString : std::false_type {};
219 : : template<class T>
220 : : struct has_toString<T, type_sink_t<decltype(std::declval<T>().toString())>> : std::true_type {};
221 : :
222 : : template<class T, class = void>
223 : : struct has_to_string : std::false_type {};
224 : : template<class T>
225 : : struct has_to_string<T, type_sink_t<decltype(to_string(std::declval<T &>()))>> : std::true_type {};
226 : :
227 : : /* Workarround for g++ 4.8.5 compiler, to explicitly exclude a type */
228 : : template<class T, class = std::true_type>
229 : : struct exclude_default : std::false_type {};
230 : :
231 : : /* Warning: this is always true with g++ 4.8.5, due to bad template delcaration */
232 : : template<class T, class = void>
233 : : struct can_ostringstream : std::false_type {};
234 : : template<class T>
235 : : struct can_ostringstream<T, type_sink_t<decltype(std::declval<std::ostringstream>() << std::declval<T>())>> : std::true_type {};
236 : :
237 : : template<class T, class = void>
238 : : struct has_mapped_type : std::false_type {};
239 : : template<class T>
240 : : struct has_mapped_type<T, type_sink_t<typename T::mapped_type>> : std::true_type {};
241 : :
242 : : template<class T, class = void>
243 : : struct is_iterable_container : std::false_type {};
244 : : /* according to C++ spec anything that has begin/end can be iterated using range */
245 : : template<class T>
246 : : struct is_iterable_container<T, typename std::enable_if<
247 : : !std::is_same<std::string, T>::value &&
248 : : type_sink_t<decltype(std::declval<T>().begin()), std::true_type>::value &&
249 : : type_sink_t<decltype(std::declval<T>().end()), std::true_type>::value>::type> : std::true_type {};
250 : : // clang-format on
251 : :
252 : : /**
253 : : * @brief general purpose logger operator
254 : : */
255 : : template<typename T>
256 : : typename std::enable_if<!exclude_default<T>::value && can_ostringstream<T>::value &&
257 : : !is_iterable_container<T>::value &&
258 : : !has_toString<T>::value && !has_to_string<T>::value,
259 : : const Logger &>::type
260 : 268 : operator<<(const Logger &logger, const T &t)
261 : : {
262 : 268 : if (!logger.isEnabled())
263 : 5 : return logger;
264 : 263 : logger.writer.buf << t;
265 : 263 : return logger;
266 : : }
267 : :
268 : : /**
269 : : * @brief stl stream modifier logger operator
270 : : * @details support std::endl and others
271 : : */
272 : : template<typename CharT = char, typename Traits = std::char_traits<char>>
273 : 2 : const Logger &operator<<(const Logger &logger,
274 : : std::basic_ostream<CharT, Traits> &(*pf)(
275 : : std::basic_ostream<CharT, Traits> &) )
276 : : {
277 : 2 : if (!logger.isEnabled())
278 : 0 : return logger;
279 : 2 : logger.writer.buf << pf;
280 : 2 : return logger;
281 : : }
282 : :
283 : : /** @brief toString member logger operator */
284 : : template<typename T>
285 : : typename std::enable_if<!exclude_default<T>::value && has_toString<T>::value &&
286 : : !has_to_string<T>::value,
287 : : const Logger &>::type
288 : 13 : operator<<(const Logger &logger, const T &t)
289 : : {
290 : 13 : if (!logger.isEnabled())
291 : 0 : return logger;
292 : 13 : return (logger << t.toString());
293 : : }
294 : :
295 : : /** @brief to_string function logger operator */
296 : : template<typename T>
297 : : typename std::enable_if<!exclude_default<T>::value && has_to_string<T>::value,
298 : : const Logger &>::type
299 : 8 : operator<<(const Logger &logger, const T &t)
300 : : {
301 : 8 : if (!logger.isEnabled())
302 : 0 : return logger;
303 : 8 : return (logger << to_string(t));
304 : : }
305 : :
306 : : /**
307 : : * @brief any duration logging
308 : : * @details will be logged up to millisecond precision
309 : : */
310 : : template<typename T, typename U>
311 : : typename std::enable_if<!exclude_default<std::chrono::duration<T, U>>::value,
312 : : const Logger &>::type
313 : : operator<<(const Logger &logger, const std::chrono::duration<T, U> &ms)
314 : : {
315 : : if (!logger.isEnabled())
316 : : return logger;
317 : : return (logger << std::chrono::duration_cast<std::chrono::milliseconds>(ms));
318 : : }
319 : :
320 : : /**
321 : : * @brief pair operator
322 : : */
323 : : template<typename K, typename V>
324 : : typename std::enable_if<!exclude_default<std::pair<K, V>>::value,
325 : : const Logger &>::type
326 : 9 : operator<<(const Logger &logger, const std::pair<K, V> &pair)
327 : : {
328 : 9 : if (!logger.isEnabled())
329 : 0 : return logger;
330 : 9 : logger << pair.first << ":" << pair.second;
331 : 9 : return logger;
332 : : }
333 : :
334 : : /**
335 : : * @brief end of tuple operator
336 : : */
337 : : template<size_t n, typename... T>
338 : : typename std::enable_if<!exclude_default<std::tuple<T...>>::value &&
339 : : (n >= sizeof...(T)),
340 : : const Logger &>::type
341 : 1 : operator<<(const Logger &logger, const std::tuple<T...> &)
342 : : {
343 : 1 : return logger;
344 : : }
345 : :
346 : : /**
347 : : * @brief tuple operator
348 : : */
349 : : template<size_t n = 0, typename... T>
350 : : typename std::enable_if<!exclude_default<std::tuple<T...>>::value &&
351 : : (n < sizeof...(T)),
352 : : const Logger &>::type
353 : 2 : operator<<(const Logger &logger, const std::tuple<T...> &tup)
354 : : {
355 : 2 : if (!logger.isEnabled())
356 : 0 : return logger;
357 : : if (n == 0)
358 : 1 : logger << "(";
359 : : else
360 : 1 : logger << ",";
361 : 2 : logger << std::get<n>(tup);
362 : 2 : operator<< <n + 1>(logger, tup);
363 : : if (n == 0)
364 : 1 : logger << ")";
365 : 2 : return logger;
366 : : }
367 : :
368 : : constexpr std::size_t MAX_CONTAINER_ITEMS = 20;
369 : : constexpr std::ostringstream::off_type MAX_CONTAINER_LEN = 30;
370 : :
371 : : /**
372 : : * @brief stl containers support (any)
373 : : * @details requirement is to be range-iterable and have value_type typedef
374 : : * @details container is limited to 20 items or 30 characters on non-debug
375 : : * loggers.
376 : : */
377 : : template<typename T>
378 : : typename std::enable_if<!exclude_default<T>::value &&
379 : : is_iterable_container<T>::value &&
380 : : !has_toString<T>::value && !has_to_string<T>::value,
381 : : const Logger &>::type
382 : 12 : operator<<(const Logger &logger, const T &value)
383 : : {
384 : 12 : if (!logger.isEnabled())
385 : 0 : return logger;
386 : 12 : logger << (has_mapped_type<T>::value ? "{" : "[");
387 : 12 : std::size_t count = 0;
388 : 12 : const std::ostringstream::pos_type pos = logger.writer.buf.tellp();
389 : 50 : for (const auto &v : value)
390 : : {
391 : 41 : if (count++)
392 : 29 : logger << ",";
393 : 41 : logger << v;
394 : 82 : if (logger.level != Level::DEBUG &&
395 : 41 : (count >= MAX_CONTAINER_ITEMS ||
396 : 82 : logger.writer.buf.tellp() >= (pos + MAX_CONTAINER_LEN)))
397 : : {
398 : 3 : logger << "...";
399 : 3 : break;
400 : : }
401 : : }
402 : 12 : logger << (has_mapped_type<T>::value ? "}" : "]");
403 : 12 : return logger;
404 : : }
405 : :
406 : : /**
407 : : * @brief millisecond duration logging operator
408 : : */
409 : : const Logger &operator<<(const Logger &logger,
410 : : const std::chrono::milliseconds &ms);
411 : :
412 : : /**
413 : : * @brief NullLogger discards everything
414 : : */
415 : : class NullLogger : public Logger
416 : : {
417 : : public:
418 : : explicit NullLogger(Level level);
419 : :
420 : : template<typename T>
421 : 1 : NullLogger &operator<<(T)
422 : : {
423 : 1 : return *this;
424 : : }
425 : : };
426 : :
427 : : /**
428 : : * @brief Configuration object
429 : : * @details use this object to modify the logger behavior
430 : : */
431 : : class Config
432 : : {
433 : : public:
434 : : /**
435 : : * @brief reset configuration and reload from env
436 : : */
437 : : Config &reset();
438 : :
439 : : /** @brief get current log level */
440 : : Level getLevel() const;
441 : : /** @brief set log level */
442 : : Config &setLevel(Level level);
443 : :
444 : : /**
445 : : * @brief clear and set category filter
446 : : * @param filter filters to set
447 : : * @details use an empty string to clear filters
448 : : */
449 : : Config &setFilter(const std::string &filter = std::string());
450 : : /**
451 : : * @brief add a new filter
452 : : * @param filter filters to append
453 : : * @details filters are appended to existing ones, being less prioritary
454 : : */
455 : : Config &addFilter(const std::string &filter);
456 : : /** @brief get current filters */
457 : : std::string getFilter() const;
458 : :
459 : : /** @brief enable a feature */
460 : : Config &enable(Feature feature);
461 : : /** @brief disable a feature */
462 : : Config &disable(Feature feature);
463 : : /** @brief check if a feature is enabled */
464 : : bool isEnabled(Feature feature) const;
465 : :
466 : : /**
467 : : * @brief Set the line writing function
468 : : *
469 : : * @param fun function pointer to set
470 : : * @details the writer function must return `true` if the given line has
471 : : * been consumed
472 : : */
473 : : Config &setWriteFunc(write_func_t fun);
474 : : };
475 : :
476 : : /**
477 : : * @brief reset stream flags to default value
478 : : * @details use as: debug() << std::fixed << std::setprecision(42) << 1.23 <<
479 : : * logger::clearflags;
480 : : */
481 : : template<typename CharT, typename Traits>
482 : 1 : std::basic_ostream<CharT, Traits> &clearflags(
483 : : std::basic_ostream<CharT, Traits> &os)
484 : : {
485 : 1 : os.setf(std::ios_base::skipws | std::ios_base::dec,
486 : : ~std::ios_base::fmtflags(0));
487 : 1 : os.width(0);
488 : 1 : os.precision(6);
489 : 1 : os.fill(os.widen(' '));
490 : 1 : return os;
491 : : }
492 : :
493 : : /**
494 : : * @brief create a Logger object
495 : : * @param level log level
496 : : * @param category associated category
497 : : */
498 : : Logger log(Level level, const std::string &category = std::string());
499 : :
500 : : #ifdef NDEBUG
501 : :
502 : : inline NullLogger debug(const std::string & = std::string())
503 : : {
504 : : return NullLogger(Level::DEBUG);
505 : : }
506 : :
507 : : #else
508 : :
509 : : /** @brief create a Level::DEBUG Logger */
510 : 12 : inline Logger debug(const std::string &category = std::string())
511 : : {
512 : 12 : return log(Level::DEBUG, category);
513 : : }
514 : :
515 : : #endif
516 : :
517 : : /** @brief create a Level::INFO Logger */
518 : 23 : inline Logger info(const std::string &category = std::string())
519 : : {
520 : 23 : return log(Level::INFO, category);
521 : : }
522 : :
523 : : /** @brief create a Level::NOTICE Logger */
524 : 7 : inline Logger notice(const std::string &category = std::string())
525 : : {
526 : 7 : return log(Level::NOTICE, category);
527 : : }
528 : :
529 : : /** @brief create a Level::WARNING Logger */
530 : 3 : inline Logger warning(const std::string &category = std::string())
531 : : {
532 : 3 : return log(Level::WARNING, category);
533 : : }
534 : :
535 : : /** @brief create a Level::ERROR Logger */
536 : 3 : inline Logger error(const std::string &category = std::string())
537 : : {
538 : 3 : return log(Level::ERROR, category);
539 : : }
540 : :
541 : : /** @brief create a Level::CRIT Logger */
542 : 2 : inline Logger crit(const std::string &category = std::string())
543 : : {
544 : 2 : return log(Level::CRIT, category);
545 : : }
546 : :
547 : : /** @brief create a Level::ALERT Logger */
548 : : inline Logger alert(const std::string &category = std::string())
549 : : {
550 : : return log(Level::ALERT, category);
551 : : }
552 : :
553 : : /** @brief create a Level::EMERG Logger */
554 : 2 : inline Logger emerg(const std::string &category = std::string())
555 : : {
556 : 2 : return log(Level::EMERG, category);
557 : : }
558 : :
559 : : /**
560 : : * @brief format any number as hex (first casted to unsigned)
561 : : * @details use it along with `logger::hex`, ex: `logger::crit() <<
562 : : * logger::hex(42)`
563 : : * @details quite similar to `logger::crit() << std::hex << 42` but:
564 : : * - will always pad depending on type width
565 : : * - will not modify the stream's internal state (no `logger::clearflags`
566 : : * required)
567 : : * - will add a prefix (when requested)
568 : : * - will support `uint8_t` and `int8_t` types (considered as `char` by regular
569 : : * streams)
570 : : */
571 : : template<typename T>
572 : : struct FmtHex
573 : : {
574 : 14 : constexpr FmtHex(const T &value, bool prefix = false) :
575 : 14 : value{value}, prefix{prefix}
576 : 14 : {}
577 : : const T &value;
578 : : const bool prefix;
579 : :
580 : 12 : std::string toString() const
581 : : {
582 : 12 : std::string str((sizeof(T) << 1) + (prefix ? 2 : 0), '0');
583 : 12 : std::string::reverse_iterator it{str.rbegin()};
584 : 12 : for (typename std::make_unsigned<T>::type temp =
585 : 12 : static_cast<typename std::make_unsigned<T>::type>(value);
586 : 116 : temp != 0; temp = temp >> 4)
587 : : {
588 : 104 : int8_t digit{static_cast<int8_t>(temp & 0x0F)};
589 : 104 : *it++ = (digit >= 10) ? (digit - 10 + 'A') : (digit + '0');
590 : : }
591 : 12 : if (prefix)
592 : 3 : str[1] = 'x';
593 : 24 : return str;
594 : 0 : }
595 : : };
596 : :
597 : : /**
598 : : * @brief convenience function for FmtHex
599 : : */
600 : : template<typename T>
601 : 14 : inline const FmtHex<T> hex(const T &value, bool prefix = false)
602 : : {
603 : 14 : return FmtHex<T>(value, prefix);
604 : : }
605 : :
606 : : /**
607 : : * @brief optimization for FmtHex<uint8_t>
608 : : */
609 : 2 : inline const logger::Logger &operator<<(const logger::Logger &logger,
610 : : const logger::FmtHex<uint8_t> &hex)
611 : : {
612 : 2 : if (logger.isEnabled())
613 : : {
614 : 2 : if (hex.prefix)
615 : 1 : logger << "0x";
616 : 2 : int8_t digit = ((hex.value >> 4) & 0xF);
617 : 2 : logger << static_cast<char>((digit >= 10) ? (digit - 10 + 'A') :
618 : 2 : (digit + '0'));
619 : 2 : digit = hex.value & 0xF;
620 : 2 : return logger << static_cast<char>((digit >= 10) ? (digit - 10 + 'A') :
621 : 2 : (digit + '0'));
622 : : }
623 : 0 : return logger;
624 : : }
625 : :
626 : : template<typename T>
627 : : struct exclude_default<std::shared_ptr<T>> : std::true_type
628 : : {};
629 : :
630 : : template<typename T>
631 : : struct exclude_default<std::unique_ptr<T>> : std::true_type
632 : : {};
633 : :
634 : : template<typename T>
635 : : typename std::enable_if<
636 : : std::is_same<std::unique_ptr<typename T::element_type>, T>::value ||
637 : : std::is_same<std::shared_ptr<typename T::element_type>, T>::value,
638 : : const logger::Logger &>::type
639 : 4 : operator<<(const logger::Logger &logger, const T &ptr)
640 : : {
641 : 4 : if (logger.isEnabled())
642 : : {
643 : 4 : if (ptr)
644 : 2 : return logger << *ptr;
645 : : else
646 : 2 : return logger << "nullptr";
647 : : }
648 : 0 : return logger;
649 : : }
650 : :
651 : : } // namespace logger
652 : :
653 : : #endif
|