【问题标题】:How to create functions like std::cout?如何创建像 std::cout 这样的函数?
【发布时间】:2015-03-28 20:26:27
【问题描述】:

我正在为我的项目创建自己的日志记录实用程序,我想创建一个类似 iostream 的 std::cout 的函数,以记录到文件并打印到控制台。

这就是我想要的:

enum
{
    debug, error, warning, info
};

LOG(level) << "test"; // level - from the above enum

结果应该是这样的:

int iPlayerID = 1337;
LOG(info) << "Player " << iPlayerID << "Connected";

[Thu Jan 29 18:32:11 2015] [info] Player 1337 Connected

【问题讨论】:

  • 如果在 Linux 或 POSIX 上考虑 syslog(3)POCOQt 等框架库提供了日志记录工具。
  • POCO 和 Qt 都是跨平台的
  • 为什么需要枚举?登录到不同的文件?
  • 不,输出将是这样的:[Thu Jan 29 18:32:11 2015] [] 消息
  • 等等,如果你这样做LOG(level) &lt;&lt; "hello " &lt;&lt; "world"会发生什么?你想要[Thu Jan 29 18:32:11 2015] [1] hello [Thu Jan 29 18:32:11 2015] [1] world吗?这似乎是个坏主意。

标签: c++ debugging logging cout


【解决方案1】:

std::cout 不是函数,它是std::ostream 类型的对象,它重载了operator&lt;&lt;

如何做到这一点的速写:

enum Level {
    debug, error, warning, info
};

struct Logger {
    std::ostream* stream;  // set this in a constructor to point
                           // either to a file or console stream
    Level debug_level;
public:
    Logger& operator<<(const std::string& msg)
    {
        *stream << msg; // also print the level etc.
        return *this;
    }

    friend Logger& log(Logger& logger, Level n);
    {
        logger.debug_level = n;
        return logger;
    }
};

然后像蚂蚁一样使用它

Logger l;
log(l, debug) << "test";

【讨论】:

  • 这是记录器类。我与您想要的 LOG(level) 语法略有不同,因为我认为稍后当您希望在同一个程序中拥有多个记录器时,您会后悔的。所以我将记录器添加为参数(我也不喜欢单例:))。
  • 需要修复 friend。它现在坏了(返回*this 和所有)。
【解决方案2】:

诀窍是让您的 LOG(level) 返回一个特殊类型 包含指向std::ostream 的指针,并定义&lt;&lt; 运算符。 比如:

class LogStream
{
    std::ostream* myDest;
public:
    LogStream( std::ostream* dest ) : myDest( dest ) {}

    template <typename T>
    LogStream& operator<<( T const& obj )
    {
        if ( myDest != nullptr ) {
            *myDest << obj;
        }
        return *this;
    }
};

LOG(level) 宏创建一个实例,类似于:

#define LOG(level) LogStream( getLogStream( level, __FILE__, __LINE__ ) )

当然,getLogStream 可以在调用它的那一刻插入它想要的任何信息(如时间戳)。

您可能希望在 LogStream 的析构函数中添加一个刷新。

【讨论】:

    【解决方案3】:

    我不会在这里输入编码细节,但我会为您提供一些快速指南:

    1. 创建一个单例对象池(对于loggers可以创建一个单例)或者命名空间或者根据枚举返回某个日志类的a:

      Logger& SingletonLoggersManager::GetLoggerForLevel(eLogLevel);

    2. 为您的类覆盖“

    https://msdn.microsoft.com/en-us/library/1z2f6c2k.aspx

    1. 定义一个宏以便能够在您的代码中进行快速调用:

      #define LOG(x) SingletonLogger::GetLoggerForLevel(eLogLoevel);

    现在当你在你的代码中使用时

     Log(debug) << "test" 
    

    它将扩展为:

     (SingletonLogger::GetLoogerForLevel(debug)) << "test";
    

    【讨论】:

      【解决方案4】:

      我在上面看到了两个问题。第一个是分叉你的消息(到文件和控制台)。第二个是用一些额外的东西包装所写的内容。

      meta_stream 处理 operator&lt;&lt; 重载。它使用 CRTP 静态分派给它的子类型:

      template<class D, class substream>
      struct meta_stream {
        D& self() { return *static_cast<D*>(this); } // cast myself to D
        // forwarders of operator<<
        template<class X>
        friend D& operator<<( meta_stream<D>& d, X const& x ) {
          d.self().write_to(x);
          return d.self();
        }
        friend D& operator<<(
          meta_stream<D>& d,
          substream&(*mod_func)(substream&)
        ) {
          d.self().write_to(mod_func);
          return d.self();
        }
      };
      

      由于std::endl 和其他修饰符的工作方式,我不得不重写&lt;&lt; 两次——它们是重载函数的名称。

      这样就解决了将同一个字符串输出到两个不同的ostream的问题:

      template<class substream>
      struct double_ostream:
        meta_stream<double_ostream<substream>,substream>
      {
        substream* a = nullptr;
        substream* b = nullptr;
        template<class X>
        void write_to( X&&x ) {
          if (d.a) (*d.a) << x;
          if (d.b) (*d.b) << std::forward<X>(x);
        }
        double_ostream( std::basic_ostream<CharT>* a_, std::basic_ostream<CharT>* b_ ):
          a(a_), b(b_)
        {}
        double_ostream(double_ostream const&)=default;
        double_ostream()=default;
        double_ostream& operator=(double_ostream const&)=default;
      };
      

      注意通过meta_stream 使用CRTP。我只需要实现write_to

      首先,将 4 个记录器写入此数组:

      enum loglevel {
        debug, error, warning, info
      };
      double_stream<std::ostream> loggers[4];
      

      给每个指针一个指向std::cout 的指针和一个指向(存储在其他地方)流包装您想要保存日志的文件的指针。如果您不希望将该级别记录到该输出流(例如,在发布中,跳过调试日志),您可以传递nullptr,并且您可以将内容记录到不同的日志文件(调试到一个文件,信息到另一个)。

      double_stream<std::ostream> log( loglevel l ) {
        double_stream<std::ostream> retval = loggers[l];
        std::string message;
        // insert code to generate the current date here in message
        // insert code to print out the log level here into message
        retval << message;
        return retval;
      }
      

      现在log(debug) &lt;&lt; "hello " &lt;&lt; "world\n" 将为您写信息。

      如果您不想在日志消息的末尾写下换行符,您可以做更多花哨的事情,但我怀疑这是否值得。只写换行符。

      如果你真的想要那个功能:

      template<class substream>
      struct write_after_ostream:
        meta_stream<write_after_ostream<substream>,substream>
      {
        substream* os = nullptr;
        template<class X>
        void write_to( X&&x ) {
          if (os) *os << std::forward<X>(x);
        }
        ~write_after_ostream() {
          write_to(message);
        }
        write_after_ostream( substream* s, std::string m ):
          os(s), message(m)
        {}
        std::string message;
      }
      
      write_after_ostream<double_stream<std::ostream>> log( loglevel l ) {
        // note & -- store a reference to it, as we will be using a pointer later:
        double_stream<std::ostream>& retval = loggers[l];
        std::string pre_message;
        // insert code to generate the current date here in pre_message
        // insert code to print out the log level here into pre_message
        retval << pre_message;
        return {&retval, "\n"};
      }
      

      但我认为这不值得。

      【讨论】:

        【解决方案5】:

        你可以定义一个enumlike

        enum loglevel_en {
            log_none, log_debug, log_info, log_waring, log_error };
        

        然后有一个全局变量:

        enum loglevel_en my_log_level;
        

        并提供一些设置它的方法(例如从程序参数)。

        最后,定义一个宏(在全局头文件中)

        #define MY_LOG(Lev,Thing) do { if (my_log_level >= Lev) \
            std::cout << __FILE__ << ":" << __LINE__ \
                      << " " << Thing << std::endl; } while(0)
        

        并像使用它

         MY_LOG(log_info, "x=" << x)
        

        随时改进MY_LOG 宏以输出更多内容(日期等...)

        你可以定义

        #define LOG(Lev) if (my_log_level >= Lev) std::cout 
        

        但是这样的代码不适合

        if (x>34)
          LOG(log_info) << "strange x=" << x;
        else return;
        

        所以我强烈建议MY_LOG

        【讨论】:

          【解决方案6】:

          只有一个不错的解决方案,而且并不简单,但它得到了LOG(log_info) &lt;&lt; "line 1\nline 2" &lt;&lt; std::endl; 正确。

          必须实施自定义std::ostreambuf所有常用的operator&lt;&lt; 函数都已应用之后,它是唯一可以重新格式化输出的类。

          特别是,当您有一堆字符要处理时,会调用您的流缓冲区方法overflow。您现在可以添加日志级别过滤,并可靠地检查格式化字符流中的换行符。

          【讨论】:

            猜你喜欢
            • 2011-10-25
            • 2017-06-03
            • 2015-04-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-12-14
            相关资源
            最近更新 更多