【问题标题】:What is the point of clog?堵塞的意义何在?
【发布时间】:2010-09-08 07:37:09
【问题描述】:

我一直在想,堵塞有什么意义?据我所知, clog 与 cerr 相同,但具有缓冲功能,因此效率更高。通常stderr和stdout一样,所以clog和cout一样。这对我来说似乎很蹩脚,所以我想我一定是误解了它。如果我有日志消息发送到同一个地方,我有错误消息发送到(可能是 /var/log/messages 中的某些内容),那么我可能不会写太多(所以使用 non 不会丢失太多-缓冲 cerr)。以我的经验,我希望我的日志消息是最新的(不缓冲),这样我可以帮助找到崩溃(所以我不想使用缓冲的阻塞)。显然我应该一直使用 cerr。

我希望能够在我的程序中重定向堵塞。重定向 cerr 会很有用,这样当我调用库例程时,我可以控制 cerr 和 clog 的去向。一些编译器可以支持这个吗?我刚刚检查了 DJGPP,stdout 被定义为 FILE 结构的地址,因此执行“stdout = freopen(...)”之类的操作是非法的。

  • 是否可以重定向 clog、cerr、cout、stdin、stdout 和/或 stderr?
  • clog 和 cerr 之间的唯一区别是缓冲吗?
  • 我应该如何实施(或找到)更强大的日志记录工具(请提供链接)?

【问题讨论】:

  • Usually stderr is the same as stdout 在 Unix 及其变体上肯定不是真的。例如,它们可以单独重定向。
  • @AndreHolzner 在所有 DOS/Windows 环境中也是如此,2> 在 Windows 7 命令提示符中有效,例如作为重定向 stderr 的一种方式。

标签: c++ logging log4cpp


【解决方案1】:

是否可以重定向 clog、cerr、cout、stdin、stdout 和/或 stderr?

是的。你想要rdbuf 函数。

ofstream ofs("logfile");
cout.rdbuf(ofs.rdbuf());
cout << "Goes to file." << endl;

clog 和 cerr 的唯一区别是缓冲吗?

据我所知,是的。

【讨论】:

  • 一些附加信息可以在stackoverflow.com/questions/33601886/…找到。
  • 写入后关闭并重置 cout 或出现段错误:ofs.close(); cout.rdbuf(nullptr);
  • @micah94 流在作用域结束时自动关闭,无需手动关闭。但是关于重置cout 缓冲区的好处。
  • 只是检查:如果在你这样做rdbuf 之后,你尝试从两个不同的线程同时写入ofscout,你会得到一个数据竞争和未定义的行为,对吧? (但如果你从不通过ofs 写信,或者如果你明确地将你的写操作与互斥锁或其他东西同步,那也没关系。)
  • @Quuxplusone 好问题。我不知道输出流在什么时候同步,但您可能是对的。
【解决方案2】:

如果你在 posix shell 环境中(我真的在想 bash),你可以重定向任何 文件描述符到任何其他文件描述符,因此要重定向,您可以:

$ myprogram 2>&5 

将 stderr 重定向到 fd=5 表示的文件。

编辑:再想一想,我更喜欢@Konrad Rudolph 关于重定向的回答。 rdbuf() 是一种更连贯且可移植的方式。

至于日志记录,嗯...我从 Boost 库开始,用于所有不在 std 库中的 C++。看哪:Boost Logging v2

编辑:Boost Logging 不是 Boost 库的一部分;它已经过审核,但没有被接受。

编辑:2 年后,也就是 2010 年 5 月,Boost 确实接受了一个日志库,现在称为 Boost.Log

当然,还有其他选择:

  • Log4Cpp(用于 C++ 的 log4j 风格 API)
  • Log4Cxx(Apache 赞助的 log4j 风格的 API)
  • Pantheios(已失效?上次我尝试无法在最近的编译器上构建它)
  • Google's GLog(帽子提示@SuperElectric)

还有 Windows 事件记录器。

还有几篇可能有用的文章:

【讨论】:

  • 需要注意的是,“Boost Logging v2”不是boost的一部分,而是一个提议的库,在审核时可能会被接受,也可能不会被接受。
  • 好点。该库于 2008 年 2 月 4 日至 2008 年 2 月 13 日进行了审查,显然被拒绝了。可能会再次审查。可能 log4cxx 或 log4cpp 是最好的选择。
  • 或者使用简单的基于行的std::cerr解决方案。
  • 我也强烈推荐 Google 的 GLog (code.google.com/p/google-glog),尤其是它简洁的断言宏和信息丰富的错误消息。
【解决方案3】:

由于这里有几个关于重定向的答案,我将添加this nice gem我最近偶然发现的关于重定向:

#include <fstream>
#include <iostream>

class redirecter
{
public:
    redirecter(std::ostream & dst, std::ostream & src)
        : src(src), sbuf(src.rdbuf(dst.rdbuf())) {}
    ~redirecter() { src.rdbuf(sbuf); }
private:
    std::ostream & src;
    std::streambuf * const sbuf;
};

void hello_world()
{
    std::cout << "Hello, world!\n";
}

int main()
{
    std::ofstream log("hello-world.log");
    redirecter redirect(log, std::cout);
    hello_world();
    return 0;
}

它基本上是一个重定向类,允许您重定向任意两个流,并在完成后恢复它。

【讨论】:

    【解决方案4】:

    重定向

    关于如何重定向std::clog (std::wclog),Konrad Rudolph 的回答很好。

    其他答案告诉您各种可能性,例如使用命令行重定向,例如2&gt;output.log。使用 Unix,您还可以创建一个文件并将另一个输出添加到您的命令中,例如 3&gt;output.log。然后,在您的程序中,您必须使用 fd 数字 3 来打印日志。您可以继续正常打印到stdoutstderr。 Visual Studio IDE 的 CDebug 命令具有类似的功能,该命令将其输出发送到 IDE 输出窗口。

    stderrstdout一样吗?

    这通常是正确的,但在 Unix 下,您可以将 stderr 设置为 /dev/console,这意味着它会转到另一个 tty(也称为终端)。这些天很少使用它。我在 IRIX 上就是这样。我会打开一个单独的 X-Window 并查看其中的错误。

    还有很多人向/dev/null 发送错误消息。在你写的命令行上:

    command ...args... 2>/dev/null
    

    系统日志

    还有一点没有提到,在 Unix 下,你也有syslog()

    Linux 下的最新版本(可能还有 Mac OS/X)的功能比以前要多得多。特别是,它可以使用标识和其他一些参数将日志重定向到特定文件(即mail.log)。 syslog 机制可以在计算机之间使用,因此计算机 A 的日志可以发送到计算机 B。当然您可以通过各种方式过滤日志,尤其是按严重性。

    syslog() 使用起来也很简单:

    syslog(LOG_ERR, "message #%d", count++);
    

    它提供 8 个级别(或严重性)、一个格式为 printf(),以及该格式的参数列表。

    如果您第一次调用openlog() 函数,您可能会以编程方式进行一些调整。您必须在第一次调用 syslog() 之前调用它。

    正如 unixman83 所提到的,您可能希望使用宏来代替。这样,您可以在消息中包含一些参数,而无需一遍又一遍地重复它们。也许是这样的(见Variadic Macro):

    // (not tested... requires msg to be a string literal)
    #define LOG(lvl, msg, ...) \
         syslog(lvl, msg " (in " __FILE__ ":%d)", __VA_ARGS__, __LINE__)
    

    您可能还会发现__func__ 很有用。

    重定向、过滤等是通过创建配置文件来完成的。这是我的 snapwebsites 项目中的an example

    mail.err /var/log/mail/mail.err
    mail.* /var/log/mail/mail.log
    & stop
    

    我在/etc/rsyslog.d/下安装文件并运行:

    invoke-rc.d rsyslog restart
    

    因此syslog 服务器会处理该更改并将所有与邮件相关的日志保存到这些文件夹中。

    注意:我还必须创建/var/log/mail 文件夹和文件夹内的文件,以确保一切正常(否则邮件守护进程可能没有足够的权限。) p>

    snaplogger(一个小插件)

    我使用过log4cplus,从 1.2.x 版本开始,它非常好。不过,我有三个缺点:

    1. 如果我想调用 fork();,它需要我完全清除所有内容
    2. 配置文件 (.properties) 在我的环境中不容易管理,我喜欢管理员在不修改原始文件的情况下进行更改
    3. 它使用 C++03,我们现在是 2019 年...我希望至少有 C++11

    正因为如此,特别是因为第 (1) 点,我编写了自己的版本,名为 snaplogger。不过,这并不完全是一个独立的项目。我使用 snapcpp 环境中的许多其他项目(获取 snapcpp 并运行 bin/build-snap 脚本或仅运行 get the binaries from launchpad 会容易得多。)

    使用 snaplogger 或 log4cplus 等记录器的优势在于,您通常可以定义任意数量的目标和许多其他参数(例如 syslog() 提供的严重级别)。 log4cplus 能够将其输出发送到许多不同的地方:文件、系统日志、MS-Windows 日志系统、控制台、服务器等。查看这两个项目中的附加程序以了解可能性列表。这里有趣的因素是任何日志都可以发送到所有目的地。这对于有一个名为 all.log 的文件很有用,所有服务都在其中发送它们的日志。这有助于理解某些在并行运行多个服务时使用单独的日志文件不容易发生的错误。

    这是 snaplogger 配置文件中的一个简单示例:

    [all]
    type=file
    lock=true
    filename=/var/log/snapwebsites/all.log
    
    [file]
    lock=false
    filename=/var/log/snapwebsites/firewall.log
    

    请注意,对于all.log 文件,我需要一个锁,这样多个写入者就不会在彼此之间破坏日志。 [file] 部分没有必要,因为我只有一个进程(没有线程)。

    两者都为您提供了一种添加自己的附加程序的方法。因此,例如,如果您有一个带有输出窗口的 Qt 应用程序,您可以编写一个附加程序来将 SNAP_LOG_ERROR() 调用的输出发送到该窗口。

    snaplogger 还为您提供了一种方法来扩展消息中的变量支持(也称为格式)。例如,我可以使用${date} 变量插入日期。然后我可以用一个参数来调整它。为了只输出年份,我使用${date:year}。这种可变参数支持也是可扩展的。

    snaplogger 可以按严重性(如系统日志)、正则表达式和组件过滤输出。我们有一个normal 和一个secure 组件,默认是normal。我希望将发送到secure 组件的日志写入安全文件。这意味着在一个子目录中,它比大多数管理员可以查看的普通日志受到更多保护。当我运行我的 HTTP 服务时,有时我会发送诸如信用卡的最后 3 位数字之类的信息。我更喜欢将它们保存在安全日志中。也可能是与密码相关的错误。任何我认为在日志中存在安全风险的东西,真的。同样,组件是可扩展的,因此您可以拥有自己的组件。

    【讨论】:

      【解决方案5】:

      关于重定向器类的一点。它需要被正确地销毁,而且只需要一次。析构函数将确保在声明它的函数实际返回时会发生这种情况,并且对象本身永远不会被复制。

      为确保无法复制,请提供私有复制和赋值运算符:

      class redirecter
      {
      public:
          redirecter(std::ostream & src, std::ostream & dst)
              : src_(src), sbuf(src.rdbuf(dst.rdbuf())) {}
          ~redirecter() { src.rdbuf(sbuf); }
      private:
          std::ostream & src_;
          std::streambuf * const sbuf_;
          // Prevent copying.                        
          redirecter( const redirecter& );
          redirecter& operator=( const redirecter& );
      };
      

      我通过将 std::clog 重定向到我的 main() 中的日志文件来使用这种技术。为了确保 main() 实际返回,我将 main() 的内容放在 try/catch 块中。然后在我的程序的其他地方,我可能会调用 exit(),我会抛出一个异常。这会将控制权返回给 main(),然后它可以执行 return 语句。

      【讨论】:

      • C++ 标准不是更喜欢=delete 而不是私有构造函数吗?
      • @DynamicSquid 我不确定标准本身更喜欢什么,但这是一回事。不过,OP 和/或回答者必须使用 C++11 或更高版本(我认为现在大多数人都是如此)。
      【解决方案6】:

      基本记录器

      #define myerr(e) {CriticalSectionLocker crit; std::cerr << e << std::endl;}
      

      用作myerr("ERR: " &lt;&lt; message);myerr("WARN: " &lt;&lt; message &lt;&lt; code &lt;&lt; etc);

      非常有效。

      然后做:

      ./programname.exe 2> ./stderr.log
      perl parsestderr.pl stderr.log
      

      或者只是手动解析 stderr.log

      我承认这不适用于非常性能关键的代码。但是谁写的呢。

      【讨论】:

      • #defines 没有类型,在 C++ 中不鼓励大多数用法。
      • 如果您想要自动捕获 _FILE_ 和 _LINE_,仍然需要定义,大多数人都会这样做。
      • @SpacenJasset 除非您使用 std::source_location(存在于 C++20 及更高版本中)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-18
      • 1970-01-01
      • 1970-01-01
      • 2014-06-01
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多