c++ - plus - iostream doc
std:: cerr no espera a std:: cout(cuando se ejecuta CTest) (3)
El contexto
Escribí un registrador imprimiendo mensajes para el usuario. Los mensajes con nivel "depuración", "información" o "advertencia" se imprimen en std::cout
y los mensajes con nivel "error" o "system_error" se imprimen en std::cerr
. Mi programa no es multiproceso. Trabajo bajo Linux openSUSE 12.3 con gcc 4.7.2 y CMake 3.1.0.
Mi problema
Descubrí que a veces, cuando un mensaje de error (impreso en std::cerr
) sigue a un mensaje de información largo (impreso en std::cout
) y cuando la salida se redirige al archivo LastTest.log
por CTest, el mensaje de error aparece en El mensaje de información (mira el ejemplo de abajo). No entiendo bien este comportamiento, pero supongo que se inicia un hilo de escritura para std::cout
, luego el código continúa y otro hilo de escritura se lanza para std::cerr
sin esperar a que std::cerr
el primero.
¿Es posible evitar eso sin usar solo std::cout
?
No tengo el problema en la terminal. Solo sucede cuando CTest redirige la salida al archivo LastTest.log
.
Tenga en cuenta que mi búfer está vaciado. No es un problema de std::endl
después de la llamada a std::cerr
!
Ejemplo
Comportamiento esperado:
[ 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
Lo que pasa :
[ 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
Como llamo a mi logger
Este es un ejemplo de cómo llamo a std::cout
o std::cerr
con mi registrador. Llamo al registrador con maccros así:
#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);
con
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);
}
Aquí, el canal de registro es el objeto dedicado a la salida del terminal, la función de escritura es:
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();
}
Puede ver que mi búfer se vacía cuando se ejecuta la instrucción de registro.
He visto este comportamiento antes en algunas formas. La idea central es recordar que std::cout
y std::cerr
escriben en dos flujos completamente separados , por lo que cada vez que vea la salida de ambos en el mismo lugar, es debido a algún mecanismo fuera de su programa que combina los dos arroyos
A veces, veo esto simplemente debido a un error, como
myprogram > logfile &
tail -f logfile
que está viendo el archivo de registro como está escrito, pero se olvidó de redirigir stderr
al archivo de registro también, así que las escrituras en stdout
pasan por al menos dos capas adicionales de almacenamiento en búfer dentro de la tail
antes de mostrarse, pero las escrituras en stderr
van directamente al tty, y así se puede mezclar en.
Otros ejemplos que he visto involucran un proceso externo que combina flujos. No sé nada acerca de CTest
pero tal vez esté haciendo esto. Dichos procesos no tienen la obligación de ordenar las líneas según la hora exacta en que originalmente las escribió en el flujo, y probablemente no tengan acceso a esa información, ¡incluso si lo quisieran!
Realmente solo tienes dos opciones aquí:
- Escriba ambos registros en la misma
std::cerr
; por ejemplo, usestd::clog
lugar destd::cout
ostd::cout
lugar destd::cerr
; omyprogram 2>&1
el programa conmyprogram 2>&1
o similar - Asegúrese de que la fusión se realice mediante un proceso que sea realmente consciente de lo que se está fusionando y que se encargue de hacerlo de manera apropiada. Esto funciona mejor si se está comunicando pasando paquetes que contienen eventos de registro en lugar de escribir los mensajes de registro formateados.
No soy un experto en C ++, pero esto podría ayudar ...
Creo que el problema que está viendo aquí, al redirigir a un archivo, se debe a que la biblioteca cstdio intenta ser inteligente. Mi entendimiento es que en Linux, los iostreams de C ++ finalmente envían su salida a la biblioteca cstdio.
En el inicio, la biblioteca cstdio detecta si está enviando la salida a un terminal o a un archivo. Si la salida va a la terminal, entonces stdio tiene búfer de línea. Si la salida va a un archivo, entonces stdio se convierte en bloque en búfer.
La salida a stderr nunca se almacena en búfer, por lo que se enviará de inmediato.
Para las soluciones, podría intentar usar fflush
en la fflush
, o podría considerar usar la función setvbuf
en la setvbuf
para forzar la salida con búfer de línea (o incluso la salida sin búfer si lo desea). Algo así debería hacer que stdout sea setvbuf(stdout, NULL, _IOLBF, 0)
búfer de línea setvbuf(stdout, NULL, _IOLBF, 0)
.
Respondiendo a mi propia pregunta
Finalmente, viene de un error en CMake .
CTest no puede administrar el orden de los dos búferes, y luego no le importa el orden exacto de la salida.
Se resolverá en CMake> = 3.4.