如何从库标准错误中读取?

Posted

技术标签:

【中文标题】如何从库标准错误中读取?【英文标题】:How to read from a library standard error? 【发布时间】:2012-02-08 07:48:17 【问题描述】:

我有一个使用 C++ 库的 Qt/C++ 应用程序。

这个库有一个日志机制,可以将字符串消息写入标准错误。

现在,我希望能够将这些消息重定向到我的 Qt 工具中的面板。 我想避免修改该库,因为许多其他客户都采用了该库。 知道如何在运行时获取这些消息吗?

如果有可能更改它,将这些消息传送到应用程序可能是一个好的做法?

【问题讨论】:

【参考方案1】:

这是非常糟糕的库设计。不过……

它如何写入标准错误。如果输出到std::cerr, 那么你可以改变std::cerr使用的streambuf,比如:

std::filebuf logStream;
if ( ~logStream.open( "logfile.txt" ) )
    //  Error handling...
std::streambuf* originalCErrStream = std::cerr.rdbuf();
std::cerr.rdbuf( &logStream );
//  Processing here, with calls to library
std::cerr.rdbuf( originalCErrStream );  //  Using RAII would be better.

别忘了恢复原来的streambuf;离开std::cerr 指向已被破坏的filebuf 不是一个好主意。

如果他们使用FILE*,C 中有一个freopen 函数(并且通过 包含在 C++ 中),您可以使用。

如果他们使用系统级输出(write 在 Unix 下,WriteFile 在 Windows 下),那么您将不得不使用一些系统级代码 改变输出。 (open 在新文件上,close 在 fd 上 STDERR_FILENO, 和dup2 设置STDERR_FILENO 使用新的 在 Unix 下打开的文件。我不确定这是否可能 Windows——可能是 ReOpenFile 或一些组合 CloseHandle 后跟CreateFile。)

编辑:

我刚刚注意到您实际上想要输出到 Qt 窗口。这 意味着您可能需要一个字符串,而不是一个文件。如果 库正在使用std::cerr,您可以使用std::stringbuf,而不是 一个std::filebuf;实际上,您可能想要创建自己的流缓冲区, 接听sync 的电话(通常会在每次 <<std::cerr)。如果库使用其他技术之一, 我唯一能想到的就是定期读取文件,看看 如果有任何添加。 (我会在 Unix 中使用 read()ReadFile() 在 Windows 中,为了确保能够区分 读取零字节,因为自上次以来没有写入任何内容 读取和错误条件。 FILE*iostream 函数处理 读取零字节作为文件结尾,不会进一步读取。)

【讨论】:

库正在打印如下消息:std::cerr 我从不使用 stringbuf...我不太清楚我的应用程序如何知道已发送某些内容...我是否触发了任何事件? @Stefano 如果要输出到 stringbuf,则必须使用某种轮询。在这种情况下,我通常使用自定义streambuf。 (std::cerr 已初始化,因此每个 << 都会导致在 streambuf 上调用 sync。)【参考方案2】:

写入标准错误实际上是一个系统调用:

write(2, "blahblah ...");

您可以将文件描述符编号 2 重定向到任何东西(文件、管道、套接字):

close(2);                        // close old stderr
int redirect_target = open(...); // open a file where you want to redirect to
                                 // or use pipe, socket whatever you like
dup2(redirect_target, 2);        // copy the redirect_target fd to fd number 2
close(redirect_target);

在您的情况下,您将需要一个管道。

close(2);
int pipefd[2];
pipe2(pipefd);
dup2(pipefd[1], 2);
close(pipefd[1]);

那么,所有写入stderr的东西都可以通过读取pipe[0]得到:

read(pipe[0], buffer, ...);

【讨论】:

请注意,在同一个线程中写入和读取同一个管道时很容易出现死锁。 read 必须在不同线程(或不同进程)的循环中才能可靠。 当然,这是纯 Unix。我不认为它可以在Windows下使用(但也许......Windows确实提供了很大一部分Unix接口)。 也许我应该创建一个带有选择的线程来等待消息而不锁定我的应用程序...【参考方案3】:

如果他们使用对 std::cerr 的调用,您可以将其重定向到 std::ostringstream

#include <iostream>
#include <sstream>

class cerr_redirector

public:
    cerr_redirector(std::ostream& os)
        :backup_(std::cerr.rdbuf())
         ,sbuf_(os.rdbuf())
    
        std::cerr.rdbuf(sbuf_);
    

    ~cerr_redirector()
    
        std::cerr.rdbuf(backup_);
    

private:
    cerr_redirector();
    cerr_redirector(const cerr_redirector& copy);
    cerr_redirector& operator =(const cerr_redirector& assign);

    std::streambuf* backup_;
    std::streambuf* sbuf_;
;

您可以使用以下方法捕获输出:

std::ostringstream os;
cerr_redirector red(os);
std::cerr << "This is written to the stream" << std::endl;

std::cout 将不受影响:

std::cout << "This is written to stdout" << std::endl;

因此您可以测试您的捕获是否正常工作:

std::cout << "and now: " << os.str() << std::endl;

或者只是将os.str() 的内容添加到您的 Qt 窗口中。

Demonstration at ideone.

【讨论】:

非常简单的解决方案...我的问题是我怎么知道客户端已经写了一些东西?我应该继续投票还是可以有任何“触发器”?【参考方案4】:

在这里我找到了我需要的完整实现......

感谢大家的帮助! :)

Will loading a DLL dynamically reconcile its stderr to a main application? If so, then how...?

【讨论】:

以上是关于如何从库标准错误中读取?的主要内容,如果未能解决你的问题,请参考以下文章

C语言,程序读取标准输入是啥意思?

NGINX - 在标准错误中发送的 FastCGI:“主要脚本未知”,同时从上游读取响应标头

无法在 Rust 中两次读取子标准错误

gnuplot:静默错误读取(非标准形式)科学格式数字

Rust:从标准输入读取和映射行并处理不同的错误类型

标准错误日志文件的良好布局是啥?