std :: cerr не ждет std :: cout (при запуске CTest)

Контекст

Я написал регистратор, печатающий сообщения для пользователя. Сообщения с уровнем «отладка», «информация» или «предупреждение» печатаются в std::cout и сообщения с уровнем «error» или «system_error» печатаются в std::cerr, Моя программа не многопоточная. Я работаю под Linux openSUSE 12.3 с gcc 4.7.2 и CMake 3.1.0.

Моя проблема

Я обнаружил, что иногда, когда сообщение об ошибке (напечатано в std::cerr) следует длинное информационное сообщение (напечатано в std::cout) и когда вывод перенаправляется в файл LastTest.log По CTest сообщение об ошибке появляется в информационном сообщении (см. пример ниже). Я не очень понимаю это поведение, но я предполагаю, что для std::cout, затем код продолжается, и другой поток записи запускается для std::cerr без ожидания первый прекращается.

Можно ли избежать этого, не используя только 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();
}

Вы можете видеть, что мой буфер очищается при выполнении инструкции журнала.

20

Решение

Я видел это поведение раньше в нескольких формах. Основная идея заключается в том, чтобы помнить, что std::cout а также std::cerr напиши двум совершенно отдельный потоки, поэтому каждый раз, когда вы видите выходные данные обоих в одном и том же месте, это происходит из-за какого-то механизма вне вашей программы, который объединяет два потока.

Иногда я вижу это просто из-за ошибки, такой как

myprogram > logfile &
tail -f logfile

который смотрит файл журнала, как он написан, но забыл перенаправить stderr в лог-файл тоже, так что пишет в stdout пройти как минимум два дополнительных слоя буферизации внутри tail до отображения, но пишет в stderr иди прямо к tty, и так можно запутаться.

Другие примеры, которые я видел, касаются внешнего процесса, объединяющего потоки. Я ничего не знаю о CTest но, возможно, он делает это. Такие процессы не обязаны сортировать строки по точному времени, когда вы изначально записали их в поток, и, вероятно, не имеют доступа к этой информации, даже если они этого хотят!


У вас действительно есть только два варианта:

  • Записать оба журнала в один поток — например, использование std::clog вместо std::cout или же std::cout вместо std::cerr; или запустите программу с myprogram 2>&1 или похожие
  • Убедитесь, что слияние выполняется процессом, который на самом деле знает, что это слияние, и позаботится о том, чтобы сделать это соответствующим образом. Это работает лучше, если вы общаетесь, передавая пакеты, содержащие события регистрации, а не записывая сами отформатированные сообщения журнала.
20

Другие решения

Отвечая на мой вопрос

Наконец, это исходит от ошибка в CMake.

CTest не может управлять порядком двух буферов и не заботится о точном порядке вывода.

Это будет решено в CMake> = 3.4.

12

Я не эксперт по C ++, но это может помочь …

Я считаю, что проблема, которую вы видите здесь, при перенаправлении в файл, вызвана тем, что библиотека cstdio пытается быть умной. Насколько я понимаю, в Linux iostreams C ++ в конечном итоге отправляют свой вывод в библиотеку cstdio.

При запуске библиотека cstdio определяет, отправляете ли вы вывод на терминал или в файл. Если вывод идет на терминал, то stdio является линейной буферизацией. Если вывод идет в файл, тогда stdio становится блочной буферизацией.

Вывод в stderr никогда не буферизуется, поэтому будет отправлен немедленно.

Для решений вы могли бы попробовать использовать fflush на стандартный вывод, или вы можете изучить использование setvbuf функция на stdout для принудительного вывода буферизованной строки (или даже небуферизованного вывода, если хотите). Нечто подобное должно заставить stdout быть буферизованным setvbuf(stdout, NULL, _IOLBF, 0),

2
По вопросам рекламы [email protected]