如何在C++中将标准错误/标准输出重定向到我的日志文件?

4

我正在使用一个第三方工具,有时会出现内部错误,当发生错误时,第三方工具会写入stderr,并且我可以在控制台中看到相关信息。

虽然我检查了第三方函数的返回值并发现它失败了,但我仍希望能够获取他写入stderr的信息。

我有一个日志记录器,使用我拥有的一种方法进行记录。

SendLog(string log);

我希望能够捕获第三方写入到stderr的内容(可能需要监听stderr),然后将这些内容写入我的日志。
我该如何做? 我需要这样做是因为我的用户无法看到控制台,只能看到日志。
值得注意的是,我的程序在出现这些错误后并不会崩溃或退出,它会继续工作。
编辑: 我的问题与其他类似的问题不同,我想避免使用链接器黑客技巧(正如在类似的问题中所使用的)。

@BenjaminBannier:差不多了——那里有一些答案,它们不依赖于cout后面的.rdbuf - MSalters
你所提到的答案并没有解答我的问题,它使用了丑陋的链接技巧。我正在寻找一种通过代码实现的方法。 - OopsUser
链接的问题中有多个答案。我同意链接黑客是丑陋的,但在没有非丑陋解决方案的情况下,有时需要采用丑陋的解决方案。 - MSalters
我相信即使有另一个看起来类似并且有一些“hacky”解决方案的问题,我们也需要给人类一个机会在关闭并放弃之前找到更好的解决方案。 - OopsUser
另一个问题尚未关闭。仍然可以添加新的干净答案。我们在 SO 上要避免的是,像你这样有相同问题的人需要检查十几个只有一个答案的问题。相反,我们将所有答案分组到一个问题下。您甚至可以授予赏金,以再次将旧问题置于聚光灯下。 - MSalters
3个回答

3
一个解决方案是将所有写入cerr的内容复制到一个文件中。
这是帮助类:
class CTee {
public:
    // Use ostream &s2 if you want to duplicate to an ostream, pass other
    // stuff you need if you have other logging mechanisms.
    CTee(ostream &s1, ostream &s2) : m_s1(s1), m_s1OrigBuf(s1.rdbuf()), m_teebuf(s1.rdbuf(), s2.rdbuf()) { s1.rdbuf(&m_teebuf); }
    ~CTee() { m_s1.rdbuf(m_s1OrigBuf); }

private:
    CTee &operator =(CTee &rhs);    // not implemented

    class CTeeBuf : public streambuf {
    public:
        // Use streambuf *sb2 if you want to duplicate to an ostream/streambuf.
        // Pass other Information if you want to log to something different.
        CTeeBuf(streambuf* sb1, streambuf* sb2) :  m_sb1(sb1), m_sb2(sb2) {}

    protected:
        virtual int_type overflow(int_type c) {
            if(streambuf::traits_type::eq_int_type(c, streambuf::traits_type::eof()))
                return c;
            else {
                // Put char to cerr/stream to duplicate
                m_sb1->sputc((streambuf::char_type)c);
                // Put char to duplicate stream. If you want to duplicate to something
                // different, then write the char whereever you want to.
                return m_sb2->sputc((streambuf::char_type)c);
            }
        }
        virtual int sync() {
            m_sb1->pubsync();
            return m_sb2->pubsync();
        }

        // Store streambuf *m_sb2 if you want to duplicate to streambuf.
        // Store anything else if you want to duplicate to something different.
        streambuf *m_sb1, *m_sb2;
    };

    ostream &m_s1;
    streambuf * const m_s1OrigBuf;
    CTeeBuf m_teebuf;
};

CTee需要一个ostream作为复制源和一个ostream作为复制目标。它获取将要被复制的ostream并用CTeeBuf(请参阅CTee ctor)替换其rdbuf,即被写入到的streambuf。CTeeBuf将写入其中的char转发到两个ostreamstreambuf中(请参阅CTeeBuf::overflow和CTeeBuf::sync)。CTee dtor将更改后的streambuf恢复为其原始值。
使用方法如下:
char logfilename[] = "myfile.log";
ofstream logfile(logfilename, ios_base::out | ios_base::app);
CTee tee(cerr, logfile);

从现在开始,所有写入 cerr 的内容都会被复制到日志文件中(在 tee 的生命周期内)。因此,这条消息将被写入 cerr,也会被写入日志文件:

cerr << "error occured: ..." << endl;

除了日志文件之外,还可以将内容写入其他的输出流。如果您不想将内容复制到另一个ostream而是想要复制到其他地方,请将CTeeBuf::overflow替换为在任何您想要记录的位置实现的函数。

另请参阅http://www.cs.technion.ac.il/~imaman/programs/teestream.htmlhttp://wordaligned.org/articles/cpp-streambufs


你能否对“tee”类和CTeeBuf类进行一些解释? - OopsUser
我该如何调用一个函数并将当前写入缓冲区的字符串传递给它? - OopsUser
@OopsUser 我已经调整了我的答案,并在代码中添加了一些注释,您需要在其中进行更改。您可以从更改溢出函数开始,以调用您想要通知每个字符的任何函数(抱歉,仅适用于字符,而不是字符串)。然后,您将看到您还需要哪些成员变量以及如何调整构造函数。 - Werner Henze
谢谢 Werner,那么同步函数实际上可以被删除吗?我有办法知道某人是否完成了对stderr的写入吗?最后一个字符会是null吗? - OopsUser
@OopsUser sync仍然必须调用m_sb1->pubsync,因此您仍然需要它。您将看不到(字符串终止)空字节,因为它们也不会出现在您写入的文件中。如果您不想逐个字符地编写,您将需要自己的缓冲逻辑来检测例如何时完成一行(检测到'\n'),然后将完整的行写入您自己的日志。 - Werner Henze

1

一种方法是使用stringstream。如果库使用c++流编写,那么它也可以工作。

class redirect_stream
{
public:
   redirect_stream(std::ostreamstream& stream, std::ostream& oldstream) :
   stream_(stream), oldstream_(oldstream)
   {
      oldbuf_ = oldstream_.rdbuf();
      oldstream_.rdbuf(stream_.rdbuf());
   }
   ~redirect_stream()
   {
      const std::string str = stream_.str();
      if (!str.empty())
      {
         SendLog(str);
      }
      oldstream_.rdbuf(oldbuf_);
   }
private:
   std::ostringstream& stream_;
   std::ostream& olstream_;
   std::streambuf* oldbuf_;
};

在使用第三方库之前,只需:

std::ostringstream oss;
redirect_stream redirecter(oss, std::cerr);

或者你可以在析构函数中不打印消息,只需在与第三方库的工作结束后打印oss.str()

简单使用示例


你认为我的程序在出错后还存在吗?事实并非如此。我使用第三方工具来支持整个程序的生命周期。而且在程序运行时,我想要获取日志记录。 - OopsUser
@OopsUser 为什么退出?那只是析构函数,对象重定向器将在作用域之后被销毁。 - ForEveR

0
你可以使用 std::stringstream。
std::stringstream log;
std::streambuf *buf = std::cerr.rdbuf(log).rdbuf());
std::cerr << "Error Msg" << std::endl;
std::string errMsg( log.str() );

errMsg将是"错误消息"。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接