簡體   English   中英

std :: cerr不等待std :: cout(運行CTest時)

[英]std::cerr doesn't wait for std::cout (when running CTest)

上下文

我為用戶寫了一個記錄器打印消息。 具有級別“debug”,“info”或“warning”的消息在std::cout中打印,級別為“error”或“system_error”的消息在std::cerr中打印。 我的程序不是多線程的。 我使用gcc 4.7.2和CMake 3.1.0在Linux openSUSE 12.3下工作。

我的問題

我發現有時,當一條錯誤消息(在std::cerr打印)跟隨一條長信息消息(在std::cout打印)並且當輸出被CTest重定向到文件LastTest.log ,錯誤消息出現在信息消息(請看下面的例子)。 我不太了解這種行為,但我想為std::cout啟動了一個寫入線程,然后代碼繼續,另一個寫入線程為std::cerr而不等待第一個終止。

不使用std::cout就可以避免這種情況嗎?

我在終端沒有問題。 它只在CTest將輸出重定向到LastTest.log文件時發生。

請注意我的緩沖區已刷新。 在調用std::cerr之后,這不是std::endl的問題!

預期行為:

[ 12:06:51.497   TRACE ] Start test
[ 12:06:52.837 WARNING ] This
                         is
                         a
                         very
                         long
                         warning
                         message...
[ 12:06:52.837   ERROR ] AT LINE 49 : 7
[ 12:06:52.841   ERROR ] AT LINE 71 : 506
[ 12:06:52.841   TRACE ] End of test

怎么了 :

[ 12:06:51.497   TRACE ] Start test
[ 12:06:52.837 WARNING ] This
                         is
                         a
                         very
                         long
[ 12:06:52.837   ERROR ] AT LINE 49 : 7
                         warning
                         message...
[ 12:06:52.841   ERROR ] AT LINE 71 : 506
[ 12:06:52.841   TRACE ] End of test

我怎么稱呼我的記錄器

這是我用記錄器調用std::coutstd::cerr的示例。 我用這樣的maccros調用記錄器:

#define LOG_DEBUG(X) {if(Log::debug_is_active()){std::ostringstream o;o<<X;Log::debug(o.str());}}
#define LOG_ERROR(X) {if(Log::error_is_active()){std::ostringstream o;o<<X;Log::error(o.str());}}
//...
LOG_DEBUG("This" << std::endl << "is" << std::endl << "a message");
LOG_ERROR("at line " << __LINE__ << " : " << err_id);

void Log::debug(const std::string& msg)
{
    Log::write_if_active(Log::DEBUG, msg);
}
void Log::error(const std::string& msg)
{
    Log::write_if_active(Log::ERROR, msg);
}
//...
void Log::write_if_active(unsigned short int state, const std::string& msg)
{
    Instant now;
    now.setCurrentTime();
    std::vector<std::string> lines;
    for(std::size_t k = 0; k < msg.size();)
    {
        std::size_t next_endl = msg.find('\n', k);
        if(next_endl == std::string::npos)
            next_endl = msg.size();
        lines.push_back(msg.substr(k, next_endl - k));
        k = next_endl + 1;
    }
    boost::mutex::scoped_lock lock(Log::mutex);
    for(unsigned long int i = 0; i < Log::chanels.size(); ++i)
        if(Log::chanels[i])
            if(Log::chanels[i]->flags & state)
                Log::chanels[i]->write(state, now, lines);
}

這里,log chanel是專用於終端輸出的對象,write函數是:

void Log::StdOut::write(unsigned short int state, const Instant& t, const std::vector<std::string>& lines)
{
    assert(lines.size() > 0 && "PRE: empty lines");
    std::string prefix =  "[ ";
    if(this->withDate || this->withTime)
    {
        std::string pattern = "";
        if(this->withDate)
            pattern += "%Y-%m-%d ";
        if(this->withTime)
            pattern += "%H:%M:%S.%Z ";
        prefix += t.toString(pattern);
    }
    std::ostream* out = 0;
    if(state == Log::TRACE)
    {
        prefix += "  TRACE";
        out = &std::cout;
    }
    else if(state == Log::DEBUG)
    {
        prefix += "  DEBUG";
        out = &std::cout;
    }
    else if(state == Log::INFO)
    {
        prefix += "   INFO";
        out = &std::cout;
    }
    else if(state == Log::WARNING)
    {
        prefix += "WARNING";
        out = &std::cout;
    }
    else if(state == Log::ERROR)
    {
        prefix += "  ERROR";
        out = &std::cerr;
    }
    else if(state == Log::SYS_ERROR)
    {
        prefix += "SYERROR";
        out = &std::cerr;
    }
    else
        assert(false && "PRE: Invalid Log state");
    prefix += " ] ";
    (*out) << prefix << lines[0] << "\n";
    prefix = std::string(prefix.size(), ' ');
    for(unsigned long int i = 1; i < lines.size(); ++i)
        (*out) << prefix << lines[i] << "\n";
    out->flush();
}

您可以看到執行日志指令時刷新了我的緩沖區。

我之前以一些形式看過這種行為。 中心思想是記住std::coutstd::cerr寫入兩個完全獨立的流,所以每當你在同一個地方看到兩者的輸出時,都是因為程序之外的某些機制合並了兩個流。

有時,我只是因為一個錯誤而看到這一點,例如

myprogram > logfile &
tail -f logfile

這是看,因為它是寫的日志文件,卻忘了重定向stderr到日志文件太多,所以寫到stdout經過內部緩沖的至少兩個附加層tail越來越顯示之前,但寫到stderr直接去TTY,等等可以混入。

我見過的其他例子涉及合並流的外部流程。 我對CTest一無所知,但也許它正在這樣做。 這些進程沒有義務按照您最初將它們寫入流的確切時間對行進行排序,即使他們想要也可能無法訪問該信息!


你真的只有兩個選擇:

  • 將兩個日志寫入同一個流 - 例如使用std::clog而不是std::coutstd::cout而不是std::cerr ; 或使用myprogram 2>&1或類似程序啟動程序
  • 確保合並是由一個實際意識到它正在合並的過程完成的,並且要注意適當地進行合並。 如果您通過傳遞包含日志記錄事件的數據包進行通信而不是自己編寫格式化的日志消息,則可以更好地工作。

回答我自己的問題

最后,它來自CMake中的一個錯誤

CTest無法管理兩個緩沖區的順序,因此不關心輸出的確切順序。

它將在CMake> = 3.4中解決。

我不是C ++專家,但這可能會有所幫助......

我相信你在這里看到的問題,當重定向到文件時,是由於cstdio庫試圖變得聰明。 我的理解是,在Linux上,C ++ iostream最終將其輸出發送到cstdio庫。

啟動時,cstdio庫會檢測您是將輸出發送到終端還是文件。 如果輸出到達終端,那么stdio是行緩沖的。 如果輸出轉到文件,則stdio將變為塊緩沖。

輸出到stderr從不緩沖,因此將立即發送。

對於解決方案,您可以嘗試在stdout上使用fflush ,或者您可以考慮在stdout上使用setvbuf函數來強制行緩沖輸出(如果您願意,甚至可以使用無緩沖輸出)。 像這樣的東西應該強制stdout是行緩沖的setvbuf(stdout, NULL, _IOLBF, 0)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM