123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- /*************************************************************************
- *
- * Copyright 2016 Realm Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- **************************************************************************/
- #ifndef REALM_UTIL_LOGGER_HPP
- #define REALM_UTIL_LOGGER_HPP
- #include <realm/util/features.h>
- #include <realm/util/thread.hpp>
- #include <realm/util/file.hpp>
- #include <cstring>
- #include <ostream>
- #include <string>
- #include <utility>
- namespace realm::util {
- /// All messages logged with a level that is lower than the current threshold
- /// will be dropped. For the sake of efficiency, this test happens before the
- /// message is formatted. This class allows for the log level threshold to be
- /// changed over time and any subclasses will share the same reference to a
- /// log level threshold instance. The default log level threshold is
- /// Logger::Level::info and is defined by Logger::default_log_level.
- ///
- /// The Logger threshold level is intrinsically thread safe since it uses an
- /// atomic to store the value. However, the do_log() operation is not, so it
- /// is up to the subclass to ensure thread safety of the output operation.
- ///
- /// Examples:
- ///
- /// logger.error("Overlong message from master coordinator");
- /// logger.info("Listening for peers on %1:%2", listen_address, listen_port);
- class Logger {
- public:
- template <class... Params>
- void trace(const char* message, Params&&...);
- template <class... Params>
- void debug(const char* message, Params&&...);
- template <class... Params>
- void detail(const char* message, Params&&...);
- template <class... Params>
- void info(const char* message, Params&&...);
- template <class... Params>
- void warn(const char* message, Params&&...);
- template <class... Params>
- void error(const char* message, Params&&...);
- template <class... Params>
- void fatal(const char* message, Params&&...);
- /// Specifies criticality when passed to log(). Functions as a criticality
- /// threshold when returned from LevelThreshold::get().
- ///
- /// error Be silent unless when there is an error.
- /// warn Be silent unless when there is an error or a warning.
- /// info Reveal information about what is going on, but in a
- /// minimalistic fashion to avoid general overhead from logging
- /// and to keep volume down.
- /// detail Same as 'info', but prioritize completeness over minimalism.
- /// debug Reveal information that can aid debugging, no longer paying
- /// attention to efficiency.
- /// trace A version of 'debug' that allows for very high volume
- /// output.
- // equivalent to realm_log_level_e in realm.h and must be kept in sync -
- // this is enforced in logging.cpp.
- enum class Level { all = 0, trace = 1, debug = 2, detail = 3, info = 4, warn = 5, error = 6, fatal = 7, off = 8 };
- template <class... Params>
- void log(Level, const char* message, Params&&...);
- virtual Level get_level_threshold() const noexcept
- {
- // Don't need strict ordering, mainly that the gets/sets are atomic
- return m_level_threshold.load(std::memory_order_relaxed);
- }
- virtual void set_level_threshold(Level level) noexcept
- {
- // Don't need strict ordering, mainly that the gets/sets are atomic
- m_level_threshold.store(level, std::memory_order_relaxed);
- }
- /// Shorthand for `int(level) >= int(m_level_threshold)`.
- inline bool would_log(Level level) const noexcept
- {
- return static_cast<int>(level) >= static_cast<int>(get_level_threshold());
- }
- virtual inline ~Logger() noexcept = default;
- static void set_default_logger(std::shared_ptr<util::Logger>) noexcept;
- static std::shared_ptr<util::Logger>& get_default_logger() noexcept;
- static void set_default_level_threshold(Level level) noexcept;
- static Level get_default_level_threshold() noexcept;
- protected:
- // Used by subclasses that link to a base logger
- std::shared_ptr<Logger> m_base_logger_ptr;
- // Shared level threshold for subclasses that link to a base logger
- // See PrefixLogger and ThreadSafeLogger
- std::atomic<Level>& m_level_threshold;
- Logger() noexcept
- : m_level_threshold{m_threshold_base}
- , m_threshold_base{get_default_level_threshold()}
- {
- }
- explicit Logger(Level level) noexcept
- : m_level_threshold{m_threshold_base}
- , m_threshold_base{level}
- {
- }
- explicit Logger(const std::shared_ptr<Logger>& base_logger) noexcept
- : m_base_logger_ptr{base_logger}
- , m_level_threshold{m_base_logger_ptr->m_level_threshold}
- {
- }
- static void do_log(Logger&, Level, const std::string& message);
- virtual void do_log(Level, const std::string& message) = 0;
- static const char* get_level_prefix(Level) noexcept;
- private:
- // Only used by the base Logger class
- std::atomic<Level> m_threshold_base;
- template <class... Params>
- REALM_NOINLINE void do_log(Level, const char* message, Params&&...);
- };
- template <class C, class T>
- std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>&, Logger::Level);
- template <class C, class T>
- std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>&, Logger::Level&);
- /// A logger that writes to STDERR, which is thread safe.
- /// Since this class is a subclass of Logger, it maintains its own modifiable log
- /// level threshold.
- class StderrLogger : public Logger {
- public:
- StderrLogger() noexcept = default;
- StderrLogger(Level level) noexcept
- : Logger{level}
- {
- }
- protected:
- void do_log(Level, const std::string&) final;
- };
- /// A logger that writes to a stream. This logger is not thread-safe.
- ///
- /// Since this class is a subclass of Logger, it maintains its own modifiable log
- /// level threshold.
- class StreamLogger : public Logger {
- public:
- explicit StreamLogger(std::ostream&) noexcept;
- protected:
- void do_log(Level, const std::string&) final;
- private:
- std::ostream& m_out;
- };
- /// A logger that writes to a new file. This logger is not thread-safe.
- ///
- /// Since this class is a subclass of Logger, it maintains its own thread safe log
- /// level threshold.
- class FileLogger : public StreamLogger {
- public:
- explicit FileLogger(std::string path);
- explicit FileLogger(util::File);
- private:
- util::File m_file;
- util::File::Streambuf m_streambuf;
- std::ostream m_out;
- };
- /// A logger that appends to a file. This logger is not thread-safe.
- ///
- /// Since this class is a subclass of Logger, it maintains its own thread safe log
- /// level threshold.
- class AppendToFileLogger : public StreamLogger {
- public:
- explicit AppendToFileLogger(std::string path);
- explicit AppendToFileLogger(util::File);
- private:
- util::File m_file;
- util::File::Streambuf m_streambuf;
- std::ostream m_out;
- };
- /// A thread-safe logger where do_log() is thread safe. The log level is already
- /// thread safe since Logger uses an atomic to store the log level threshold.
- class ThreadSafeLogger : public Logger {
- public:
- explicit ThreadSafeLogger(const std::shared_ptr<Logger>& base_logger) noexcept;
- protected:
- void do_log(Level, const std::string&) final;
- private:
- Mutex m_mutex;
- };
- /// A logger that adds a fixed prefix to each message.
- class PrefixLogger : public Logger {
- public:
- // A PrefixLogger must initially be created from a base Logger shared_ptr
- PrefixLogger(std::string prefix, const std::shared_ptr<Logger>& base_logger) noexcept;
- // Used for chaining a series of prefixes together for logging that combines prefix values
- PrefixLogger(std::string prefix, PrefixLogger& chained_logger) noexcept;
- protected:
- void do_log(Level, const std::string&) final;
- private:
- const std::string m_prefix;
- // The next logger in the chain for chained PrefixLoggers or the base_logger
- Logger& m_chained_logger;
- };
- // Logger with a local log level that is independent of the parent log level threshold
- // The LocalThresholdLogger will define its own atomic log level threshold and
- // will be unaffected by changes to the log level threshold of the parent.
- // In addition, any changes to the log level threshold of this class or any
- // subsequent linked loggers will not change the log level threshold of the
- // parent. The parent will only be used for outputting log messages.
- class LocalThresholdLogger : public Logger {
- public:
- // A shared_ptr parent must be provided for this class for log output
- // Local log level is initialized with the current value from the provided logger
- LocalThresholdLogger(const std::shared_ptr<Logger>&);
- // A shared_ptr parent must be provided for this class for log output
- LocalThresholdLogger(const std::shared_ptr<Logger>&, Level);
- void do_log(Logger::Level level, std::string const& message) override;
- protected:
- std::shared_ptr<Logger> m_chained_logger;
- };
- /// A logger that essentially performs a noop when logging functions are called
- /// The log level threshold for this logger is always Logger::Level::off and
- /// cannot be changed.
- class NullLogger : public Logger {
- public:
- NullLogger()
- : Logger{Level::off}
- {
- }
- Level get_level_threshold() const noexcept override
- {
- return Level::off;
- }
- void set_level_threshold(Level) noexcept override {}
- protected:
- // Since we don't want to log anything, do_log() does nothing
- void do_log(Level, const std::string&) override {}
- };
- // Implementation
- template <class... Params>
- inline void Logger::trace(const char* message, Params&&... params)
- {
- log(Level::trace, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::debug(const char* message, Params&&... params)
- {
- log(Level::debug, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::detail(const char* message, Params&&... params)
- {
- log(Level::detail, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::info(const char* message, Params&&... params)
- {
- log(Level::info, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::warn(const char* message, Params&&... params)
- {
- log(Level::warn, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::error(const char* message, Params&&... params)
- {
- log(Level::error, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::fatal(const char* message, Params&&... params)
- {
- log(Level::fatal, message, std::forward<Params>(params)...); // Throws
- }
- template <class... Params>
- inline void Logger::log(Level level, const char* message, Params&&... params)
- {
- if (would_log(level))
- do_log(level, message, std::forward<Params>(params)...); // Throws
- #if REALM_DEBUG
- else {
- // Do the string formatting even if it won't be logged to hopefully
- // catch invalid format strings
- static_cast<void>(format(message, std::forward<Params>(params)...)); // Throws
- }
- #endif
- }
- inline void Logger::do_log(Logger& logger, Level level, const std::string& message)
- {
- logger.do_log(level, std::move(message)); // Throws
- }
- template <class... Params>
- void Logger::do_log(Level level, const char* message, Params&&... params)
- {
- do_log(level, format(message, std::forward<Params>(params)...)); // Throws
- }
- template <class C, class T>
- std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& out, Logger::Level level)
- {
- switch (level) {
- case Logger::Level::all:
- out << "all";
- return out;
- case Logger::Level::trace:
- out << "trace";
- return out;
- case Logger::Level::debug:
- out << "debug";
- return out;
- case Logger::Level::detail:
- out << "detail";
- return out;
- case Logger::Level::info:
- out << "info";
- return out;
- case Logger::Level::warn:
- out << "warn";
- return out;
- case Logger::Level::error:
- out << "error";
- return out;
- case Logger::Level::fatal:
- out << "fatal";
- return out;
- case Logger::Level::off:
- out << "off";
- return out;
- }
- REALM_ASSERT(false);
- return out;
- }
- template <class C, class T>
- std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>& in, Logger::Level& level)
- {
- std::basic_string<C, T> str;
- auto check = [&](const char* name) {
- size_t n = strlen(name);
- if (n != str.size())
- return false;
- for (size_t i = 0; i < n; ++i) {
- if (in.widen(name[i]) != str[i])
- return false;
- }
- return true;
- };
- if (in >> str) {
- if (check("all")) {
- level = Logger::Level::all;
- }
- else if (check("trace")) {
- level = Logger::Level::trace;
- }
- else if (check("debug")) {
- level = Logger::Level::debug;
- }
- else if (check("detail")) {
- level = Logger::Level::detail;
- }
- else if (check("info")) {
- level = Logger::Level::info;
- }
- else if (check("warn")) {
- level = Logger::Level::warn;
- }
- else if (check("error")) {
- level = Logger::Level::error;
- }
- else if (check("fatal")) {
- level = Logger::Level::fatal;
- }
- else if (check("off")) {
- level = Logger::Level::off;
- }
- else {
- in.setstate(std::ios_base::failbit);
- }
- }
- return in;
- }
- inline StreamLogger::StreamLogger(std::ostream& out) noexcept
- : m_out(out)
- {
- }
- inline FileLogger::FileLogger(std::string path)
- : StreamLogger(m_out)
- , m_file(path, util::File::mode_Write) // Throws
- , m_streambuf(&m_file) // Throws
- , m_out(&m_streambuf) // Throws
- {
- }
- inline FileLogger::FileLogger(util::File file)
- : StreamLogger(m_out)
- , m_file(std::move(file))
- , m_streambuf(&m_file) // Throws
- , m_out(&m_streambuf) // Throws
- {
- }
- inline AppendToFileLogger::AppendToFileLogger(std::string path)
- : StreamLogger(m_out)
- , m_file(path, util::File::mode_Append) // Throws
- , m_streambuf(&m_file) // Throws
- , m_out(&m_streambuf) // Throws
- {
- }
- inline AppendToFileLogger::AppendToFileLogger(util::File file)
- : StreamLogger(m_out)
- , m_file(std::move(file))
- , m_streambuf(&m_file) // Throws
- , m_out(&m_streambuf) // Throws
- {
- }
- inline ThreadSafeLogger::ThreadSafeLogger(const std::shared_ptr<Logger>& base_logger) noexcept
- : Logger(base_logger)
- {
- }
- // Construct a PrefixLogger from another PrefixLogger object for chaining the prefixes on log output
- inline PrefixLogger::PrefixLogger(std::string prefix, PrefixLogger& prefix_logger) noexcept
- // Save an alias of the base_logger shared_ptr from the passed in PrefixLogger
- : Logger(prefix_logger.m_base_logger_ptr)
- , m_prefix{std::move(prefix)}
- , m_chained_logger{prefix_logger} // do_log() writes to the chained logger
- {
- }
- // Construct a PrefixLogger from any Logger shared_ptr (PrefixLogger, StdErrLogger, etc.)
- // The first PrefixLogger must always be created from a Logger shared_ptr, subsequent PrefixLoggers
- // created, will point back to this logger shared_ptr for referencing the level_threshold when
- // logging output.
- inline PrefixLogger::PrefixLogger(std::string prefix, const std::shared_ptr<Logger>& base_logger) noexcept
- : Logger(base_logger) // Save an alias of the passed in base_logger shared_ptr
- , m_prefix{std::move(prefix)}
- , m_chained_logger{*base_logger} // do_log() writes to the chained logger
- {
- }
- // Construct a LocalThresholdLogger using the current log level value from the parent
- inline LocalThresholdLogger::LocalThresholdLogger(const std::shared_ptr<Logger>& base_logger)
- : Logger(base_logger->get_level_threshold())
- , m_chained_logger{base_logger}
- {
- }
- // Construct a LocalThresholdLogger using the provided log level threshold value
- inline LocalThresholdLogger::LocalThresholdLogger(const std::shared_ptr<Logger>& base_logger, Level threshold)
- : Logger(threshold)
- , m_chained_logger{base_logger}
- {
- // Verify the passed in shared ptr is not null
- REALM_ASSERT(m_chained_logger);
- }
- } // namespace realm::util
- #endif // REALM_UTIL_LOGGER_HPP
|