/** * \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) { static constexpr char types[] = { 'T', 'D', 'I', 'W', 'E', 'F', }; 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, "] "); } template std::string context_to_string(context const &l_ctx) { std::string log_txt; fmt_context f_ctx(log_txt); LIBIMP_TRY { if (!context_to_string(f_ctx, l_ctx)) { return {}; } f_ctx.finish(); return log_txt; } LIBIMP_CATCH(...) { f_ctx.finish(); throw; } } /// \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 Get the exception information. inline char const *exception_string(std::exception_ptr eptr) noexcept { LIBIMP_TRY { if (eptr) { std::rethrow_exception(eptr); } } LIBIMP_CATCH(std::exception const &e) { return e.what(); } LIBIMP_CATCH(...) { return "unknown"; } return "none"; } /// \brief Record the last information when an exception occurs. inline void exception_print(char const *func, std::exception_ptr eptr) noexcept { if (func == nullptr) { func = "-"; } std::fprintf(stderr, "[F][%s] exception: %s\n", func, exception_string(eptr)); } /** * \brief Log information base class. */ class logger_base { protected: char const *func_; level level_limit_; logger_base(char const *func, level level_limit) noexcept : func_ (func) , level_limit_(level_limit) {} }; /** * \brief Log information grips. */ template class logger : public logger_base { Outputer out_; public: template logger(char const *func, O &&out, level level_limit) noexcept : logger_base(func, level_limit) , out_ (std::forward(out)) {} template logger 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(...) { exception_print(func_, std::current_exception()); } return *this; } template logger const &trace (A &&...args) const noexcept { return (*this)(log::level::trace , std::forward(args)...); } template logger const &debug (A &&...args) const noexcept { return (*this)(log::level::debug , std::forward(args)...); } template logger const &info (A &&...args) const noexcept { return (*this)(log::level::info , std::forward(args)...); } template logger const &warning(A &&...args) const noexcept { return (*this)(log::level::warning, std::forward(args)...); } template logger const &error (A &&...args) const noexcept { return (*this)(log::level::error , std::forward(args)...); } template logger const &failed (A &&...args) const noexcept { return (*this)(log::level::failed , std::forward(args)...); } }; template inline auto make_logger(char const *func, O &&out, level level_limit = level::info) noexcept { return logger>(func, std::forward(out), level_limit); } inline auto make_logger(char const *func, level level_limit = level::info) noexcept { return make_logger(func, make_std_out(), level_limit); } inline auto make_logger(char const * /*ignore*/, char const *name, level level_limit = level::info) noexcept { return make_logger(name, make_std_out(), level_limit); } } // namespace log LIBIMP_NAMESPACE_END_ #define LIBIMP_LOG_(...) auto log = ::LIBIMP::log::make_logger(__func__,##__VA_ARGS__)