algorithms  2.2.0
Framework-independent algorithms for the EIC
Loading...
Searching...
No Matches
logger.h
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (C) 2022 Wouter Deconinck, Sylvester Joosten
3//
4// Logging service, which will use a callback to use the framework logging infrastructure.
5// use ::action(void(LogLevel, std::string_view, std::string_view)) to register
6// a logger.
7//
8// Also provides the LoggerMixin and LoggedService base classes
9//
10#pragma once
11
12#include <array>
13#include <functional>
14#include <ios>
15#include <iostream>
16#include <memory>
17#include <mutex>
18#include <ostream>
19#include <sstream>
20#include <string>
21
22#include <algorithms/error.h>
23#include <algorithms/service.h>
24#include <fmt/format.h>
25
26// Simple thread-safe logger with optional overrides by the calling framework
27namespace algorithms {
28
29enum class LogLevel : unsigned {
30 kTrace = 0,
31 kDebug = 1,
32 kInfo = 2,
34 kError = 4,
36};
37constexpr std::string_view logLevelName(LogLevel level) {
38 // Compiler can warn if not all of the enum is covered
39 switch (level) {
41 return "TRACE";
43 return "DEBUG";
44 case LogLevel::kInfo:
45 return "INFO";
47 return "WARNING";
49 return "ERROR";
51 return "CRITICAL";
52 }
53 // Default return to make gcc happy, will never happen
54 return "UNKNOWN";
55}
56
57// Note: the log action is responsible for dealing with concurrent calls
58// the default LogAction is a thread-safe example
59class LogSvc : public Service<LogSvc> {
60public:
61 using LogAction = std::function<void(LogLevel, std::string_view, std::string_view)>;
62 void defaultLevel(const LogLevel l) { m_level.set(detail::upcast_type_t<LogLevel>(l)); }
63 LogLevel defaultLevel() const { return m_level; }
64 void init() {
65 ; // do nothing by default, as we are already initialized
66 }
67 void init(LogAction a) { m_action = a; }
68 void report(const LogLevel l, std::string_view caller, std::string_view msg) const {
69 m_action(l, caller, msg);
70 }
71
72private:
73 LogAction makeDefaultAction() {
74 return [](const LogLevel l, std::string_view caller, std::string_view msg) {
75 static std::mutex m;
76 std::lock_guard<std::mutex> lock(m);
77 fmt::print("{} [{}] {}\n", logLevelName(l), caller, msg);
78 };
79 }
80
81 Property<LogLevel> m_level{this, "defaultLevel", LogLevel::kInfo,
82 "Default log level for the LogSvc"};
83 LogAction m_action = makeDefaultAction();
84
86};
87
88namespace detail {
89 // Output buffer that calls our global logger's report() function
90 class LoggerBuffer : public std::stringbuf {
91 public:
92 LoggerBuffer(const LogLevel l, std::string_view caller)
93 : m_mylevel{l}, m_caller{caller}, m_logger{LogSvc::instance()} {}
94 virtual int sync() {
95 // report should deal with concurrency (the minimal version does)
96 m_logger.report(m_mylevel, m_caller, this->str());
97 this->str("");
98 return 0;
99 }
100
101 private:
102 // The output buffer knows the log level of its associated logger
103 // (eg. is this the debug logger?)
104 LogLevel m_mylevel;
105 const std::string m_caller;
106 const LogSvc& m_logger;
107 };
108 // thread-safe output stream for the logger
109 // The caller name must outlive the stream. The output buffer is only created when something
110 // is written above threshold, so suppressed messages are cheap. Unflushed output is reported
111 // when the stream is destroyed.
113 public:
114 LoggerStream(std::string_view caller, const LogLevel level,
115 const LogLevel threshold = LogSvc::instance().defaultLevel())
116 : m_caller{caller}, m_level{level}, m_threshold{threshold} {}
117 LoggerStream() = delete;
118 LoggerStream(const LoggerStream&) = delete;
120 if (m_impl && !m_impl->buffer.str().empty()) {
121 m_impl->os.flush();
122 }
123 }
124
125 template <class Arg> LoggerStream& operator<<(Arg&& streamable) {
126 if (m_level >= m_threshold) {
127 std::lock_guard<std::mutex> lock{m_mutex};
128 os() << std::forward<Arg>(streamable);
129 return *this;
130 }
131 return *this;
132 }
133 // To support input manipulators such as std::endl
134 // Note: would be better with Concepts
135 using IOManipType1 = std::ostream&(std::ostream&); // this capturs std::endl;
136 using IOManipType2 = std::ios_base&(std::ios_base&);
138 if (m_level >= m_threshold) {
139 std::lock_guard<std::mutex> lock{m_mutex};
140 f(os());
141 return *this;
142 }
143 return *this;
144 }
146 if (m_level >= m_threshold) {
147 std::lock_guard<std::mutex> lock{m_mutex};
148 f(os());
149 return *this;
150 }
151 return *this;
152 }
153 LogLevel threshold() const { return m_threshold; }
154 void threshold(const LogLevel th) { m_threshold = th; }
155
156 private:
157 struct Impl {
158 Impl(const LogLevel level, std::string_view caller) : buffer{level, caller}, os{&buffer} {}
159 LoggerBuffer buffer;
160 std::ostream os;
161 };
162 std::ostream& os() {
163 if (!m_impl) {
164 m_impl = std::make_unique<Impl>(m_level, m_caller);
165 }
166 return m_impl->os;
167 }
168
169 std::mutex m_mutex;
170 std::string_view m_caller;
171 std::unique_ptr<Impl> m_impl;
172 const LogLevel m_level;
173 LogLevel m_threshold;
174 };
175} // namespace detail
176
177// Mixin meant to add utility logger functions to algorithms/services/etc
179public:
180 LoggerMixin(std::string_view caller, const LogLevel threshold = LogSvc::instance().defaultLevel())
181 : m_caller{caller}, m_level{threshold}, m_logger{LogSvc::instance()} {}
182 LoggerMixin(const LoggerMixin&) = delete;
184
185public:
186 // Not done through Properties, as that would require entanglement with the
187 // PropertyMixin which is not appropriate here. It's the FW responsibility to set this
188 // on the algorithm level if desired, before or during the init() stage.
189 void level(const LogLevel threshold) { m_level = threshold; }
190 LogLevel level() const { return m_level; }
191
192protected:
193 // Each call returns a new stream, so messages composed concurrently from several threads
194 // (e.g. in a shared algorithm) do not get mixed up
196 detail::LoggerStream error() const { return stream(LogLevel::kError); }
198 detail::LoggerStream info() const { return stream(LogLevel::kInfo); }
199 detail::LoggerStream debug() const { return stream(LogLevel::kDebug); }
200 detail::LoggerStream trace() const { return stream(LogLevel::kTrace); }
201
202 void critical(std::string_view msg) const { report<LogLevel::kCritical>(msg); }
203 void error(std::string_view msg) const { report<LogLevel::kError>(msg); }
204 void warning(std::string_view msg) const { report<LogLevel::kWarning>(msg); }
205 void info(std::string_view msg) const { report<LogLevel::kInfo>(msg); }
206 void debug(std::string_view msg) const { report<LogLevel::kDebug>(msg); }
207 void trace(std::string_view msg) const { report<LogLevel::kTrace>(msg); }
208
209 template <typename ...T> constexpr void critical(fmt::format_string<T...> fmt, T&&... args) const {
210 report_fmt<LogLevel::kCritical>(fmt, std::forward<decltype(args)>(args)...);
211 }
212 template <typename ...T> constexpr void error(fmt::format_string<T...> fmt, T&&... args) const {
213 report_fmt<LogLevel::kError>(fmt, std::forward<decltype(args)>(args)...);
214 }
215 template <typename ...T> constexpr void warning(fmt::format_string<T...> fmt, T&&... args) const {
216 report_fmt<LogLevel::kWarning>(fmt, std::forward<decltype(args)>(args)...);
217 }
218 template <typename ...T> constexpr void info(fmt::format_string<T...> fmt, T&&... args) const {
219 report_fmt<LogLevel::kInfo>(fmt, std::forward<decltype(args)>(args)...);
220 }
221 template <typename ...T> constexpr void debug(fmt::format_string<T...> fmt, T&&... args) const {
222 report_fmt<LogLevel::kDebug>(fmt, std::forward<decltype(args)>(args)...);
223 }
224 template <typename ...T> constexpr void trace(fmt::format_string<T...> fmt, T&&... args) const {
225 report_fmt<LogLevel::kTrace>(fmt, std::forward<decltype(args)>(args)...);
226 }
227
228 bool aboveCriticalThreshold() const { return m_level >= LogLevel::kCritical; }
229 bool aboveErrorThreshold() const { return m_level >= LogLevel::kError; }
230 bool aboveWarningThreshold() const { return m_level >= LogLevel::kWarning; }
231 bool aboveInfoThreshold() const { return m_level >= LogLevel::kInfo; }
232 bool aboveDebugThreshold() const { return m_level >= LogLevel::kDebug; }
233 bool aboveTraceThreshold() const { return m_level >= LogLevel::kTrace; }
234
235 // LoggerMixin also provides nice error raising
236 // ErrorTypes needs to derive from Error, and needs to have a constructor that takes two
237 // strings --> TODO add C++20 Concept version
238 template <class ErrorType = Error> void raise(std::string_view msg) const {
239 error() << msg << endmsg;
240 throw ErrorType(msg);
241 }
242
243 // Endmsg (flush) support to initiate a sync() action
244 static std::ostream& endmsg(std::ostream& os) {
245 os.flush();
246 return os;
247 }
248
249 template <LogLevel l, typename ...T>
250 constexpr void report_fmt(fmt::format_string<T...> fmt, T&&... args) const {
251 if (l >= m_level) {
252 m_logger.report(l, m_caller, fmt::format(fmt, std::forward<decltype(args)>(args)...));
253 }
254 }
255
256private:
257 template <LogLevel l> void report(std::string_view msg) const {
258 if (l >= m_level) {
259 m_logger.report(l, m_caller, msg);
260 }
261 }
262 detail::LoggerStream stream(const LogLevel l) const { return {m_caller, l, m_level}; }
263
264 const std::string m_caller;
265 LogLevel m_level;
266
267 const LogSvc& m_logger;
268};
269
270// A Service base class with logger functionality
271template <class SvcType> class LoggedService : public Service<SvcType>, public LoggerMixin {
272protected:
273 LoggedService(std::string_view name) : Service<SvcType>(name), LoggerMixin(name) {}
274};
275
276} // namespace algorithms
277
278#define ALGORITHMS_DEFINE_LOGGED_SERVICE(className) \
279protected: \
280 className() : LoggedService<className>(#className) {} \
281 \
282public: \
283 friend class Service<className>; \
284 className(const className&) = delete; \
285 void operator=(const className&) = delete; \
286 constexpr static const char* kName = #className;
287
288//#define endmsg std::flush
void report(const LogLevel l, std::string_view caller, std::string_view msg) const
Definition logger.h:68
LogLevel defaultLevel() const
Definition logger.h:63
void defaultLevel(const LogLevel l)
Definition logger.h:62
void init(LogAction a)
Definition logger.h:67
std::function< void(LogLevel, std::string_view, std::string_view)> LogAction
Definition logger.h:61
LoggedService(std::string_view name)
Definition logger.h:273
bool aboveDebugThreshold() const
Definition logger.h:232
bool aboveWarningThreshold() const
Definition logger.h:230
void debug(std::string_view msg) const
Definition logger.h:206
void critical(std::string_view msg) const
Definition logger.h:202
LoggerMixin(std::string_view caller, const LogLevel threshold=LogSvc::instance().defaultLevel())
Definition logger.h:180
detail::LoggerStream error() const
Definition logger.h:196
static std::ostream & endmsg(std::ostream &os)
Definition logger.h:244
bool aboveInfoThreshold() const
Definition logger.h:231
detail::LoggerStream critical() const
Definition logger.h:195
LogLevel level() const
Definition logger.h:190
void warning(std::string_view msg) const
Definition logger.h:204
detail::LoggerStream warning() const
Definition logger.h:197
constexpr void report_fmt(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:250
detail::LoggerStream info() const
Definition logger.h:198
LoggerMixin & operator=(const LoggerMixin &)=delete
detail::LoggerStream debug() const
Definition logger.h:199
constexpr void debug(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:221
constexpr void info(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:218
constexpr void warning(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:215
void level(const LogLevel threshold)
Definition logger.h:189
void trace(std::string_view msg) const
Definition logger.h:207
void error(std::string_view msg) const
Definition logger.h:203
void raise(std::string_view msg) const
Definition logger.h:238
detail::LoggerStream trace() const
Definition logger.h:200
LoggerMixin(const LoggerMixin &)=delete
constexpr void critical(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:209
constexpr void error(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:212
bool aboveTraceThreshold() const
Definition logger.h:233
constexpr void trace(fmt::format_string< T... > fmt, T &&... args) const
Definition logger.h:224
bool aboveCriticalThreshold() const
Definition logger.h:228
void info(std::string_view msg) const
Definition logger.h:205
bool aboveErrorThreshold() const
Definition logger.h:229
std::string_view name() const
Definition name.h:17
Service(std::string_view name)
Definition service.h:242
static LogSvc & instance()
Definition service.h:228
LoggerBuffer(const LogLevel l, std::string_view caller)
Definition logger.h:92
LoggerStream(std::string_view caller, const LogLevel level, const LogLevel threshold=LogSvc::instance().defaultLevel())
Definition logger.h:114
LogLevel threshold() const
Definition logger.h:153
std::ios_base &(std::ios_base &) IOManipType2
Definition logger.h:136
LoggerStream(const LoggerStream &)=delete
LoggerStream & operator<<(Arg &&streamable)
Definition logger.h:125
LoggerStream & operator<<(IOManipType1 *f)
Definition logger.h:137
LoggerStream & operator<<(IOManipType2 *f)
Definition logger.h:145
std::ostream &(std::ostream &) IOManipType1
Definition logger.h:135
void threshold(const LogLevel th)
Definition logger.h:154
typename upcast_type< std::remove_cvref_t< T > >::type upcast_type_t
Definition upcast.h:35
constexpr std::string_view logLevelName(LogLevel level)
Definition logger.h:37
#define ALGORITHMS_DEFINE_SERVICE(className)
Definition service.h:26