Skip to content

Instantly share code, notes, and snippets.

@kice
Last active October 27, 2022 07:31
Show Gist options
  • Save kice/cd2c254309349c7129edb38cded0a909 to your computer and use it in GitHub Desktop.
Save kice/cd2c254309349c7129edb38cded0a909 to your computer and use it in GitHub Desktop.
<fmt> logging library
#pragma once
#ifdef _DEBUG
#define FMTLOG_ENABLE
#endif
#include <string>
#include <functional>
#define FMT_HEADER_ONLY
#include "fmt/format.h"
#include "fmt/xchar.h"
#define LOG_LEVEL_TRACE 0
#define LOG_LEVEL_DEBUG 1
#define LOG_LEVEL_INFO 2
#define LOG_LEVEL_WARN 3
#define LOG_LEVEL_ERROR 4
#define LOG_LEVEL_CRITICAL 5
#define LOG_LEVEL_OFF 6
#define LOG_LEVEL_ALWAYS 7
namespace fmtlog
{
#ifdef FMTLOG_NO_CALLBACK
template<typename ...Args>
constexpr auto filter(Args&&... t) -> bool { return true; }
template<typename ...Args>
constexpr auto callback(Args&&... t) {}
#else
bool filter(int level, const std::string_view category);
void callback(int level, const std::string_view category, const char *location, const std::string_view message);
void callback(int level, const std::string_view category, const wchar_t *location, const std::wstring_view message);
#endif // FMTLOG_NO_CALLBACK
#ifdef FMTLOG_CALLBACK_STDOUT
inline bool filter(int level, const std::string_view category)
{
return true;
}
inline void callback(int level, const std::string_view category, const char *location, const std::string_view message)
{
fprintf(stdout, "%.*s\n", message.size(), message.data());
}
inline void callback(int level, const std::string_view category, const wchar_t *location, const std::wstring_view message)
{
// expect to be ???? for non-ascii char on cmd.exe
fwprintf(stdout, L"%.*s\n", message.size(), message.data());
}
#endif
};
namespace fmtlog
{
enum loglevel
{
trace = LOG_LEVEL_TRACE,
debug = LOG_LEVEL_DEBUG,
info = LOG_LEVEL_INFO,
warn = LOG_LEVEL_WARN,
error = LOG_LEVEL_ERROR,
critical = LOG_LEVEL_CRITICAL,
off = LOG_LEVEL_OFF,
always = LOG_LEVEL_ALWAYS,
n_levels,
};
namespace details
{
#ifdef _WIN32
#include <Windows.h>
#define CP_SHIFTJIS 932
#define CP_GBK 936
#define CP_BIG5 950
#define CP_UTF8 65001
#define CP_UTF16 1200
inline int _wtoa(const wchar_t *w, char *a, int chars, UINT codepage = CP_THREAD_ACP)
{
return WideCharToMultiByte(codepage, 0, w, -1, a, (int)(a ? chars : 0), 0, 0);
}
inline int _atow(const char *a, wchar_t *w, int chars, UINT codepage = CP_THREAD_ACP)
{
return MultiByteToWideChar(codepage, 0, a, -1, w, (int)(w ? chars : 0));
}
#else
#define HIGH_SURROGATE_START 0xd800
#define HIGH_SURROGATE_END 0xdbff
#define LOW_SURROGATE_START 0xdc00
#define LOW_SURROGATE_END 0xdfff
#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && ((wch) <= HIGH_SURROGATE_END))
#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && ((wch) <= LOW_SURROGATE_END))
#define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && IS_LOW_SURROGATE(ls))
static inline int _wtoa(const wchar_t *w, char *a, int chars, UINT codepage = 0)
{
return wcstombs(a, w, chars - 1) + 1;
}
static inline int _atow(const char *a, wchar_t *w, int chars, UINT codepage = 0)
{
return mbstowcs(w, a, chars - 1) + 1;
}
#endif // _WIN32
// convert ANSI to utf-16le
inline std::wstring atow(const std::string_view &string)
{
int len = _atow(string.data(), 0, 0);
wchar_t *buffer = new wchar_t[len];
memset(buffer, 0, len * sizeof(*buffer));
_atow(string.data(), buffer, (int)len);
std::wstring s = buffer;
delete[] buffer;
return s;
}
// on windows, wchar_t should be stored as UTF-16LE
inline std::wstring u8tow(const std::string_view &string)
{
int len = _atow(string.data(), 0, 0, CP_UTF8);
wchar_t *buffer = new wchar_t[len];
memset(buffer, 0, len * sizeof(*buffer));
_atow(string.data(), buffer, (int)len, CP_UTF8);
std::wstring s = buffer;
delete[] buffer;
return s;
}
// convert utf-16le to ANSI
inline std::string wtoa(const std::wstring_view &string)
{
int len = _wtoa(string.data(), 0, 0);
char *buffer = new char[len];
memset(buffer, 0, len * sizeof(*buffer));
_wtoa(string.data(), buffer, (int)len);
std::string s = buffer;
delete[] buffer;
return s;
}
// convert utf-16le to utf-8
inline std::string wtou8(const std::wstring_view &string)
{
int len = _wtoa(string.data(), 0, 0, CP_UTF8);
char *buffer = new char[len];
memset(buffer, 0, len * sizeof(*buffer));
_wtoa(string.data(), buffer, (int)len, CP_UTF8);
std::string s = buffer;
delete[] buffer;
return s;
}
#if __cpp_nontype_template_args >= 201911L and !defined(FMTLOG_FULLPATH)
#define __FMTLOG_USE_FILENAME_LITERIAL
template<typename _Elem, size_t N>
struct FileName
{
_Elem fname[N]{ 0 };
constexpr FileName(const _Elem(&fp)[N])
{
#if _WIN32
constexpr auto path_sep = (_Elem)'\\';
#else
constexpr auto path_sep = (_Elem)'/';
#endif
_Elem rfname[N]{ 0 };
auto pos = 0;
for (int i = N - 1; i >= 0; --i) {
if (fp[i] == '"') {
continue;
}
if (fp[i] == path_sep) {
break;
}
rfname[pos++] = fp[i];
}
for (int i = 0; pos > 0; ++i) {
fname[i] = rfname[--pos];
}
}
};
#endif
}
namespace string_type
{
template <typename T> struct is_char : std::false_type {};
template <> struct is_char<char> : std::true_type {};
template <> struct is_char<wchar_t> : std::true_type {};
#if __cpp_lib_char8_t >= 201907L
template <> struct is_char<char8_t> : std::true_type {};
#endif
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
template <typename Char, std::enable_if_t<(is_char<Char>::value), int> = 0>
inline auto to_string_view(const Char *s)
-> std::basic_string_view<Char>
{
return s;
}
template <typename Char, typename Traits, typename Alloc>
inline auto to_string_view(const std::basic_string<Char, Traits, Alloc> &s)
-> std::basic_string_view<Char>
{
return s;
}
template <typename Char>
constexpr auto to_string_view(std::basic_string_view<Char> s)
-> std::basic_string_view<Char>
{
return s;
}
#if defined(FMT_VERSION)
template <typename S, std::enable_if_t<(fmt::is_compile_string<S>::value), int> = 0>
constexpr auto to_string_view(const S &s)
-> std::basic_string_view<typename S::char_type>
{
return {};
}
#endif
void to_string_view(...);
template<typename char_type>
struct string_mapper
{
using string_view_t = std::basic_string_view<char_type>;
constexpr inline auto map(char_type *val) -> const char_type *
{
return val;
}
constexpr inline auto map(const char_type *val) -> const char_type *
{
return val;
}
template <typename S>
struct _is_string
: std::is_same<decltype(to_string_view(std::declval<const S &>())), string_view_t>
{
};
template <typename T, std::enable_if_t<(_is_string<T>::value), int> = 0>
constexpr inline auto map(const T &val)-> string_view_t
{
return to_string_view(val);
}
struct not_string {};
auto map(...) -> not_string { return {}; }
};
//
//template<typename T, typename Char>
//using test_type = decltype(string_mapper<Char>().map(std::declval<const T &>()));
//
//using a_type = test_type<std::string, char>;
//static_assert(std::is_same_v<a_type, std::string_view>);
template <typename S, class char_type, class string_type>
struct is_string_t
: std::is_same<decltype(string_mapper<char_type>().map(std::declval<const S &>())),
string_type>
{
};
template <typename S>
struct is_cwstring : is_string_t<S, wchar_t, const wchar_t *> {};
template <typename S>
struct is_wstring : is_string_t<S, wchar_t, std::wstring_view> {};
template <typename S>
struct is_cstring : is_string_t<S, char, const char *> {};
template <typename S>
struct is_string : is_string_t<S, char, std::string_view> {};
}
template<class F>
struct category_logger
{
F f;
bool invoke = true;
explicit category_logger(F &&_f) noexcept : f(_f)
{
}
category_logger(category_logger &&other) noexcept
: f(std::move(other.f)), invoke(std::exchange(other.invoke, false))
{
}
category_logger(const category_logger &) = delete;
category_logger &operator=(const category_logger &) = delete;
category_logger &operator=(category_logger &&) = delete;
~category_logger()
{
if (invoke) {
f(std::string_view());
}
}
template<typename T>
void operator()(const T &category)
{
if (invoke) {
f(category);
}
invoke = false;
}
};
struct empty_logger
{
template<typename T>
constexpr void operator()(T &&) const noexcept
{
}
};
template<typename _Char>
inline void vlog(int level, const std::string_view category,
const _Char *location, const _Char *func,
const fmt::basic_string_view<_Char> format, bool func_tag,
fmt::basic_format_args<fmt::buffer_context<_Char>> args)
{
fmt::basic_memory_buffer<_Char> out;
if constexpr (std::is_same_v<_Char, wchar_t>) {
if (func_tag) {
fmt::format_to(std::back_inserter(out), FMT_STRING(L"{} [{}]: "), location, func);
} else {
fmt::format_to(std::back_inserter(out), FMT_STRING(L"{}: "), location);
}
} else {
if (func_tag) {
fmt::format_to(std::back_inserter(out), FMT_STRING("{} [{}]: "), location, func);
} else {
fmt::format_to(std::back_inserter(out), FMT_STRING("{}: "), location);
}
}
fmt::detail::vformat_to(out, format, args);
callback(level, category, location, fmt::to_string(out));
}
#if __cpp_nontype_template_args >= 201911L
template<typename _Elem, size_t N>
struct VariableName
{
_Elem str[N]{ 0 };
constexpr VariableName(const _Elem(&name)[N])
{
for (int i = 0; i < N; ++i) {
str[i] = name[i];
}
}
};
template<typename T, VariableName name, VariableName wname>
struct VariableRepr
{
const T &ref;
VariableRepr(const T &var) : ref(var) {}
};
#else
template<typename T>
struct VariableRepr
{
const T &ref;
std::string_view name;
std::wstring_view wname;
VariableRepr(const T &var, const std::string_view str, const std::wstring_view wstr)
: ref(var), name(str), wname(wstr)
{
}
};
#endif
#if __cpp_generic_lambdas >= 201707L
template <typename S>
inline auto log(int level, const char *loc, const wchar_t *wloc,
const char *func, const wchar_t *wfunc,
const S &format, bool func_tag, auto&&... args)
{
using namespace string_type;
auto f = [level, loc, wloc, func, wfunc, func_tag, format = std::move(format)]
<typename ...Args>
(const std::string_view category, Args&& ...args)
{
if (!filter(level, category)) {
return;
}
auto to_str = [](auto x) {
if constexpr (is_wstring<decltype(x)>::value || is_cwstring<decltype(x)>::value) {
return details::wtoa(x);
} else {
return x;
}
};
auto to_wstr = [](auto x) {
if constexpr (is_string<decltype(x)>::value || is_cstring<decltype(x)>::value) {
return details::atow(x);
} else {
return x;
}
};
static_assert(fmt::is_compile_string<S>::value);
using format_char_type = typename S::char_type;
if constexpr (std::is_same<format_char_type, wchar_t>::value) {
vlog<wchar_t>(level, category, wloc, wfunc, format, func_tag,
fmt::make_args_checked<decltype(to_wstr(args))...>(format, to_wstr(args)...));
} else {
vlog<char>(level, category, loc, func, format, func_tag,
fmt::make_args_checked<decltype(to_str(args))...>(format, to_str(args)...));
}
};
return category_logger(std::bind(f, std::placeholders::_1, std::forward<decltype(args)>(args)...));
}
#else
template <typename S, typename ...Args>
inline auto log(int level, const char *loc, const wchar_t *wloc,
const char *func, const wchar_t *wfunc,
const S &format, bool func_tag, auto&&... args)
{
using namespace string_type;
using namespace details;
auto to_str = [](auto x) {
if constexpr (is_wstring<decltype(x)>::value || is_cwstring<decltype(x)>::value) {
return wtoa(x);
} else {
return x;
}
};
auto to_wstr = [](auto x) {
if constexpr (is_string<decltype(x)>::value || is_cstring<decltype(x)>::value) {
return atow(x);
} else {
return x;
}
};
static_assert(fmt::is_compile_string<S>::value);
using format_char_type = typename S::char_type;
if constexpr (std::is_same<format_char_type, wchar_t>::value) {
vlog<wchar_t>(level, "", wloc, wfunc, format, func_tag,
fmt::make_args_checked<decltype(to_wstr(args))...>(format, to_wstr(args)...));
} else {
vlog<char>(level, "", loc, func, format, func_tag,
fmt::make_args_checked<decltype(to_str(args))...>(format, to_str(args)...));
}
return [](auto &&) {};
}
#endif
namespace formatter
{
struct atow_formatter : fmt::formatter<std::wstring_view, wchar_t>
{
template<typename FormatContext>
auto format(std::string_view const &str, FormatContext &ctx)
{
using namespace fmtlog::details;
return fmt::formatter<std::wstring_view, wchar_t>::format(atow(str), ctx);
}
};
struct wtoa_formatter : fmt::formatter<std::string_view, char>
{
template<typename FormatContext>
auto format(std::wstring_view const &str, FormatContext &ctx)
{
using namespace fmtlog::details;
return fmt::formatter<std::string_view, char>::format(wtoa(str), ctx);
}
};
template<typename Char>
struct formatter_map
{
#if _HAS_CXX20
template<typename T>
using remove_cvref_t = std::remove_cvref_t<T>;
#else
template<typename T>
using remove_cvref_t = typename std::remove_cv<std::remove_reference_t<T>>::type;
#endif
using args_char = std::conditional_t<std::is_same_v<Char, char>, wchar_t, char>;
using string_view_t = std::basic_string_view<args_char>;
template <typename S>
struct _is_string
: std::is_same<decltype(fmtlog::string_type::to_string_view(std::declval<const S &>())), string_view_t>
{
};
template <typename T, std::enable_if_t<(_is_string<T>::value), int> = 0>
constexpr inline auto map(const T &val)
{
if constexpr (std::is_same_v<args_char, char>) {
return atow_formatter{};
} else {
return wtoa_formatter{};
}
}
template<typename T>
auto map(T &&) -> fmt::formatter<remove_cvref_t<T>, Char> { return {}; }
};
template<typename Arg, typename Target>
using mapper = decltype(formatter_map<Target>().map(std::declval<const Arg &>()));
}
}
#define __TOWIDE2(x) L##x
#define __TOWIDE(x) __TOWIDE2(x)
#define __FMTLOG_TOWIDE2(x) L##x
#define __FMTLOG_TOWIDE(x) __FMTLOG_TOWIDE2(x)
#define __FMTLOG_WS1(x) L""#x
#define __FMTLOG_WS2(x) __FMTLOG_WS1(x)
#define __FMTLOG_S1(x) #x
#define __FMTLOG_S2(x) __FMTLOG_S1(x)
#define __FMTLOG_I(x) x
#if __cpp_nontype_template_args >= 201911L
template<typename T, typename Char, fmtlog::VariableName name, fmtlog::VariableName wname>
struct fmt::formatter<fmtlog::VariableRepr<T, name, wname>, Char> : fmtlog::formatter::mapper<T, Char>
{
template<typename FormatContext>
auto format(fmtlog::VariableRepr<T, name, wname> const &repr, FormatContext &ctx)
{
if constexpr (std::is_same_v<Char, char>) {
// format_to(ctx.out(), "{}=", name.str);
auto appender = ctx.out();
// appender = name.str;
appender = '=';
} else {
format_to(ctx.out(), L"{}=", wname.str);
}
return fmtlog::formatter::mapper<T, Char>::format(repr.ref, ctx);
}
};
#define F(var) fmtlog::VariableRepr<decltype(var), __FMTLOG_S2(var), __FMTLOG_WS2(var)>(var)
#else
template<typename T, typename Char>
struct fmt::formatter<fmtlog::VariableRepr<T>, Char> : fmtlog::formatter::mapper<T, Char>
{
template<typename FormatContext>
auto format(fmtlog::VariableRepr<T> const &repr, FormatContext &ctx)
{
using namespace fmtlog::string_type;
if constexpr (std::is_same<Char, char>::value) {
format_to(ctx.out(), "{}=", repr.name);
} else {
format_to(ctx.out(), L"{}=", repr.wname);
}
return fmtlog::formatter::mapper<T, Char>::format(repr.ref, ctx);
}
};
#define F(var) fmtlog::VariableRepr<decltype(var)>(var, __FMTLOG_S2(var), __FMTLOG_WS2(var))
#endif
#ifdef __FMTLOG_USE_FILENAME_LITERIAL
template<fmtlog::details::FileName F>
constexpr auto operator"" _fmtlog_fname() { return F.fname; }
#else
constexpr auto operator"" _fmtlog_fname(const char *str, std::size_t len) { return str; }
constexpr auto operator"" _fmtlog_fname(const wchar_t *str, std::size_t len) { return str; }
#endif
#define __FILE_NAME__ __FMTLOG_I(__FILE__)_fmtlog_fname
#define __FILE_NAMEW__ __FMTLOG_TOWIDE(__FILE__)_fmtlog_fname
#define __FMTLOG_LOCATION __FILE_NAME__ "(" __FMTLOG_S2(__LINE__) ")"
#define __FMTLOG_LOCATION_W __FILE_NAMEW__ L"(" __FMTLOG_WS2(__LINE__) L")"
#define __FMTLOG_FUNC __func__
#define __FMTLOG_FUNC_W __FMTLOG_TOWIDE(__FUNCTION__)
#define logn(format, ...) \
fmtlog::log(fmtlog::loglevel::always, \
__FMTLOG_LOCATION, __FMTLOG_LOCATION_W, \
__FMTLOG_FUNC, __FMTLOG_FUNC_W, \
FMT_STRING(format), false, __VA_ARGS__)
#define lognf(format, ...) \
fmtlog::log(fmtlog::loglevel::always, \
__FMTLOG_LOCATION, __FMTLOG_LOCATION_W, \
__FMTLOG_FUNC, __FMTLOG_FUNC_W, \
FMT_STRING(format), true, __VA_ARGS__)
#ifdef FMTLOG_ENABLE
#define LOG_IMPL(level, format, func_tag, ...) \
fmtlog::log(level, \
__FMTLOG_LOCATION, __FMTLOG_LOCATION_W, \
__FMTLOG_FUNC, __FMTLOG_FUNC_W, \
FMT_STRING(format), func_tag, __VA_ARGS__)
#define logt(format, ...) LOG_IMPL(fmtlog::loglevel::trace, format, false, __VA_ARGS__)
#define logd(format, ...) LOG_IMPL(fmtlog::loglevel::debug, format, false, __VA_ARGS__)
#define logi(format, ...) LOG_IMPL(fmtlog::loglevel::info, format, false, __VA_ARGS__)
#define logw(format, ...) LOG_IMPL(fmtlog::loglevel::warn, format, false, __VA_ARGS__)
#define loge(format, ...) LOG_IMPL(fmtlog::loglevel::error, format, false, __VA_ARGS__)
#define logc(format, ...) LOG_IMPL(fmtlog::loglevel::critical, format, false, __VA_ARGS__)
#define logf(format, ...) LOG_IMPL(fmtlog::loglevel::debug, format, true, __VA_ARGS__)
#define logfi(format, ...) LOG_IMPL(fmtlog::loglevel::info, format, true, __VA_ARGS__)
#define logfw(format, ...) LOG_IMPL(fmtlog::loglevel::warn, format, true, __VA_ARGS__)
#define logfe(format, ...) LOG_IMPL(fmtlog::loglevel::error, format, true, __VA_ARGS__)
#else
#define LOG_IMPL(...) fmtlog::empty_logger()
#define logt(format, ...) LOG_IMPL()
#define logd(format, ...) LOG_IMPL()
#define logi(format, ...) LOG_IMPL()
#define logw(format, ...) LOG_IMPL()
#define loge(format, ...) LOG_IMPL()
#define logc(format, ...) LOG_IMPL()
#define logf(format, ...) LOG_IMPL()
#define logfi(format, ...) LOG_IMPL()
#define logfw(format, ...) LOG_IMPL()
#define logfe(format, ...) LOG_IMPL()
#endif
#define FMTLOG_CALLBACK_STDOUT
#include "fmtlog.h"
auto main() -> int
{
auto wstr = std::wstring(L"自动转换-abc");
auto str = std::string("自动转换-ABC");
// Need to set console to UTF-8 output mode to show non-ascii characters for using wcout/wprintf
int abc = 123;
logd("{} {} {}", F(abc), F(wstr), F(str));
// main.cpp(12): abc=123 wstr=自动转换-abc str=自动转换-ABC
logd(L"{} {} {}", F(abc), F(wstr), F(str));
// main.cpp(14): abc=123 wstr=????-abc str=????-ABC
auto sv = std::string_view("中文");
auto wsv = std::wstring_view(L"中文");
auto cstr = "测试";
auto wcstr = "测试";
logd("{} {} {} {}", F(sv), F(wsv), F(cstr), F(wcstr));
// main.cpp(23): sv=中文 wsv=中文 cstr=测试 wcstr=测试
logd(L"{} {} {} {}", F(sv), F(wsv), F(cstr), F(wcstr));
// main.cpp(25): sv=?? wsv=?? cstr=?? wcstr=??
logt("invalid squishiness: {}{}{}", std::string_view("中文"), "测试", str)("test");
// main.cpp(28): invalid squishiness: 中文测试自动转换-ABC
logd(L"invalid squishiness: {}{}{}", std::wstring_view(L"中文"), L"测试", wstr)(std::string("test"));
// main.cpp(31): invalid squishiness: ????????-abc
logi(L"invalid squishiness: {}{}{}", std::string_view("中文"), "测试", str)(std::string_view("test"));
// main.cpp(33): invalid squishiness: ????????-ABC
logw("invalid squishiness: {}{}{}", std::wstring_view(L"中文"), L"测试", wstr);
// main.cpp(36): invalid squishiness: 中文测试自动转换-abc
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment