Skip to content

Instantly share code, notes, and snippets.

@beantowel
Created September 21, 2023 10:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beantowel/114aa914e1b65e67e3dd794cb2893ce8 to your computer and use it in GitHub Desktop.
Save beantowel/114aa914e1b65e67e3dd794cb2893ce8 to your computer and use it in GitHub Desktop.
one file header only log for c++11

copy, paste and log!

one-file convenient code for simple logging.

  • c++11 compatible
  • header only
  • type-safe formatting

example

#include "onelog.h"

int main() {
    olog::stopwatch sw{};
    std::string s = "copy-paste and log";
    TRACE("{}", "hidden");
    ERROR("{}", s);

    olog::default_logger()->setLevel(olog::level::trace);
    TRACE("{}", "show msg (and location)");
    olog::default_logger()->setLevel(olog::level::warn);
    WARN("{}", "(hide location)");
    olog::default_logger()->setLevel(olog::level::debug);

    INFOF("old printf style format: %s", s.c_str());
    DEBUG("take {}ms", sw.elapsedMS());
    return 0;
}

output:

[Error] [15:00:49] [example.cpp:7] [thread 1]:
copy-paste and log
[Trace] [15:00:49] [example.cpp:10] [thread 1]:
show msg (and location)
[Warn] [15:00:49]:
(hide location)
[Info] [15:00:49] [example.cpp:15] [thread 1]:
old printf style format: copy-paste and log
[Debug] [15:00:49] [example.cpp:16] [thread 1]:
take 11.8347ms
#ifndef ONE_LOG_H
#define ONE_LOG_H
#include <chrono>
#include <cstdio>
#include <ctime>
#include <functional>
#include <iomanip>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include <vector>
namespace olog
{
class stopwatch
{
using Clock = std::chrono::steady_clock;
typename Clock::time_point mLast;
typename Clock::duration mElapsed;
bool isStart;
public:
stopwatch(bool start = true)
: mLast(Clock::now()), mElapsed(), isStart(start){};
void reset(bool start = true)
{
mLast = Clock::now();
mElapsed = Clock::duration::zero();
isStart = start;
}
void start()
{
mLast = Clock::now();
isStart = true;
}
void stop()
{
mElapsed += Clock::now() - mLast;
isStart = false;
}
typename Clock::duration elapsed() const
{
return isStart ? Clock::now() - mLast + mElapsed : mElapsed;
}
float elapsedMS() const
{
auto s =
std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed());
return s.count() / 1000000.f;
}
};
using argWrapper = std::function<void(std::ostream &)>;
inline std::string formatImpl(const std::string &fmt,
const std::vector<argWrapper> &wrappers)
{
std::ostringstream ss;
size_t start = 0;
for (size_t i = 0; i < wrappers.size(); i++)
{
auto pos = fmt.find("{}", start);
if (pos == std::string::npos)
{
break;
}
ss << fmt.substr(start, pos - start);
wrappers[i](ss);
start = pos + 2;
}
ss << fmt.substr(start);
return ss.str();
}
template <typename Arg>
inline argWrapper wrap(const Arg &arg)
{
return [&arg](std::ostream &ss) -> void
{
ss << arg;
};
}
template <typename... Args>
inline std::string format(const std::string &fmt, Args &&... args)
{
return formatImpl(fmt, {wrap(std::forward<Args>(args))...});
};
template <typename... Args>
inline std::string formatf(const std::string &fmt, Args &&... args)
{
int sz =
std::snprintf(nullptr, 0, fmt.c_str(), std::forward<Args>(args)...);
std::vector<char> buf(sz + 1);
std::snprintf(buf.data(), sz + 1, fmt.c_str(), std::forward<Args>(args)...);
return std::string{buf.data()};
}
enum class level : int
{
trace = 0,
debug = 1,
info = 2,
warn = 3,
error = 4,
fatal = 5,
};
extern const char *levelNames[];
struct source_loc
{
const std::string &file;
int line;
};
class logger
{
public:
logger();
void setLevel(level l);
bool filter(level l);
void write(source_loc loc, level l, const std::string &content);
// c++20 like format
template <typename... Args>
void log(source_loc loc, level l, const std::string &fmt, Args &&... args)
{
if (filter(l))
{
write(loc, l, format(fmt, std::forward<Args>(args)...));
}
}
template <typename... Args>
void log(level l, const std::string &fmt, Args &&... args)
{
log(source_loc{"", 0}, l, fmt, std::forward<Args>(args)...);
}
template <typename... Args>
void logf(source_loc loc, level l, const std::string &fmt,
Args &&... args)
{
if (filter(l))
{
write(loc, l, formatf(fmt, std::forward<Args>(args)...));
}
}
template <typename... Args>
void logf(level l, const std::string &fmt, Args &&... args)
{
logf(source_loc{"", 0}, l, fmt, std::forward<Args>(args)...);
}
private:
level mLevel;
};
inline logger *default_logger()
{
static logger *l = new logger();
return l;
}
inline std::string prefix(source_loc loc, level l, bool detail)
{
const char *levelNames[] = {
"Trace", "Debug", "Info", "Warn", "Error", "Fatal",
};
const char *levelColors[] = {
"\x1B[34m", // blue
"\x1B[36m", // cyan
"\x1B[32m", // green
"\x1B[33m", // yellow
"\x1B[31m", // red
"\x1B[35m", // magenta
};
char now[16];
auto t = std::time({});
std::strftime(now, sizeof(now), "%H:%M:%S", std::localtime(&t));
std::string p;
if (detail)
{
auto pos = loc.file.find_last_of("/\\");
p = format(
"{}[{}] [{}] [{}:{}] [thread {}]", levelColors[static_cast<int>(l)],
levelNames[static_cast<int>(l)], now,
(pos == std::string::npos ? loc.file : loc.file.substr(pos + 1)),
loc.line, std::this_thread::get_id());
}
else
{
p = format("{}[{}] [{}]", levelColors[static_cast<int>(l)],
levelNames[static_cast<int>(l)], now);
}
return p;
}
inline logger::logger() : mLevel(level::warn)
{
#ifndef NDEBUG
mLevel = level::debug;
#endif
auto s = std::getenv("PRNT_LEVEL");
if (s != nullptr)
{
mLevel = static_cast<level>(atoi(s));
}
}
inline void logger::setLevel(level l) { mLevel = l; }
inline bool logger::filter(level l)
{
if (l < mLevel)
{
return false;
}
return true;
}
inline void logger::write(source_loc loc, level l, const std::string &content)
{
constexpr char resetColor[] = "\033[0m";
if (l < mLevel)
{
return;
}
auto p = prefix(loc, l, mLevel < level::info);
printf("%s:\n%s%s\n", p.c_str(), content.c_str(), resetColor);
}
#define PRINT_LOG(logger, level, ...) \
(logger)->log(olog::source_loc{__FILE__, __LINE__}, (level), __VA_ARGS__)
#define TRACE(...) \
PRINT_LOG(olog::default_logger(), olog::level::trace, __VA_ARGS__)
#define DEBUG(...) \
PRINT_LOG(olog::default_logger(), olog::level::debug, __VA_ARGS__)
#define INFO(...) \
PRINT_LOG(olog::default_logger(), olog::level::info, __VA_ARGS__)
#define WARN(...) \
PRINT_LOG(olog::default_logger(), olog::level::warn, __VA_ARGS__)
#define ERROR(...) \
PRINT_LOG(olog::default_logger(), olog::level::error, __VA_ARGS__)
#define FATAL(...) \
PRINT_LOG(olog::default_logger(), olog::level::fatal, __VA_ARGS__)
#define PRINT_LOGF(logger, level, ...) \
(logger)->logf(olog::source_loc{__FILE__, __LINE__}, (level), __VA_ARGS__)
#define TRACEF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::trace, __VA_ARGS__)
#define DEBUGF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::debug, __VA_ARGS__)
#define INFOF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::info, __VA_ARGS__)
#define WARNF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::warn, __VA_ARGS__)
#define ERRORF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::error, __VA_ARGS__)
#define FATALF(...) \
PRINT_LOGF(olog::default_logger(), olog::level::fatal, __VA_ARGS__)
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment