sábado, 18 de septiembre de 2010

Iostream - Redireccionar clog para "loguear" en un archivo

La iostream es la librería de entrada y salida de C++. Existen algunos streams estándares de salida: std::cout, std::cerr y std::clog. El primero apunta a la consola, y los dos últimos... también. Las diferencias son estas:
  • cout apunta a la salida estándar STDOUT (texto de resultado esperado de un programa);
  • cerr y clog apuntan a STDERR (salida de errores y cualquier otra porquería).
Aunque a primera vista todo el texto se envía a la consola, uno puede redireccionar la salida a otros lados. Por ejemplo, imaginemos que tenemos el siguiente programa test.exe:
#include <iostream>

using namespace std;

int main()
{
  cout << "A\n";
  cerr << "B\n";
  clog << "C\n";
  return 0;
}
Al ejecutarlo, obtenemos por pantalla las tres líneas:
test.exe [ENTER]
A
B
C
Pero resulta interesante saber que podemos redireccionar el STDOUT a un archivo y el STDERR a otro. Ejemplo:
test.exe 1>stdout.txt 2>stderr.txt
¿Qué demonios es 1 y 2? Los archivos tienen un descriptor que los identifica, 1 es para la STDOUT, y 2 para STDERR. El signo mayor (>) significa que "quiero redireccionar toda la salida de texto que vaya para este descriptor a este archivo". En el anterior ejemplo logramos obtener dos archivos distintos, stdout.txt que contiene una línea (A), y stderr.txt que contiene dos líneas (B y C).

Generalmente, los logs van a un archivo, no a la pantalla. Aunque por defecto clog mande todo a STDERR, resulta útil redireccionar este stream a un archivo propio (por ejemplo, test.log). De esta forma, podemos hacer uso de clog para "loguear" todo lo que nuestro programa hace.

¿Cómo se redirecciona clog? Básicamente los streams de C++ tienen un streambuf asociado, y éste es el realmente encargado de leer y escribir datos (en la pantalla, en un archivo, en un string en memoria, etc.). Por lo tanto, si creamos un fstream y le colocamos su propio streambuf a clog, podemos usar clog como un "alias" del fstream original (clog va a estar compartiendo el mismo streambuf que el fstream). El código resultante es bastante sencillo:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
  // Creamos un archivo de salida para logging.
  ofstream test_log;
  test_log.open("test.log");

  // Obtenemos el streambuf actual de clog (esto
  // lo usaremos luego para restaurar el streambuf
  // a su valor original, por si las moscas).
  streambuf* old_rdbuf = clog.rdbuf();

  // Reemplazamos el streambuf de clog con el del archivo.
  // Ahora ambos streams utilizarán el mismo streambuf (es
  // decir, escriben en el archivo test.log).
  clog.rdbuf(test_log.rdbuf());

  // Hacemos lo mismo que el ejemplo original.
  cout << "A\n";
  cerr << "B\n";
  clog << "C\n";
  
  // Restauramos el viejo streambuf de clog.
  clog.rdbuf(old_rdbuf);

  // Cerramos el archivo.
  test_log.close();
  return 0;
}
Y listo, ahora podemos hacer lo mismo que antes:
test.exe 1>stdout.txt 2>stderr.txt
Con lo cual obtenemos tres archivos:
  • stdout.txt con la línea A.
  • stderr.txt con la línea B.
  • test.log con la línea C.