1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_UTIL_LOGGER_HPP
20 #define REALM_UTIL_LOGGER_HPP
29 #include <realm/util/features.h>
30 #include <realm/util/thread.hpp>
31 #include <realm/util/file.hpp>
37 /// All Logger objects store a reference to a LevelThreshold object which it
38 /// uses to efficiently query about the current log level threshold
39 /// (`level_threshold.get()`). All messages logged with a level that is lower
40 /// than the current threshold will be dropped. For the sake of efficiency, this
41 /// test happens before the message is formatted.
43 /// A logger is not inherently thread-safe, but specific implementations can be
44 /// (see ThreadSafeLogger). For a logger to be thread-safe, the implementation
45 /// of do_log() must be thread-safe and the referenced LevelThreshold object
46 /// must have a thread-safe get() method.
50 /// logger.error("Overlong message from master coordinator");
51 /// logger.info("Listening for peers on %1:%2", listen_address, listen_port);
54 template <class... Params>
55 void trace(const char* message, Params&&...);
56 template <class... Params>
57 void debug(const char* message, Params&&...);
58 template <class... Params>
59 void detail(const char* message, Params&&...);
60 template <class... Params>
61 void info(const char* message, Params&&...);
62 template <class... Params>
63 void warn(const char* message, Params&&...);
64 template <class... Params>
65 void error(const char* message, Params&&...);
66 template <class... Params>
67 void fatal(const char* message, Params&&...);
69 /// Specifies criticality when passed to log(). Functions as a criticality
70 /// threshold when returned from LevelThreshold::get().
72 /// error Be silent unless when there is an error.
73 /// warn Be silent unless when there is an error or a warning.
74 /// info Reveal information about what is going on, but in a
75 /// minimalistic fashion to avoid general overhead from logging
76 /// and to keep volume down.
77 /// detail Same as 'info', but prioritize completeness over minimalism.
78 /// debug Reveal information that can aid debugging, no longer paying
79 /// attention to efficiency.
80 /// trace A version of 'debug' that allows for very high volume
82 enum class Level { all, trace, debug, detail, info, warn, error, fatal, off };
84 template <class... Params>
85 void log(Level, const char* message, Params&&...);
87 /// Shorthand for `int(level) >= int(level_threshold.get())`.
88 bool would_log(Level level) const noexcept;
92 const LevelThreshold& level_threshold;
94 virtual ~Logger() noexcept;
97 Logger(const LevelThreshold&) noexcept;
99 static void do_log(Logger&, Level, std::string message);
101 virtual void do_log(Level, std::string message) = 0;
103 static const char* get_level_prefix(Level) noexcept;
108 template <class... Params>
109 REALM_NOINLINE void do_log(Level, const char* message, Params&&...);
110 void log_impl(State&);
111 template <class Param, class... Params>
112 void log_impl(State&, Param&&, Params&&...);
113 template <class Param>
114 static void subst(State&, Param&&);
117 template <class C, class T>
118 std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>&, Logger::Level);
120 template <class C, class T>
121 std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>&, Logger::Level&);
123 class Logger::LevelThreshold {
125 virtual Level get() const noexcept = 0;
129 /// A root logger that is not thread-safe and allows for the log level threshold
130 /// to be changed over time. The initial log level threshold is
131 /// Logger::Level::info.
132 class RootLogger : private Logger::LevelThreshold, public Logger {
134 void set_level_threshold(Level) noexcept;
140 Level m_level_threshold = Level::info;
141 Level get() const noexcept override final;
145 /// A logger that writes to STDERR. This logger is not thread-safe.
147 /// Since this class is a RootLogger, it contains modifiable a log level
149 class StderrLogger : public RootLogger {
151 void do_log(Level, std::string) override final;
155 /// A logger that writes to a stream. This logger is not thread-safe.
157 /// Since this class is a RootLogger, it contains modifiable a log level
159 class StreamLogger : public RootLogger {
161 explicit StreamLogger(std::ostream&) noexcept;
164 void do_log(Level, std::string) override final;
171 /// A logger that writes to a file. This logger is not thread-safe.
173 /// Since this class is a RootLogger, it contains modifiable a log level
175 class FileLogger : public StreamLogger {
177 explicit FileLogger(std::string path);
178 explicit FileLogger(util::File);
182 util::File::Streambuf m_streambuf;
187 /// A thread-safe logger. This logger ignores the level threshold of the base
188 /// logger. Instead, it introduces new a LevelThreshold object with a fixed
189 /// value to achieve thread safety.
190 class ThreadSafeLogger : private Logger::LevelThreshold, public Logger {
192 explicit ThreadSafeLogger(Logger& base_logger, Level = Level::info);
195 void do_log(Level, std::string) override final;
198 const Level m_level_threshold; // Immutable for thread safety
199 Logger& m_base_logger;
201 Level get() const noexcept override final;
205 /// A logger that adds a fixed prefix to each message. This logger inherits the
206 /// LevelThreshold object of the specified base logger. This logger is
207 /// thread-safe if, and only if the base logger is thread-safe.
208 class PrefixLogger : public Logger {
210 PrefixLogger(std::string prefix, Logger& base_logger) noexcept;
213 void do_log(Level, std::string) override final;
216 const std::string m_prefix;
217 Logger& m_base_logger;
223 struct Logger::State {
224 Logger::Level m_level;
225 std::string m_message;
226 std::string m_search;
228 std::ostringstream m_formatter;
229 std::locale m_locale = std::locale::classic();
230 State(Logger::Level level, const char* s)
233 , m_search(m_message)
235 m_formatter.imbue(m_locale);
239 template <class... Params>
240 inline void Logger::trace(const char* message, Params&&... params)
242 log(Level::trace, message, std::forward<Params>(params)...); // Throws
245 template <class... Params>
246 inline void Logger::debug(const char* message, Params&&... params)
248 log(Level::debug, message, std::forward<Params>(params)...); // Throws
251 template <class... Params>
252 inline void Logger::detail(const char* message, Params&&... params)
254 log(Level::detail, message, std::forward<Params>(params)...); // Throws
257 template <class... Params>
258 inline void Logger::info(const char* message, Params&&... params)
260 log(Level::info, message, std::forward<Params>(params)...); // Throws
263 template <class... Params>
264 inline void Logger::warn(const char* message, Params&&... params)
266 log(Level::warn, message, std::forward<Params>(params)...); // Throws
269 template <class... Params>
270 inline void Logger::error(const char* message, Params&&... params)
272 log(Level::error, message, std::forward<Params>(params)...); // Throws
275 template <class... Params>
276 inline void Logger::fatal(const char* message, Params&&... params)
278 log(Level::fatal, message, std::forward<Params>(params)...); // Throws
281 template <class... Params>
282 inline void Logger::log(Level level, const char* message, Params&&... params)
284 if (would_log(level))
285 do_log(level, message, std::forward<Params>(params)...); // Throws
288 inline bool Logger::would_log(Level level) const noexcept
290 return int(level) >= int(level_threshold.get());
293 inline Logger::~Logger() noexcept
297 inline Logger::Logger(const LevelThreshold& lt) noexcept
298 : level_threshold(lt)
302 inline void Logger::do_log(Logger& logger, Level level, std::string message)
304 logger.do_log(level, std::move(message)); // Throws
307 template <class... Params>
308 void Logger::do_log(Level level, const char* message, Params&&... params)
310 State state(level, message);
311 log_impl(state, std::forward<Params>(params)...); // Throws
314 inline void Logger::log_impl(State& state)
316 do_log(state.m_level, std::move(state.m_message)); // Throws
319 template <class Param, class... Params>
320 inline void Logger::log_impl(State& state, Param&& param, Params&&... params)
322 subst(state, std::forward<Param>(param)); // Throws
323 log_impl(state, std::forward<Params>(params)...); // Throws
326 template <class Param>
327 void Logger::subst(State& state, Param&& param)
329 state.m_formatter << "%" << state.m_param_num;
330 std::string key = state.m_formatter.str();
331 state.m_formatter.str(std::string());
332 std::string::size_type j = state.m_search.find(key);
333 if (j != std::string::npos) {
334 state.m_formatter << std::forward<Param>(param);
335 std::string str = state.m_formatter.str();
336 state.m_formatter.str(std::string());
337 state.m_message.replace(j, key.size(), str);
338 state.m_search.replace(j, key.size(), std::string(str.size(), '\0'));
343 template <class C, class T>
344 std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& out, Logger::Level level)
347 case Logger::Level::all:
350 case Logger::Level::trace:
353 case Logger::Level::debug:
356 case Logger::Level::detail:
359 case Logger::Level::info:
362 case Logger::Level::warn:
365 case Logger::Level::error:
368 case Logger::Level::fatal:
371 case Logger::Level::off:
379 template <class C, class T>
380 std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>& in, Logger::Level& level)
382 std::basic_string<C, T> str;
383 auto check = [&](const char* name) {
384 size_t n = strlen(name);
387 for (size_t i = 0; i < n; ++i) {
388 if (in.widen(name[i]) != str[i])
395 level = Logger::Level::all;
397 else if (check("trace")) {
398 level = Logger::Level::trace;
400 else if (check("debug")) {
401 level = Logger::Level::debug;
403 else if (check("detail")) {
404 level = Logger::Level::detail;
406 else if (check("info")) {
407 level = Logger::Level::info;
409 else if (check("warn")) {
410 level = Logger::Level::warn;
412 else if (check("error")) {
413 level = Logger::Level::error;
415 else if (check("fatal")) {
416 level = Logger::Level::fatal;
418 else if (check("off")) {
419 level = Logger::Level::off;
422 in.setstate(std::ios_base::failbit);
428 inline void RootLogger::set_level_threshold(Level new_level_threshold) noexcept
430 m_level_threshold = new_level_threshold;
433 inline RootLogger::RootLogger()
434 : Logger::LevelThreshold()
435 , Logger(static_cast<Logger::LevelThreshold&>(*this))
439 inline Logger::Level RootLogger::get() const noexcept
441 return m_level_threshold;
444 inline void StderrLogger::do_log(Level level, std::string message)
446 std::cerr << get_level_prefix(level) << message << '\n'; // Throws
447 std::cerr.flush(); // Throws
450 inline StreamLogger::StreamLogger(std::ostream& out) noexcept
455 inline void StreamLogger::do_log(Level level, std::string message)
457 m_out << get_level_prefix(level) << message << '\n'; // Throws
458 m_out.flush(); // Throws
461 inline FileLogger::FileLogger(std::string path)
462 : StreamLogger(m_out)
463 , m_file(path, util::File::mode_Write) // Throws
464 , m_streambuf(&m_file) // Throws
465 , m_out(&m_streambuf) // Throws
469 inline FileLogger::FileLogger(util::File file)
470 : StreamLogger(m_out)
471 , m_file(std::move(file))
472 , m_streambuf(&m_file) // Throws
473 , m_out(&m_streambuf) // Throws
477 inline ThreadSafeLogger::ThreadSafeLogger(Logger& base_logger, Level threshold)
478 : Logger::LevelThreshold()
479 , Logger(static_cast<Logger::LevelThreshold&>(*this))
480 , m_level_threshold(threshold)
481 , m_base_logger(base_logger)
485 inline void ThreadSafeLogger::do_log(Level level, std::string message)
487 LockGuard l(m_mutex);
488 Logger::do_log(m_base_logger, level, message); // Throws
491 inline Logger::Level ThreadSafeLogger::get() const noexcept
493 return m_level_threshold;
496 inline PrefixLogger::PrefixLogger(std::string prefix, Logger& base_logger) noexcept
497 : Logger(base_logger.level_threshold)
498 , m_prefix(std::move(prefix))
499 , m_base_logger(base_logger)
503 inline void PrefixLogger::do_log(Level level, std::string message)
505 Logger::do_log(m_base_logger, level, m_prefix + message); // Throws
511 #endif // REALM_UTIL_LOGGER_HPP