Program Listing for File logger.hpp

Return to documentation for file (include/wrench/log/logger.hpp)

//==--- wrench/log/logger.hpp ------------------------------ -*- C++ -*- ---==//
//
//                                Wrench
//
//                      Copyright (c) 2020 Ripple
//
//  This file is distributed under the MIT License. See LICENSE for details.
//
//==------------------------------------------------------------------------==//
//
//
//==------------------------------------------------------------------------==//

#ifndef WRENCH_LOG_LOGGER_HPP
#define WRENCH_LOG_LOGGER_HPP

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

namespace wrench {

static constexpr size_t logger_default_buffer_size = 1024;

static constexpr const char* const logfile_path = ".wrench_log.txt";

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 WRENCH_LOG_LEVEL_NONE
  Logger<LogLevel::none>;
#elif WRENCH_LOG_LEVEL_ERROR
  Logger<LogLevel::error>;
#elif WRENCH_LOG_LEVEL_WARN
  Logger<LogLevel::warn>;
#elif WRENCH_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) -> void;

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

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

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

//==--- [implementation] ---------------------------------------------------==//

// 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:
  //==--- [constants] ------------------------------------------------------==//

  static constexpr LogLevel level = MinLevel;

  //==--- [construction] ---------------------------------------------------==//

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

  //==--- [defaulted] ------------------------------------------------------==//

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

  //==--- [deleted] --------------------------------------------------------==//

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

  //==--- [interface] ------------------------------------------------------==//

  static auto logger() noexcept -> Logger& {
    static Logger l(logfile_path);
    return l;
  }

  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_);
      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_t      buffer_ = {};
  size_t        end_    = 0;
  std::ofstream stream_;
  std::mutex    lock_;

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

//==--- [log macros] -------------------------------------------------------==//

// Implementation of log_error.
template <typename Fmt, typename... FmtArgs>
auto log_error(Fmt&& format, FmtArgs&&... fmt_args) -> 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) -> 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) -> 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) -> 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 wrench

#endif // WRENCH_LOG_LOGGER_HPP