Newer
Older
bremer-ios-app / Pods / Realm / core / realm-monorepo.xcframework / watchos-arm64_armv7k_arm64_32 / Headers / realm / util / logger.hpp
/*************************************************************************
 *
 * 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