[C con Clase] Logueo de errores...

Steven Davidson srd4121 en njit.edu
Vie Ago 13 22:52:08 CEST 2010


Hola Gilberto,

Gilberto Cuba Ricardo wrote:
> Hola Steven,
> 
> La verdad es que nunca me imaginé que todo esto pudiera salir con las
> excepciones. Siempre las había visto ahí, mirándolas con poco ojo
> crítico, y las utilicé muy vágamente hace mucho en delphi, pero de ahí
> nunca más las había tocado. He mirado este capítulo completo y me
> pareció bastante bueno para mí como principiante. Luego he revisado el
> sitio: http://ww.zator.com/Cpp/E1_6_1a.htm que me permitió profundizar
> aún más; pero ahora, toda esta lectura me ha dejado la impresión de
> que es bastante trabajoso, y que hay que ir llevando el "sistema de
> excepciones", a la medida que se va construyendo la aplicación. Es
> casi imposible, a mi enteder, poder insertarle el tratamiento de las
> excepciones, a una aplicación que se encuentra terminada.
> 

Son de esas cosas que sabes que existen, pero no las usas con 
frecuencia, hasta que las necesites :)

> Existe algún modelo en específico o acercamiento a él, de como poder
> insertar el tratamiento de las excepciones en el modelo de una
> aplicación cualquiera. La idea es que no sabría como diseñar lo
> suficientemente bien este sistema en una aplicación, de forma tal que
> sea lo más genérico posible. Ahora leo a Stroustrup B. [3rd Ed.] "The
> C++ Programing Languaje", que espero me de un poco de vista.
> 

Esto depende del control que quieras tener. En general, vas a tener que 
fijarte en cada posible excepción y aplicar la resolución que quieras. 
Esto es lo que te ofrece el sistema de excepciones. Puedes resolver el 
problema inmediato y continuar con el programa, por lo que no es 
necesario el comportamiento de una terminación abrupta del programa.

Ahora bien, si ya tienes pensado el mismo comportamiento de abortar el 
programa para cualquier excepción, entonces quizá no te interese el 
sistema de excepciones. En su lugar, puedes usar las funciones 
estándares para registrar una función propia para que se invoque al 
abortarse el programa. Esto se basa en recibir la señal 'SIGABRT'. Por 
ejemplo,

void antes_de_abortar( int n )
{
   ofstream ofsBitacora( "error.log", ios::app );
   ofsBitacora << "Mensaje" << endl;
}

int main()
{
   signal( SIGABRT, antes_de_abortar );
   ...
}

Si prefieres mayor control para detallar el comportamiento de cada 
excepción, entonces tendrás que usar el sistema de excepciones o 
implementar uno parecido. Obviamente, te aconsejo que uses el sistema de 
excepciones que trae C++ porque ya está hecho.

> 
> Esta segunda opción me parece a mí también la más correcta en cuanto 
> al diseño del sistema de logueo, de forma tal que sea lo más cercano
> a la realidad lo que se pueda leer en el archivo.
> 
> A todas estas ya he ido desarrollando algunas cositas para poder ir
> armándome de algo genérico en cuanto al tratamiento de la excepciones
> y, a parte de pensar en como realizar determinados volcados de
> información de algunos objetos, aún no sé como diseñarlo. He estado
> trabajado en como poder loguear, para el peor de los casos la línea,
> nombre del fichero y función en la que se produce la excepción. Para
> ello he utilizado a: __LINE__, __FUNCTION__ y __FILE__; que
> anteriormente los había utilizado para compilaciones del tipo "Debug".
> Pero hoy he visto que también me funciona para el "Release". Mi
> preocupación ahora es si con la utilización de estas no vaya a estar
> incluyendo en el ejecutable, información referente a "Debug" y el
> archivo crezca notoriamente.
> 

Las definiciones estándares de '__LINE__' y '__FILE__' son símbolos del 
precompilador. Por lo tanto, no tienen nada que ver con el depurador. 
Por ejemplo,

int main()
{
   int n = __LINE__;

   return 0;
}

El precompilador reescribirá el código fuente para que el compilador vea 
esto:

int main()
{
   int n = 3;

   return 0;
}

No tiene más misterio, la verdad.

En cuanto a __FUNCTION__, esta constante simbólica no es estándar, pero 
sí aparece en el compilador de GNU.

Ten presente, que si quieres usar estas constantes, tendrás que hacer 
obtener esta información justo en el momento en que se produce el error. 
Esto significa que no obtendrás la información correcta si usas el 
sistema de excepciones. Acabarías haciendo esto, por ejemplo,

#define bitacora(b,m)\
b << "Error en la línea #" << __LINE__ \
<< " de la función '" << __FUNCTION__ \
<< "' en el fichero \"" << __FILE__ << "\": " << m
...
int main()
{
   ofstream ofsBitacora( "error.log", ios::app );

   if( bErrorDesbordamiento )
     bitacora( ofsBitacora, "Desbordamiento" ) << endl;
   ...
}

Existe otra forma para poder usar estas constantes simbólicas dentro del 
sistema de excepciones, pero tendrás que contar las líneas. Por ejemplo,

struct MiExcepcion
{
   string sFuncion;
   string sFichero;

   MiExcepcion( const string &func, const string &fich )
   : sFuncion(func), sFichero(fich)  {}
};

class MiClase
{
private:
   int *pLista;
   int nCant;

public:
   MiClase() : pLista(0), nCant(0);

   int dato( int n ) const
   {
     if( n >= nCant )
       throw MiExcepcion( "int MiClase::dato(int)", __FILE__ );
     return pLista[n];
   }
};

int main()
{
   MiClase obj;

   try
   {
     int num = obj.dato(1000);
   }
   catch( MiExcepcion &ref )
   {
     ofstream ofsBitacora( "error.log", ios::app );
     ofsBitacora << "Error en la línea #" << __LINE__-5
                 << " de la función '" << ref.sFuncion
                 << "' en el fichero \"" << ref.sFichero << "\": "
                 << "Desbordamiento" << endl;
   }
   catch( ... )
   {
     ofstream ofsBitacora( "error.log", ios::app );
     ofsBitacora << "Error: mal de ojo" << endl;
   }

   return 0;
}

Como puedes ver, usamos __LINE__, pero restamos 5 para indicar 5 líneas 
anteriores; justo donde se encuentra la invocación de 'dato()'.

Supongo que la otra forma de hacer esto es guardando el código fuente 
entero como una series de cadenas de caracteres, como si perteneciese a 
una base de datos. De esta forma, implementas los usos de __LINE__, 
__FUNCTION__, y __FILE__ dentro de tu programa. Nuevamente, tienes mayor 
control, pero estás "reinventando la rueda", como se suele decir.

En general, no es necesario conocer la línea, función, y fichero fuente 
(o de cabecera) donde se produce el error. Si no necesitas conocer esta 
información, entonces creo que con un mensaje detallado del error o de 
la excepción nos ahorramos "problemas" y así podemos recurrir a la 
bitácora y al sistema de excepciones de C++.


Espero que todo esto te ayude.

Steven





Más información sobre la lista de distribución Cconclase