【问题标题】:Passing unlimited arguments to logging function in C++17将无限参数传递给 C++17 中的日志记录函数
【发布时间】:2019-07-19 19:21:48
【问题描述】:

所以我实现了一个相当简单的记录器,但我想扩展它,以便我可以将数据参数传递给它,可能带有格式,我似乎无法弄清楚如何最好地做到这一点。

到目前为止是这样写的:

// 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);

我该怎么做呢?最好的方法是什么?

【问题讨论】:

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


【解决方案1】:

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

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

您可以使用引用,例如 std::vector&lt;std::any&gt; 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);

【讨论】:

    【解决方案2】:

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

    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);
    

    希望对您有所帮助。

    【讨论】:

    • %%%有什么区别?
    • @Rietty 有两个%,因为我们注入了格式化程序(如果需要,首先是%)然后是值(第二个%)。每个% 都可以替换为任何内容。甚至'%' ;^)
    猜你喜欢
    • 1970-01-01
    • 2017-06-09
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多