• Docs >
  • Program Listing for File logger.hpp
Shortcuts

Program Listing for File logger.hpp

Return to documentation for file (include/ripple/utility/logger.hpp)

#ifndef RIPPLE_UTILITY_LOGGER_HPP
#define RIPPLE_UTILITY_LOGGER_HPP

#include "spinlock.hpp"
#include <fmt/chrono.h>
#include <fmt/format.h>
#include <array>
#include <cstddef>
#include <fstream>

namespace ripple {

static constexpr size_t logger_default_buffer_size = 1024;

static constexpr const char* const logfile_prefix = ".ripple_log";

enum class LogLevel : uint8_t {
  debug   = 0,
  info    = 1,
  warning = 2,
  error   = 3,
  none    = 4
};

template <
  LogLevel MinLevel,
  size_t   BufferSize = logger_default_buffer_size,
  typename LockPolicy = Spinlock>
class Logger;

/*==--- [alias for the logger] ---------------------------------------------==*/

using Log =
#if RIPPLE_LOG_LEVEL_NONE
  Logger<LogLevel::none>;
#elif RIPPLE_LOG_LEVEL_ERROR
  Logger<LogLevel::error>;
#elif RIPPLE_LOG_LEVEL_WARN
  Logger<LogLevel::warn>;
#elif RIPPLE_LOG_LEVEL_INFO
  Logger<LogLevel::info>;
#else
  Logger<LogLevel::debug>;
#endif

/*==--- [log functions] ----------------------------------------------------==*/

template <typename Fmt, typename... FmtArgs>
auto log_error(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void;

template <typename Fmt, typename... FmtArgs>
auto log_warn(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void;

template <typename Fmt, typename... FmtArgs>
auto log_info(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void;

template <typename Fmt, typename... FmtArgs>
auto log_debug(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void;

/*
 * Implementation of the logger class.
 * \tparam MinLevel   The minimum level for message logging.
 * \tparam BufferSize The size of the message buffer.
 * \tparam LockPolicy The locking policy for the logger.
 */
template <LogLevel MinLevel, size_t BufferSize, typename LockPolicy>
class Logger {
  static constexpr size_t buffer_end = BufferSize;
  using Buffer = std::array<char, buffer_end>;
  using Guard = std::lock_guard<LockPolicy>;

  template <LogLevel Level>
  using VoidLogEnable = std::enable_if_t<(Level < MinLevel), int>;

  template <LogLevel Level>
  using ValidLogEnable = std::enable_if_t<(Level >= MinLevel), int>;

 public:
  static constexpr LogLevel level = MinLevel;

  ~Logger() {
    flush();
    stream_.close();
  }

  // clang-format off
  Logger(Logger&&) noexcept                    = default;
  auto operator=(Logger&&) noexcept -> Logger& = default;

  Logger()                      = delete;
  Logger(const Logger&)         = delete;
  auto operator=(const Logger&) = delete;
  // clang-format on

  static auto logger() noexcept -> Logger& {
    static Logger logger_(logfile_prefix);
    return logger_;
  }

  template <LogLevel L>
  static constexpr auto would_log() noexcept -> bool {
    return L >= MinLevel;
  }

  auto flush() -> void {
    stream_.write(&buffer_[0], end_);
    end_ = 0;
  }

  template <LogLevel L, VoidLogEnable<L> = 0>
  auto log(const std::string& message) noexcept -> void {}

  template <LogLevel L, ValidLogEnable<L> = 0>
  void log(const std::string& message) {
    const auto rem = buffer_end - end_;

    // NOTE: Here we take the lock until the write is done. If we just take the
    // lock to increment the end of the buffer, then when we do the write into
    // the portion of the buffer there could be false sharing. Since this isn't
    // critical to perforamance, we just lock the whole operation.

    // message fits in remaining buffer:
    if (message.length() < rem) {
      Guard g(lock_);
      flush();
      end_ += sprintf(&buffer_[end_], "%s", message.c_str());
      return;
    }

    // message fits in whole buffer:
    if (message.length() < buffer_end) {
      Guard g(lock_);
      flush();
      end_ += sprintf(&buffer_[end_], "%s", message.c_str());
      return;
    }

    // message doesn't fit, write what we can and flush.
    Guard g(lock_);
    snprintf(&buffer_[end_], rem, "%s", message.c_str());
    flush();
  }

 private:
  Buffer        buffer_ = {};
  size_t        end_    = 0;
  std::ofstream stream_;
  LockPolicy    lock_;

  Logger(const std::string& log_file)
  : stream_(append_date(log_file), std::ios::trunc) {}

  auto append_date(const std::string& str) const noexcept -> std::string {
    return fmt::format(
      "{}_{:%Y-%m-%d}_{:%H:%M:%S}.txt",
      str.c_str(),
      fmt::localtime(std::time(nullptr)),
      std::chrono::floor<std::chrono::seconds>(
        std::chrono::high_resolution_clock::now().time_since_epoch()));
  }
};

/*==--- [log implemenations] -----------------------------------------------==*/

/*
 * \todo Add colored output for formatter.
 */

/*
 * Implementation of log_error.
 */
template <typename Fmt, typename... FmtArgs>
auto log_error(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void {
  if constexpr (Log::level <= LogLevel::error) {
    Log::logger().log<LogLevel::error>(fmt::format(
      "[Error] | {0:%H:%M:%S} | {1}\n",
      std::chrono::high_resolution_clock::now().time_since_epoch(),
      fmt::format(format, fmt_args...)));
  }
}

/*
 * Implementation of log_warn.
 */
template <typename Fmt, typename... FmtArgs>
auto log_warn(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void {
  if constexpr (Log::level <= LogLevel::warning) {
    Log::logger().log<LogLevel::warning>(fmt::format(
      "[Warn]  | {0:%H:%M:%S} | {1}\n",
      std::chrono::high_resolution_clock::now().time_since_epoch(),
      fmt::format(format, fmt_args...)));
  }
}

/*
 * Implementation of log_info.
 */
template <typename Fmt, typename... FmtArgs>
auto log_info(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void {
  if constexpr (Log::level <= LogLevel::info) {
    Log::logger().log<LogLevel::info>(fmt::format(
      "[Info]  | {0:%H:%M:%S} | {1}\n",
      std::chrono::high_resolution_clock::now().time_since_epoch(),
      fmt::format(format, fmt_args...)));
  }
}

/*
 * Implementation of log_debug.
 */
template <typename Fmt, typename... FmtArgs>
auto log_debug(Fmt&& format, FmtArgs&&... fmt_args) noexcept -> void {
  if constexpr (Log::level <= LogLevel::debug) {
    Log::logger().log<LogLevel::debug>(fmt::format(
      "[Debug] | {0:%H:%M:%S} | {1}\n",
      std::chrono::high_resolution_clock::now().time_since_epoch(),
      fmt::format(format, fmt_args...)));
  }
}

} // namespace ripple

#endif // RIPPLE_UTILITY_LOGGER_HPP

Docs

Access comprehensive developer documentation for Ripple

View Docs

Tutorials

Get tutorials to help with understand all features

View Tutorials

Examples

Find examples to help get started

View Examples