如何创建像std :: cout这样的函数?

时间:2015-01-29 14:53:40

标签: c++ debugging logging cout

我正在为我的项目创建自己的日志记录实用程序,我想创建一个像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

6 个答案:

答案 0 :(得分:4)

std::cout不是函数,它是std::ostream类型的对象,它会重载operator<<

快速勾勒出如何做到这一点:

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

Ant然后使用它

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

答案 1 :(得分:2)

我不会在此输入编码详情,但我会为您提供一些快速指南:

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

    日志&安培; SingletonLoggersManager :: GetLoggerForLevel(eLogLevel);

  2. 覆盖“&lt;&lt;”您的课程的操作员,以便在您的Logger课程中输出符合您需求的课程

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

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

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

    2. 现在当你在代码中使用时

       Log(debug) << "test" 
      

      它将扩展为:

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

答案 2 :(得分:2)

诀窍是你的LOG(level)返回一个特殊类型 包含指向std::ostream的指针,并定义<<运算符。 类似的东西:

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的析构函数中添加flush。

答案 3 :(得分:2)

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

meta_stream处理operator<<重载。它使用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两次 - 它们是重载函数的名称。

这解决了将相同的字符串输出到两个不同的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) << "hello " << "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"};
}

但我认为这不值得。

答案 4 :(得分:0)

您可以定义enum之类的

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

的建议

答案 5 :(得分:0)

只有一个不错的解决方案,并不简单,但它会LOG(log_info) << "line 1\nline 2" << std::endl;正确。

必须实施custòmstd::ostreambuf。它是唯一可以在应用所有常用operator<<函数后重新格式化输出的类。

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