upd: [imp] optimized log implementation

This commit is contained in:
mutouyun 2022-11-19 22:10:42 +08:00
parent 6c9926bb93
commit 093597ca66
3 changed files with 101 additions and 54 deletions

View File

@ -12,7 +12,6 @@
#include <exception> #include <exception>
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/chrono.h"
#include "libimp/def.h" #include "libimp/def.h"
#include "libimp/detect_plat.h" #include "libimp/detect_plat.h"
@ -22,11 +21,6 @@
LIBIMP_NAMESPACE_BEG_ LIBIMP_NAMESPACE_BEG_
namespace log { namespace log {
template <typename Fmt, typename... A>
std::string fmt(Fmt &&ft, A &&... args) {
return ::fmt::format(std::forward<Fmt>(ft), std::forward<A>(args)...);
}
enum class level : std::int32_t { enum class level : std::int32_t {
trace, trace,
debug, debug,
@ -36,47 +30,79 @@ enum class level : std::int32_t {
failed, failed,
}; };
struct context {
log::level level;
std::chrono::system_clock::time_point tp;
char const *func;
std::string text;
};
LIBIMP_EXPORT std::string to_string(context &&) noexcept;
} // namespace log } // namespace log
namespace detail_log { namespace detail_log {
enum out_type : unsigned {
out_none = 0x0,
out_string = 0x1,
out_context = 0x2,
};
template <typename T> template <typename T>
class has_fn_output { class has_fn_output {
static std::false_type check(...); static std::integral_constant<out_type, out_none> check(...);
template <typename U> template <typename U>
static auto check(U *u) -> decltype(u->output(log::level::trace, std::declval<std::string>()), std::true_type{}); static auto check(U *u)
-> decltype(u->output(log::level::trace, std::declval<std::string>()),
std::integral_constant<out_type, out_string>{});
template <typename U>
static auto check(U *u)
-> decltype(u->output(log::level::trace, std::declval<log::context>()),
std::integral_constant<out_type, out_context>{});
public: public:
using type = decltype(check(static_cast<T *>(nullptr))); using type = decltype(check(static_cast<T *>(nullptr)));
}; };
template <typename T> template <typename T>
constexpr bool has_fn_output_v = has_fn_output<T>::type::value; constexpr out_type has_fn_output_v = has_fn_output<T>::type::value;
struct vtable_t { struct vtable_t {
void (*output)(void *, log::level, std::string &&); void (*output)(void *, log::context &&);
}; };
template <typename T> template <typename T>
class traits { class traits {
template <typename U> template <typename U>
static auto make_fn_output() noexcept static auto make_fn_output() noexcept
-> std::enable_if_t<has_fn_output_v<U>, void (*)(void *, log::level, std::string &&)> { -> std::enable_if_t<(has_fn_output_v<U> == out_none), decltype(vtable_t{}.output)> {
return [](void *p, log::level l, std::string &&s) { return [](void *, log::context &&) {};
static_cast<U *>(p)->output(l, std::move(s));
};
} }
template <typename U> template <typename U>
static auto make_fn_output() noexcept static auto make_fn_output() noexcept
-> std::enable_if_t<!has_fn_output_v<U>, void (*)(void *, log::level, std::string &&)> { -> std::enable_if_t<(has_fn_output_v<U> == out_string), decltype(vtable_t{}.output)> {
return [](void *, log::level, std::string &&) {}; return [](void *p, log::context &&ctx) {
auto lev = ctx.level;
auto str = to_string(std::move(ctx));
static_cast<U *>(p)->output(lev, std::move(str));
};
}
template <typename U>
static auto make_fn_output() noexcept
-> std::enable_if_t<(has_fn_output_v<U> == out_context), decltype(vtable_t{}.output)> {
return [](void *p, log::context &&ctx) {
static_cast<U *>(p)->output(std::move(ctx));
};
} }
public: public:
static auto make_vtable() noexcept { static auto make_vtable() noexcept {
static vtable_t vt { static vtable_t vt { make_fn_output<T>() };
make_fn_output<T>(),
};
return &vt; return &vt;
} }
}; };
@ -93,15 +119,14 @@ public:
printer() noexcept = default; printer() noexcept = default;
template <typename T, template <typename T,
/// @brief generic constructor may shadow the default copy constructor /// @remark generic constructor may shadow the default copy constructor
typename = std::enable_if_t<!std::is_same<printer, T>::value>> typename = std::enable_if_t<!std::is_same<printer, T>::value>>
printer(T &p) noexcept printer(T &p) noexcept
: objp_ (static_cast<void *>(&p)) : objp_ (static_cast<void *>(&p))
, vtable_(detail_log::traits<T>::make_vtable()) {} , vtable_(detail_log::traits<T>::make_vtable()) {}
explicit operator bool() const noexcept; explicit operator bool() const noexcept;
void output(context) noexcept;
void output(log::level, std::string &&) noexcept;
}; };
class LIBIMP_EXPORT std_t { class LIBIMP_EXPORT std_t {
@ -121,18 +146,19 @@ class gripper {
if (!printer_ || (enum_cast(l) < enum_cast(level_limit_))) { if (!printer_ || (enum_cast(l) < enum_cast(level_limit_))) {
return *this; return *this;
} }
constexpr static char types[] = { context ctx;
'T', 'D', 'I', 'W', 'E', 'F' LIBIMP_TRY {
ctx = {
l, std::chrono::system_clock::now(), func_,
::fmt::format(std::forward<Fmt>(ft), std::forward<A>(args)...),
}; };
try { } LIBIMP_CATCH(std::exception const &e) {
auto tp = std::chrono::system_clock::now();
auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(tp).time_since_epoch().count() % 1000;
auto px = fmt("[{}][{:%Y-%m-%d %H:%M:%S}.{:03}][{}] ", types[enum_cast(l)], tp, ms, func_);
printer_.output(l, std::move(px += fmt(std::forward<Fmt>(ft), std::forward<A>(args)...)));
} catch (std::exception const &e) {
/// @remark [TBD] std::string constructor may throw an exception /// @remark [TBD] std::string constructor may throw an exception
printer_.output(level::failed, e.what()); ctx = {
level::failed, std::chrono::system_clock::now(), func_, e.what(),
};
} }
printer_.output(std::move(ctx));
return *this; return *this;
} }

View File

@ -2,18 +2,33 @@
#include <utility> #include <utility>
#include <cstdio> #include <cstdio>
#include "fmt/chrono.h"
#include "libimp/log.h" #include "libimp/log.h"
LIBIMP_NAMESPACE_BEG_ LIBIMP_NAMESPACE_BEG_
namespace log { namespace log {
std::string to_string(context &&ctx) noexcept {
constexpr static char types[] = {
'T', 'D', 'I', 'W', 'E', 'F',
};
LIBIMP_TRY {
auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(ctx.tp).time_since_epoch().count() % 1000;
return ::fmt::format("[{}][{:%Y-%m-%d %H:%M:%S}.{:03}][{}] {}", types[enum_cast(ctx.level)], ctx.tp, ms, ctx.func, ctx.text);
} LIBIMP_CATCH(std::exception const &e) {
/// @remark [TBD] std::string constructor may throw an exception
return e.what();
}
}
printer::operator bool() const noexcept { printer::operator bool() const noexcept {
return (objp_ != nullptr) && (vtable_ != nullptr); return (objp_ != nullptr) && (vtable_ != nullptr);
} }
void printer::output(log::level l, std::string &&s) noexcept { void printer::output(context ctx) noexcept {
if (!*this) return; if (!*this) return;
vtable_->output(objp_, l, std::move(s)); vtable_->output(objp_, std::move(ctx));
} }
std_t std_out; std_t std_out;

View File

@ -1,21 +1,23 @@
#include <iostream>
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "libimp/log.h" #include "libimp/log.h"
TEST(log, detail) { TEST(log, detail) {
EXPECT_FALSE(imp::detail_log::has_fn_output_v<int>); EXPECT_EQ(imp::detail_log::has_fn_output_v<int>, imp::detail_log::out_none);
struct foo { struct foo {
int info(std::string); int info(std::string);
}; };
EXPECT_FALSE(imp::detail_log::has_fn_output_v<foo>); EXPECT_EQ(imp::detail_log::has_fn_output_v<foo>, imp::detail_log::out_none);
struct bar { struct bar {
int info(char const *); int info(char const *);
void output(imp::log::level, std::string &&); void output(imp::log::level, std::string &&);
}; };
EXPECT_TRUE(imp::detail_log::has_fn_output_v<bar>); EXPECT_EQ(imp::detail_log::has_fn_output_v<bar>, imp::detail_log::out_string);
struct str { struct str {
str(std::string const &); str(std::string const &);
@ -23,25 +25,27 @@ TEST(log, detail) {
struct foo_bar { struct foo_bar {
void output(imp::log::level, str); void output(imp::log::level, str);
}; };
EXPECT_TRUE(imp::detail_log::has_fn_output_v<foo_bar>); EXPECT_EQ(imp::detail_log::has_fn_output_v<foo_bar>, imp::detail_log::out_string);
auto vt_int = imp::detail_log::traits<int>::make_vtable(); auto vt_int = imp::detail_log::traits<int>::make_vtable();
EXPECT_NE(vt_int, nullptr); EXPECT_NE(vt_int, nullptr);
vt_int->output(nullptr, imp::log::level::debug, "123"); EXPECT_NO_THROW(vt_int->output(nullptr, imp::log::context{}));
struct log { struct log {
std::string what; std::string what;
void output(imp::log::level l, std::string &&s) { void output(imp::log::level l, std::string &&s) {
if (l == imp::log::level::error) what += s; if (l == imp::log::level::error) what += s + '\n';
} }
} ll; } ll;
auto vt_log = imp::detail_log::traits<log>::make_vtable(); auto vt_log = imp::detail_log::traits<log>::make_vtable();
EXPECT_NE(vt_log, nullptr); EXPECT_NE(vt_log, nullptr);
vt_log->output(&ll, imp::log::level::info , "123");
vt_log->output(&ll, imp::log::level::error, "321"); vt_log->output(&ll, imp::log::context{imp::log::level::info , std::chrono::system_clock::now(), __func__, "123"});
vt_log->output(&ll, imp::log::level::info , "654"); vt_log->output(&ll, imp::log::context{imp::log::level::error, std::chrono::system_clock::now(), __func__, "321"});
vt_log->output(&ll, imp::log::level::error, "456"); vt_log->output(&ll, imp::log::context{imp::log::level::info , std::chrono::system_clock::now(), __func__, "654"});
EXPECT_EQ(ll.what, "321456"); vt_log->output(&ll, imp::log::context{imp::log::level::error, std::chrono::system_clock::now(), __func__, "456"});
std::cout << ll.what << "\n";
SUCCEED();
} }
TEST(log, log_printer) { TEST(log, log_printer) {
@ -50,21 +54,22 @@ TEST(log, log_printer) {
std::string e; std::string e;
void output(imp::log::level l, std::string &&s) { void output(imp::log::level l, std::string &&s) {
if (l == imp::log::level::error) e += s; if (l == imp::log::level::error) e += s + "\n";
else if (l == imp::log::level::info) i += s; else if (l == imp::log::level::info) i += s + "\n";
} }
} ll; } ll;
imp::log::printer pt = ll; imp::log::printer pt = ll;
pt.output(imp::log::level::info , "hello "); pt.output({imp::log::level::info , std::chrono::system_clock::now(), __func__, "hello "});
pt.output(imp::log::level::error, "failed: "); pt.output({imp::log::level::error, std::chrono::system_clock::now(), __func__, "failed: "});
pt.output(imp::log::level::info , "log-pt"); pt.output({imp::log::level::info , std::chrono::system_clock::now(), __func__, "log-pt"});
pt.output(imp::log::level::error, "whatever"); pt.output({imp::log::level::error, std::chrono::system_clock::now(), __func__, "whatever"});
EXPECT_EQ(ll.i, "hello log-pt"); std::cout << ll.i << "\n";
EXPECT_EQ(ll.e, "failed: whatever"); std::cout << ll.e << "\n";
imp::log::printer ps = imp::log::std_out; imp::log::printer ps = imp::log::std_out;
ps.output(imp::log::level::info, "hello world\n"); ps.output({imp::log::level::info, std::chrono::system_clock::now(), __func__, "hello world"});
SUCCEED();
} }
TEST(log, gripper) { TEST(log, gripper) {
@ -76,4 +81,5 @@ TEST(log, gripper) {
LIBIMP_LOG_(); LIBIMP_LOG_();
log.info("hello 2"); log.info("hello 2");
} }
SUCCEED();
} }