在C ++ 17中将无限参数传递给日志记录功能

时间:2019-07-19 19:21:48

标签: c++ logging arguments c++17

因此,我实现了一个相当琐碎的记录器,但我想对其进行扩展,以便我可以将数据参数传递给它,可能是通过格式化,而我似乎无法弄清楚如何做到最好。

到目前为止,它是这样写的:

// Standard Headers.
#include <ostream>
#include <variant>
#include <memory>
#include <utility>
#include <mutex>
#include <array>
#include <string_view>
#include <iostream>

namespace Logger {
    // Various logging severity levels.
    enum class Level {
        Info
    };

    class Log {
    public:
        // Takes standard streams cout, cerr, etc.
        explicit Log(std::ostream& p_stream) : m_log(&p_stream) {}

        // Create logger using std::make_unique<std::ofstream>(...) so ownership is passed.
        explicit Log(std::unique_ptr<std::ostream> p_stream) : m_log(std::move(p_stream)) {}

        template <typename T>
        inline void info(T&& p_message);

    private:
        template <typename T>
        void log(T&& p_msg) const {
            auto const t_lock = std::lock_guard(*m_lock);
            std::visit([&](auto&& p_ptr) {
                (*p_ptr) << p_msg;
            }, m_log);
        };

        std::ostream& stream() const {
            return std::visit([](auto&& ptr) -> std::ostream& {
                return *ptr;
            }, m_log);
        }

        template <typename T>
        inline void add(Logger::Level p_level, T&& p_message);

        std::variant<std::unique_ptr<std::ostream>, std::ostream*> m_log;
        std::unique_ptr<std::mutex> m_lock = std::make_unique<std::mutex>();
        std::array<std::string_view, 1> m_levels = { "Info" };
    };

    template <typename T>
    void Log::add(Level p_level, T&& p_message) {
        auto const f_lock = std::lock_guard(*m_lock);
        stream() << m_levels[static_cast<size_t>(p_level)] << ": " << p_message << '\n';
    }

    template <typename T>
    inline void Log::info(T&& p_message) {
        add(Level::Info, p_message);
    }
}

int main() {
    auto logger = Logger::Log(std::cout);
    logger.info("Hello, world!");
    return 0;
}

我想做的是使用.info()时能够指定写入日志时将被替换的任意数量的参数,类似于:

logger.info("Some error message with arg: {}", 1);

我将如何去做?最好的方法是什么?

2 个答案:

答案 0 :(得分:0)

出于某些奇怪的原因,我以这种方式做类似的事情(您将不得不根据自己的需要进行调整):

inline
std::ostream &
txt(std::ostream &output,
    const char *format)
{
  return output << format;
}

template<typename First,
         typename ...Args>
inline
std::ostream &
txt(std::ostream &output,
    const char *format,
    First &&first,
    Args &&...args)
{
  while(*format)
  {
    if(*format=='%')
    {
      return txt(output << std::forward<First>(first),
                 ++format, std::forward<Args>(args)...);
    }
    output << *format++;
  }
  return output;
}

前面的两个函数在格式字符串中使用标记%来插入下一个参数(无论是什么,它是一个值,一个格式说明符,一个自定义对象...)在流中。

当然,某些适配器可以简化用法:

template<typename ...Args>
inline
std::string
txt(const char *format,
    Args &&...args)
{
  std::ostringstream output;
  txt(output, format, std::forward<Args>(args)...);
  return output.str();
}

例如:

std::ostream &
operator<<(std::ostream &output,
           const MyStuff &ms)
{
  return output << '[' << ms.member1 << '|' << ms.member2 << ']'; 
}
...
MyStuff my_stuff= ... ;
auto msg=txt("an integer %, a (formatted) real %% and something else %\n",
             12, std::setprecision(12), 34.56, my_stuff);

然后应该可以调整Log::add()(然后是Log::info()和任何相关内容),使其以与问题中预期的类似的方式可用

logger.info("Some error message with arg: %", 1);

希望有帮助。

答案 1 :(得分:0)

我通常使用std::any。这是一个非优化的版本(使用副本而不是引用,从向量中删除项目等),但是基本思想是使用此行将编译时参数包转换为运行时参数包:

 std::vector<std::any> a = {args ...};

您可以将引用与std::vector<std::any> a = {std::ref(args)...};

一起使用

完整功能如下:

template<typename ... many>
void safe_printf2(const char *s, many ... args)
{
    using namespace std; 
    vector<any> a = {args ...};

    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {

                if (a.empty())
                    throw logic_error("Fewer arguments provided to printf");

                if (a[0].type() == typeid(string)) cout << any_cast<string>(a[0]);
                if (a[0].type() == typeid(int)) cout << any_cast<int>(a[0]);
                if (a[0].type() == typeid(double)) cout << any_cast<double>(a[0]);

                a.erase(a.begin());
                s++;
            }
        }
        cout << *s++;
    }
}

示例:

 safe_printf2("Hello % how are you today? I have % eggs and your height is %","Jack"s, 32,5.7);