/** * \file libimp/log.h * \author mutouyun (orz@orzz.org) * \brief Simple log output component. * \date 2022-05-22 */ #pragma once #include #include #include #include #include #include "libimp/def.h" #include "libimp/detect_plat.h" #include "libimp/export.h" #include "libimp/underlyof.h" #include "libimp/fmt.h" #include "libimp/generic.h" LIBIMP_NAMESPACE_BEG_ namespace log { enum class level : std::int32_t { trace, debug, info, warning, error, failed, }; /// \struct template struct context /// \brief Logging context. /// \tparam ...T - a log records all parameter types passed template struct context { log::level level; std::chrono::system_clock::time_point tp; char const *func; std::tuple params; }; /// \brief Custom defined fmt_to method for imp::fmt namespace detail { template bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence, A &&...args) { return fmt_to(ctx, std::forward(args)..., std::get(tp)...); } } // namespace detail template bool context_to_string(fmt_context &f_ctx, context const &l_ctx) noexcept { constexpr static char types[] = { 'T', 'D', 'I', 'W', 'E', 'F', }; LIBIMP_TRY { auto ms = std::chrono::time_point_cast(l_ctx.tp).time_since_epoch().count() % 1000; return detail::unfold_tuple_fmt_to(f_ctx, l_ctx.params, std::index_sequence_for{}, "[", types[underlyof(l_ctx.level)], "]" "[", l_ctx.tp, ".", spec("03")(ms), "]" "[", l_ctx.func, "] "); } LIBIMP_CATCH(...) { return false; } } template std::string context_to_string(context const &l_ctx) noexcept { LIBIMP_TRY { std::string log_txt; fmt_context f_ctx(log_txt); if (!context_to_string(f_ctx, l_ctx)) { return {}; } f_ctx.finish(); return log_txt; } LIBIMP_CATCH(...) { return {}; } } /// \brief Standard console output object. inline auto &make_std_out() noexcept { static auto std_out = [](auto const &ctx) { auto s = context_to_string(ctx); switch (ctx.level) { case level::trace: case level::debug: case level::info: std::fprintf(stdout, "%s\n", s.c_str()); break; case level::warning: case level::error: case level::failed: std::fprintf(stderr, "%s\n", s.c_str()); break; default: break; } }; return std_out; } /** * \brief Log information grips. */ template class grip { Outputer out_; char const *func_; level level_limit_; public: template grip(char const *func, O &&out, level level_limit) noexcept : out_ (std::forward(out)) , func_ (func) , level_limit_(level_limit) {} template grip const &operator()(log::level l, A &&... args) const noexcept { if (underlyof(l) < underlyof(level_limit_)) { return *this; } LIBIMP_TRY { out_(context { l, std::chrono::system_clock::now(), func_, std::forward_as_tuple(std::forward(args)...), }); } LIBIMP_CATCH(...) {} return *this; } template grip const &trace (A &&...args) const noexcept { return (*this)(log::level::trace , std::forward(args)...); } template grip const &debug (A &&...args) const noexcept { return (*this)(log::level::debug , std::forward(args)...); } template grip const &info (A &&...args) const noexcept { return (*this)(log::level::info , std::forward(args)...); } template grip const &warning(A &&...args) const noexcept { return (*this)(log::level::warning, std::forward(args)...); } template grip const &error (A &&...args) const noexcept { return (*this)(log::level::error , std::forward(args)...); } template grip const &failed (A &&...args) const noexcept { return (*this)(log::level::failed , std::forward(args)...); } }; template inline auto make_grip(char const *func, O &&out, level level_limit = level::info) noexcept { return grip>(func, std::forward(out), level_limit); } inline auto make_grip(char const *func, level level_limit = level::info) noexcept { return make_grip(func, make_std_out(), level_limit); } inline auto make_grip(char const * /*ignore*/, char const *func, level level_limit = level::info) noexcept { return make_grip(func, make_std_out(), level_limit); } } // namespace log LIBIMP_NAMESPACE_END_ #define LIBIMP_LOG_(...) auto log = ::LIBIMP::log::make_grip(__func__,##__VA_ARGS__)