20

コンテキスト

ユーザー向けのメッセージを出力するロガーを作成しました。レベルが「debug」、「info」、または「warning」std::coutのメッセージは に出力され、レベルが「error」または「system_error」のメッセージは に出力されstd::cerrます。私のプログラムはマルチスレッドではありません。Linux openSUSE 12.3 と gcc 4.7.2 および CMake 3.1.0 で作業しています。

私の問題

エラー メッセージ (に出力)std::cerrが長い情報メッセージ ( に出力) の後に続き、出力がCTest によってstd::coutファイルにリダイレクトされるときに、エラー メッセージが情報メッセージに表示されることがあることがわかりました (以下の例を参照)。LastTest.log私はこの動作をよく理解していませんが、書き込みスレッドが起動されstd::cout、コードが続行され、最初の書き込みスレッドが終了するのを待たずに別の書き込みスレッドが起動されると思いstd::cerrます。

only を使用せずにそれを回避することは可能std::coutですか?

端末に問題はありません。これは、CTest が出力をLastTest.logファイルにリダイレクトする場合にのみ発生します。

私のバッファがフラッシュされていることに注意してください。std::endlへの呼び出しの後に来るのは問題ではありませんstd::cerr

予想される動作:

[ 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::coutこれは、またはstd::cerrロガーを使用して呼び出す方法の例です。私はそのようなマクロでロガーを呼び出します:

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

ここで、ログ シャネルは端末出力専用のオブジェクトであり、書き込み関数は次のとおりです。

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

ログ命令が実行されると、バッファがフラッシュされることがわかります。

4

3 に答える 3

20

この動作は、以前にいくつかの形で見たことがあります。中心的な考え方は、それを覚えてstd::cout、 2 つの完全に別々std::cerrのストリームに書き込むことです。そのため、両方からの出力が同じ場所に表示されるときはいつでも、2 つのストリームをマージするプログラムの外部の何らかのメカニズムが原因です。

時々、次のような単純な間違いが原因でこれが表示されます

myprogram > logfile &
tail -f logfile

書き込まれたログファイルを監視していますが、ログファイルにもリダイレクトするのを忘れていたため、表示される前に少なくとも2つの追加のバッファリングレイヤーを通過するstderrように書き込みますが、ttyに直接移動するように書き込み、混在する可能性があります.stdouttailstderr

私が見た他の例には、ストリームをマージする外部プロセスが含まれます。私は何も知りませんCTestが、おそらくこれをやっています。このようなプロセスは、最初にストリームに書き込んだ正確な時間で行を並べ替える義務はありません。おそらく、たとえそうしたくても、その情報にアクセスすることはできません!


ここでは、実際には 2 つの選択肢しかありません。

  • 両方のログを同じストリームに書き込みます。たとえば、std::clog代わりにstd::coutまたはstd::cout代わりに使用しstd::cerrます。myprogram 2>&1または同様のプログラムを起動します
  • マージは、何をマージしているのかを実際に認識し、適切に実行するように注意するプロセスによって行われることを確認してください。これは、フォーマットされたログ メッセージ自体を書き込むのではなく、ロギング イベントを含むパケットをやり取りすることで通信している場合に適しています。
于 2015-06-02T11:05:22.573 に答える
2

私はC++の専門家ではありませんが、これが役立つかもしれません...

ファイルにリダイレクトするときにここに表示されている問題は、cstdio ライブラリが賢くしようとしていることが原因だと思います。私の理解では、Linux では、C++ iostream が最終的に出力を cstdio ライブラリに送信します。

起動時に、cstdio ライブラリは、出力を端末またはファイルに送信しているかどうかを検出します。出力が端末に送られる場合、stdio は行バッファーされます。出力がファイルに送られる場合、stdio はブロック バッファリングされます。

stderr への出力はバッファリングされないため、すぐに送信されます。

ソリューションについては、stdout で使用fflushしてみるか、stdout で関数を使用してsetvbufライン バッファ出力 (または必要に応じてバッファなし出力) を強制することを検討できます。このようなものは、stdout を強制的に line buffered にする必要がありますsetvbuf(stdout, NULL, _IOLBF, 0)

于 2015-06-02T16:11:43.743 に答える