C++ Logging Routines

C++ Logging Routines

I have standardized the logging for my current C++ projects. They operate as streams and look the same in the code regardless of the platform it is running on, be it a Linux PC or an Android device.

  1. On Linux the messages go to std::out.
  2. On Android the output is handled by the "__android_log_print" function.

The messages include the source file and line number, in addition to severity and the message itself. Under Android the platform's log format is used. Inspired my Android's format, Linux maintains the format, but outputs to std::cout instead.

I created the log message format a long time ago, and I recently started using External link CLion for C++ development. It is notable that these messages are interpreted by CLion as file locations. Clicking the message in CLion's display links to correct line in the file.

The source files follow.

Logging header file "u_log.h"

#pragma once
#include <cxxabi.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

#include <algorithm>
#include <codecvt>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <iterator>
#include <limits>
#include <locale>
#include <sstream>
#include <string>
#include <vector>

#define Q(x) #x
#define QUOTE(x) Q(x)

#ifdef FILE_ARG
#include QUOTE(FILE_ARG)
#endif

#if defined(ANDROID) || defined(__ANDROID__)
#include <android/log.h>
#endif

#define LOG_TAG "COM_THREEDBB"

namespace mystd {
std::string urlEncode(const std::string &str);
std::string urlDecode(const std::string &str);

template <typename T> std::string to_string(T value) {
  std::ostringstream os;
  os << value;
  return os.str();
}
template <typename T> std::string to_pointer(T value) {
  std::ostringstream os;
  os << static_cast<void *>(value);
  return os.str();
}
std::string toUTF8(const std::u16string &str);
} // namespace mystd

template <typename T> std::u16string to_u16string(T i) {
  std::wstring_convert<
      std::codecvt_utf8_utf16<char16_t, 0x10ffff, std::little_endian>, char16_t>
      conv;
  return conv.from_bytes(std::to_string(i));
}

float u16stof(std::u16string const &u16s);

template <typename T>
void fromUTF8(
    const std::string &source,
    std::basic_string<T, std::char_traits<T>, std::allocator<T>> &result) {
  std::wstring_convert<std::codecvt_utf8_utf16<T>, T> convertor;
  result = convertor.from_bytes(source);
}

///
/// \class ULog log.h
/// \brief Log stream class outputting formatted log messages to the approprate
/// target for the platform
///
class ULog {
private:
  int m_line;
  std::string m_file;
  std::string m_function;
  int m_logLevel;
  std::string m_slogLevel;
  std::stringstream m_ss;

public:
  ULog(int Xi_logLevel, std::string file, std::string function, int linenum);
  ULog(std::string level, std::string file, std::string function, int linenum);
  ~ULog();
  ULog &operator/(std::string const &Xi_val);
  template <typename T> ULog &operator<<(T const &Xi_val) {
    m_ss << Xi_val;
    return *this;
  }
  ULog &operator<<(const std::u16string &u16) {
    m_ss << mystd::toUTF8(u16);
    return *this;
  }
  ULog &operator<<(std::ostream &(*f)(std::ostream &)) {
    if (f == static_cast<decltype(f)>(std::endl)) {
      m_ss << '\n';
    }
    return *this;
  }
  std::ostream &os() { return m_ss; }
};

#if defined(ANDROID) || defined(__ANDROID__)
#define MY_LOG(LOG_LEVEL)                                                      \
  ULog(ANDROID_LOG_##LOG_LEVEL, __FILE__, __FUNCTION__, __LINE__)
#else
#define MY_LOG(LOG_LEVEL) ULog(#LOG_LEVEL, __FILE__, __FUNCTION__, __LINE__)
#endif

#define LVERBOSE MY_LOG(VERBOSE)
#define LTODO MY_LOG(TODO)
#define LDEBUG MY_LOG(DEBUG)
#define LINFO MY_LOG(INFO)
#define LWARN MY_LOG(WARN)
#define LERROR MY_LOG(ERROR)

Logging source file "u_log.cpp"

#include "u_log.h"

ULog::ULog(int Xi_logLevel, std::string file, std::string function,
           int linenum) {
  m_logLevel = Xi_logLevel;
  m_file = file.substr(file.find_last_of("\\/") + 1);
  m_function = function;
  m_line = linenum;
}

ULog::ULog(std::string level, std::string file, std::string function,
           int linenum) {
  m_slogLevel = level;
  m_file = file.substr(file.find_last_of("\\/") + 1);
  m_function = function;
  m_line = linenum;
}

ULog::~ULog() {
  std::stringstream f(m_ss.str());
  std::string s;
  while (getline(f, s, '\n')) {
#if defined(ANDROID) || defined(__ANDROID__)
    std::string ostr;
    ostr.append(m_file);
    ostr.append(":");
    ostr.append(m_function);
    ostr.append(":");
    ostr.append(mystd::to_string(m_line));
    ostr.append(":");
    ostr.append(s);
    __android_log_print(m_logLevel, LOG_TAG, "%s", ostr.c_str());
#else
    std::cout << m_slogLevel << ":" << m_file << ":" << m_line << ":"
              << m_function << ":" << s << "\n";
#endif
  }
}

// put string tokens on separate lines
ULog &ULog::operator/(std::string const &str) {
  std::istringstream iss(str);
  std::vector<std::string> tokens{std::istream_iterator<std::string>{iss},
                                  std::istream_iterator<std::string>{}};
  for (unsigned int i = 0; i < tokens.size() - 1; i++) {
    m_ss << tokens[i] << '\n';
  }
  int s = tokens.size();
  if (s > 0) {
    m_ss << tokens[s - 1];
  }
  return *this;
}

std::string mystd::toUTF8(const std::u16string &str) {
  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
  return convert.to_bytes(str);
}

// https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
std::string mystd::urlEncode(const std::string &str) {
  std::string new_str = "";
  char c;
  int ic;
  const char *chars = str.c_str();
  char bufHex[10];
  int len = strlen(chars);

  for (int i = 0; i < len; i++) {
    c = chars[i];
    ic = c;
    // uncomment this if you want to encode spaces with +
    /*if (c==' ') new_str += '+';
    else */
    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
      new_str += c;
    else {
      sprintf(bufHex, "%X", c);
      if (ic < 16)
        new_str += "%0";
      else
        new_str += "%";
      new_str += bufHex;
    }
  }
  return new_str;
}

// https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
std::string mystd::urlDecode(const std::string &str) {
  std::string ret;
  char ch;
  unsigned int i, ii, len = str.length();

  for (i = 0; i < len; i++) {
    if (str[i] != '%') {
      if (str[i] == '+')
        ret += ' ';
      else
        ret += str[i];
    } else {
      sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii);
      ch = static_cast<char>(ii);
      ret += ch;
      i = i + 2;
    }
  }
  return ret;
}

float u16stof(std::u16string const &u16s) {
  char buf[std::numeric_limits<float>::max_digits10 + 1];

  std::transform(std::begin(u16s), std::end(u16s), buf,
                 [](char16_t c) { return char(c); });

  buf[u16s.size()] = '\0'; // terminator

  // some error checking here?
  return std::strtof(buf, NULL);
}

Programming Samples C++ STL to JSON