carl  24.04
Computer ARithmetic Library
Logger.h
Go to the documentation of this file.
1 #pragma once
2 
5 #include "logging_utils.h"
6 #include "config.h"
7 
8 #include "Filter.h"
9 #include "Formatter.h"
10 #include "Sink.h"
11 
12 #include <cassert>
13 #include <cstring>
14 #include <fstream>
15 #include <iostream>
16 #include <map>
17 #include <memory>
18 #include <mutex>
19 #include <sstream>
20 #include <utility>
21 
22 
23 namespace carl {
24 
25 /**
26  * Contains a custom logging facility.
27  *
28  * This logging facility is fairly generic and is used as a simple and header-only alternative to more advanced solutions like log4cplus or boost::log.
29  *
30  * The basic components are Sinks, Channels, Filters, RecordInfos, Formatters and the central Logger component.
31  *
32  * A Sink represents a logging output like a terminal or a log file.
33  * This implementation provides a FileSink and a StreamSink, but the basic Sink class can be extended as necessary.
34  *
35  * A Channel is a string that identifies the context of the log message, usually something like the class name where the log message is emitted.
36  * Channels are organized hierarchically where the levels are separated by dots. For example, `carl` is considered the parent of `carl.core`.
37  *
38  * A Filter is associated with a Sink and makes sure that only a subset of all log messages is forwarded to the Sink.
39  * Filter rules are pairs of a Channel and a minimum LogLevel, meaning that messages of this Channel and at least the given LogLevel are forwarded.
40  * If a Filter does not contain any rule for some Channel, the parent Channel is considered. Each Filter contains a rule for the empty Channel, initialized with LVL_DEFAULT.
41  *
42  * A RecordInfo stores auxiliary information of a log message like the filename, line number and function name where the log message was emitted.
43  *
44  * A Formatter is associated with a Sink and produces the actual string that is sent to the Sink.
45  * Usually, it adds auxiliary information like the current time, LogLevel, Channel and information from a RecordInfo to the string logged by the user.
46  * The Formatter implements a reasonable default behaviour for log files, but it can be subclassed and modified as necessary.
47  *
48  * The Logger class finally plugs all these components together.
49  * It allows to configure multiple Sink objects which are identified by strings called `id` and offers a central `log()` method.
50  *
51  * Initial configuration may look like this:
52  * @code{.cpp}
53  * carl::logging::logger().configure("logfile", "carl.log");
54  * carl::logging::logger().filter("logfile")
55  * ("carl", carl::logging::LogLevel::LVL_INFO)
56  * ("carl.core", carl::logging::LogLevel::LVL_DEBUG)
57  * ;
58  * carl::logging::logger().resetFormatter();
59  * @endcode
60  *
61  * Macro facilitate the usage:
62  * <ul>
63  * <li>`CARLLOG_<LVL>(channel, msg)` produces a normal log message where channel should be string identifying the channel and msg is the message to be logged.</li>
64  * <li>`CARLLOG_FUNC(channel, args)` produces a log message tailored for function calls. args should represent the function arguments.</li>
65  * <li>`CARLLOG_ASSERT(channel, condition, msg)` checks the condition and if it fails calls `CARLLOG_FATAL(channel, msg)` and asserts the condition.</li>
66  * </ul>
67  * Any message (`msg` or `args`) can be an arbitrary expression that one would stream to an `std::ostream` like `stream << (msg);`. No final newline is needed.
68  */
69 namespace logging {
70 
71 /**
72  * Main logger class.
73  */
74 class Logger: public carl::Singleton<Logger> {
76  /// Mapping from channels to associated logging classes.
77  std::map<std::string, std::tuple<std::shared_ptr<Sink>, Filter, std::shared_ptr<Formatter>>> mData;
78  /// Logging mutex to ensure thread-safe logging.
79  std::mutex mMutex;
80 
81 public:
82  /**
83  * Check if a Sink with the given id has been installed.
84  * @param id Sink identifier.
85  * @return If a Sink with this id is present.
86  */
87  bool has(const std::string& id) const noexcept {
88  return mData.find(id) != mData.end();
89  }
90  /**
91  * Installs the given sink.
92  * If a Sink with this name is already present, it is overwritten.
93  * @param id Sink identifier.
94  * @param sink Sink.
95  */
96  void configure(const std::string& id, std::shared_ptr<Sink> sink) {
97  std::lock_guard<std::mutex> lock(mMutex);
98  mData[id] = std::make_tuple(std::move(sink), Filter(), std::make_shared<Formatter>());
99  }
100  /**
101  * Installs a FileSink.
102  * @param id Sink identifier.
103  * @param filename Filename passed to the FileSink.
104  */
105  void configure(const std::string& id, const std::string& filename) {
106  configure(id, std::make_shared<FileSink>(filename));
107  }
108  /**
109  * Installs a StreamSink.
110  * @param id Sink identifier.
111  * @param os Output stream passed to the StreamSink.
112  */
113  void configure(const std::string& id, std::ostream& os) {
114  configure(id, std::make_shared<StreamSink>(os));
115  }
116  /**
117  * Retrieves the Filter for some Sink.
118  * @param id Sink identifier.
119  * @return Filter.
120  */
121  Filter& filter(const std::string& id) noexcept {
122  auto it = mData.find(id);
123  assert(it != mData.end());
124  return std::get<1>(it->second);
125  }
126  /**
127  * Retrieves the Formatter for some Sink.
128  * @param id Sink identifier.
129  * @return Formatter.
130  */
131  const std::shared_ptr<Formatter>& formatter(const std::string& id) noexcept {
132  auto it = mData.find(id);
133  assert(it != mData.end());
134  return std::get<2>(it->second);
135  }
136  /**
137  * Overwrites the Formatter for some Sink.
138  * @param id Sink identifier.
139  * @param fmt New Formatter.
140  */
141  void formatter(const std::string& id, std::shared_ptr<Formatter> fmt) noexcept {
142  auto it = mData.find(id);
143  assert(it != mData.end());
144  std::get<2>(it->second) = std::move(fmt);
145  std::get<2>(it->second)->configure(std::get<1>(it->second));
146  }
147  /**
148  * Reconfigures all Formatter objects.
149  * This should be done once after all configuration is finished.
150  */
151  void resetFormatter() noexcept {
152  for (auto& t: mData) {
153  std::get<2>(t.second)->configure(std::get<1>(t.second));
154  }
155  }
156  /**
157  * Checks whether a log message would be visible for some sink.
158  * If this is not the case, we do not need to render it at all.
159  * @param level LogLevel.
160  * @param channel Channel name.
161  */
162  bool visible(LogLevel level, const std::string& channel) const noexcept {
163  for (const auto& t: mData) {
164  if (std::get<1>(t.second).check(channel, level)) {
165  return true;
166  }
167  }
168  return false;
169  }
170  /**
171  * Logs a message.
172  * @param level LogLevel.
173  * @param channel Channel name.
174  * @param ss Message to be logged.
175  * @param info Auxiliary information.
176  */
177  void log(LogLevel level, const std::string& channel, const std::stringstream& ss, const RecordInfo& info) {
178  std::lock_guard<std::mutex> lock(mMutex);
179  for (auto& t: mData) {
180  if (!std::get<1>(t.second).check(channel, level)) continue;
181  std::get<2>(t.second)->prefix(std::get<0>(t.second)->log(), channel, level, info);
182  std::get<0>(t.second)->log() << ss.str();
183  std::get<2>(t.second)->suffix(std::get<0>(t.second)->log());
184  }
185  }
186 };
187 
188 /**
189  * Returns the single global instance of a Logger.
190  *
191  * Calls `Logger::getInstance()`.
192  * @return Logger object.
193  */
194 inline Logger& logger() {
195  return Logger::getInstance();
196 }
197 
198 }
199 }
carl is the main namespace for the library.
Logger & logger()
Returns the single global instance of a Logger.
Definition: Logger.h:194
LogLevel
Indicated which log messages should be forwarded to some sink.
Definition: LogLevel.h:12
Base class that implements a singleton.
Definition: Singleton.h:24
static Logger & getInstance()
Returns the single instance of this class by reference.
Definition: Singleton.h:45
This class checks if some log message shall be forwarded to some sink.
Definition: Filter.h:15
Main logger class.
Definition: Logger.h:74
const std::shared_ptr< Formatter > & formatter(const std::string &id) noexcept
Retrieves the Formatter for some Sink.
Definition: Logger.h:131
void log(LogLevel level, const std::string &channel, const std::stringstream &ss, const RecordInfo &info)
Logs a message.
Definition: Logger.h:177
void configure(const std::string &id, std::shared_ptr< Sink > sink)
Installs the given sink.
Definition: Logger.h:96
bool visible(LogLevel level, const std::string &channel) const noexcept
Checks whether a log message would be visible for some sink.
Definition: Logger.h:162
std::mutex mMutex
Logging mutex to ensure thread-safe logging.
Definition: Logger.h:79
bool has(const std::string &id) const noexcept
Check if a Sink with the given id has been installed.
Definition: Logger.h:87
void configure(const std::string &id, const std::string &filename)
Installs a FileSink.
Definition: Logger.h:105
Filter & filter(const std::string &id) noexcept
Retrieves the Filter for some Sink.
Definition: Logger.h:121
void configure(const std::string &id, std::ostream &os)
Installs a StreamSink.
Definition: Logger.h:113
void formatter(const std::string &id, std::shared_ptr< Formatter > fmt) noexcept
Overwrites the Formatter for some Sink.
Definition: Logger.h:141
std::map< std::string, std::tuple< std::shared_ptr< Sink >, Filter, std::shared_ptr< Formatter > > > mData
Mapping from channels to associated logging classes.
Definition: Logger.h:77
void resetFormatter() noexcept
Reconfigures all Formatter objects.
Definition: Logger.h:151
Additional information about a log message.
Definition: logging.h:13