Program Listing for File Log.cpp

Return to documentation for file (Source\Log\Src\Log.cpp)

#include "Log/Log.h"

#include <iostream>
#include <cstdlib>
#include <utility>

#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>

#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/attributes/named_scope.hpp>

#include "yaml-cpp/yaml.h"
#include "Utils/Macros.h"

namespace fs = boost::filesystem;

const int DEFAULT_LOG_BUFFER_SIZE = 4096;

namespace Azura {

namespace {
void OneTimeSetup(const YAML::Node& settingsNode, const boost::log::formatter& logFmt) {

  const auto logKeyStreams         = settingsNode["Streams"];
  const auto flushLogs             = settingsNode["AutoFlush"].as<bool>();
  const String outputDir           = settingsNode["OutputDir"].as<String>();
  const fs::path fileLogOutputPath = fs::path(outputDir);

  for (YAML::const_iterator itr = logKeyStreams.begin(); itr != logKeyStreams.end(); ++itr) {
    const String logKeyStream   = itr->as<String>();

    if (logKeyStream == "File") {
      const fs::path finalPath = fileLogOutputPath / fs::path("DefaultStaticLog.log");
      auto fsSink              = boost::log::add_file_log(finalPath.string());
      fsSink->locked_backend()->auto_flush(flushLogs);
      fsSink->set_formatter(logFmt);
    } else if (logKeyStream == "Console") {
      auto consoleSink = boost::log::add_console_log(std::clog);
      consoleSink->set_formatter(logFmt);
    }
  }
}
} // namespace

Log::Log(String key)
  : m_debugLevel(0),
    m_infoLevel(0),
    m_warningLevel(0),
    m_errorLevel(0),
    m_fatalLevel(0),
    m_key(std::move(key)),
    m_isReady(false),
    m_temporaryBuffer(4096) {

#ifdef BUILD_DEBUG

  const char* configEnv = std::getenv("AZURA_CONFIG");
  if (configEnv == nullptr) {
    std::cout << "No Azura Config Specified. Log Module will run in Default Mode. \n";
    return;
  }

  const fs::path configDir(configEnv);
  if (!is_directory(configDir)) {
    std::cout << "Azura Config Directory not present. Log Module will run in Default Mode. \n";
    return;
  }

  const fs::path fullPath = configDir / fs::path("log.yml");
  if (!fs::exists(fullPath)) {
    return;
  }
#elif defined(BUILD_RELEASE)
  const fs::path fullPath = fs::path("config") / fs::path("log.yml");
#endif

  try {
    boost::log::add_common_attributes();
    boost::log::core::get()->add_global_attribute("Scope", boost::log::attributes::named_scope());

    auto fmtTimeStamp = boost::log::expressions::format_date_time<boost::posix_time::ptime>("TimeStamp",
                                                                                            "%Y-%m-%d %H:%M:%S.%f");
    auto fmtThreadId = boost::log::expressions::attr<boost::log::attributes::current_thread_id::value_type>("ThreadID");
    auto fmtSeverity = boost::log::expressions::attr<boost::log::trivial::severity_level>("Severity");

    auto fmtScope = boost::log::
      expressions::format_named_scope("Scope",
                                      boost::log::keywords::format    = "%n(%f:%l)",
                                      boost::log::keywords::iteration = boost::log::
                                      expressions::reverse,
                                      boost::log::keywords::depth = 2);

    const boost::log::formatter logFmt =
      boost::log::expressions::format("[%1%] (%2%) [%3%] [%4%] %5%")
      % fmtTimeStamp % fmtThreadId % fmtSeverity % fmtScope
      % boost::log::expressions::smessage;

    static const YAML::Node s_mainConfig = [&]() -> YAML::Node
    {
      YAML::Node mainConfig = YAML::LoadFile(fullPath.string());
      if (!mainConfig) {
        std::cout << "Invalid YAML in log.yml. \n";
        return {};
      }

      const YAML::Node settingsNode = mainConfig["Settings"];
      OneTimeSetup(settingsNode, logFmt);

      return mainConfig;
    }();

    if (s_mainConfig.IsNull()) {
      return;
    }

    bool isActive   = false;
    bool hasDefault = false;
    YAML::Node logNode;
    YAML::Node defaultLogNode;
    YAML::Node logStreamsNode = s_mainConfig["LogStreams"];
    YAML::Node settingsNode   = s_mainConfig["Settings"];

    // Format of Log Stream:
    //  Settings:
    //    AutoFlush: true
    //
    //  LogStreams:
    //    VkRenderer:
    //      Levels: D0 I30 W50 E0 F0
    //      Streams:
    //        - File
    //        - Console
    //
    //    Common:
    //      Levels: D0 I50 W50 E0 F0
    //      Streams:
    //        - File
    //        - Console
    for (YAML::const_iterator itr = logStreamsNode.begin(); itr != logStreamsNode.end(); ++itr) {
      const String activeLogKey   = itr->first.as<String>();

      if (m_key == activeLogKey) {
        logNode  = itr->second;
        isActive = true;
      } else if (activeLogKey == "Default") {
        hasDefault     = true;
        defaultLogNode = itr->second;
      }
    }

    if (!isActive && !hasDefault) {
      return;
    }

    // Take Default if present & key not present
    if (!isActive && hasDefault) {
      logNode = defaultLogNode;
    }

    const auto levelString = logNode["Levels"].as<String>();
    ParseLevelString(levelString);

    m_isReady = true;
  } catch (const std::runtime_error& error) {
    std::cout << "Parsing Log File Failed: " << error.what();
  }
}

void Log::ParseLevelString(const String& levelStr) {
  std::vector<String> results;
  boost::split(results, levelStr, [](const char character) { return character == ' '; });

  for (const auto& logLevel : results) {
    const char index      = toupper(logLevel[0]);
    String level          = logLevel.substr(1, logLevel.length() - 1);
    const auto levelValue = U8(std::stoi(level));

    switch (index) {
      case 'D':
        m_debugLevel = levelValue;
        break;

      case 'I':
        m_infoLevel = levelValue;
        break;

      case 'W':
        m_warningLevel = levelValue;
        break;

      case 'E':
        m_errorLevel = levelValue;
        break;

      case 'F':
        m_fatalLevel = levelValue;
        break;

      default:
        std::cout << "Invalid Log Levels in Stream. Check log config file";
        break;
    }
  }
}

void Log::Debug(U32 level, const char* fmt, ...) const {
  if (!m_isReady || level < m_debugLevel) {
    return;
  }

  va_list arg;
  va_start(arg, fmt);
  const int bufferLength = std::min(1 + std::vsnprintf(nullptr, 0, fmt, arg), DEFAULT_LOG_BUFFER_SIZE);
  std::vsnprintf(const_cast<char *>(m_temporaryBuffer.data()), bufferLength, fmt, arg);
  va_end(arg);

  BOOST_LOG_TRIVIAL(debug) << m_temporaryBuffer.data();
}

void Log::Info(U32 level, const char* fmt, ...) const {
  if (!m_isReady || level < m_infoLevel) {
    return;
  }

  va_list arg;
  va_start(arg, fmt);
  const int bufferLength = std::min(1 + std::vsnprintf(nullptr, 0, fmt, arg), DEFAULT_LOG_BUFFER_SIZE);
  std::vsnprintf(const_cast<char *>(m_temporaryBuffer.data()), bufferLength, fmt, arg);
  va_end(arg);

  BOOST_LOG_TRIVIAL(info) << m_temporaryBuffer.data();
}


void Log::Warning(U32 level, const char* fmt, ...) const {
  if (!m_isReady || level < m_warningLevel) {
    return;
  }

  va_list arg;
  va_start(arg, fmt);
  const int bufferLength = std::min(1 + std::vsnprintf(nullptr, 0, fmt, arg), DEFAULT_LOG_BUFFER_SIZE);
  std::vsnprintf(const_cast<char *>(m_temporaryBuffer.data()), bufferLength, fmt, arg);
  va_end(arg);

  BOOST_LOG_TRIVIAL(warning) << m_temporaryBuffer.data();
}

void Log::Error(U32 level, const char* fmt, ...) const {
  if (!m_isReady || level < m_errorLevel) {
    return;
  }

  va_list arg;
  va_start(arg, fmt);
  const int bufferLength = std::min(1 + std::vsnprintf(nullptr, 0, fmt, arg), DEFAULT_LOG_BUFFER_SIZE);
  std::vsnprintf(const_cast<char *>(m_temporaryBuffer.data()), bufferLength, fmt, arg);
  va_end(arg);

  BOOST_LOG_TRIVIAL(error) << m_temporaryBuffer.data();
}

void Log::Fatal(U32 level, const char* fmt, ...) const {
  if (!m_isReady || level < m_fatalLevel) {
    return;
  }

  va_list arg;
  va_start(arg, fmt);
  const int bufferLength = std::min(1 + std::vsnprintf(nullptr, 0, fmt, arg), DEFAULT_LOG_BUFFER_SIZE);
  std::vsnprintf(const_cast<char *>(m_temporaryBuffer.data()), bufferLength, fmt, arg);
  va_end(arg);

  BOOST_LOG_TRIVIAL(fatal) << m_temporaryBuffer.data();
}
} // namespace Azura