Es una cuestión sumamente importante porque a diferencia de las aplicaciones domésticas estas aplicaciones son utilizadas por otras personas en otras instalaciones donde pueden darse un sinfin de circunstancias que hagan que algo aparentemente correcto no funcione bien.
El sistema de Logs además tiene que ser muy claro para poder trazar lo ocurrido. Se trata de que un cliente pueda reclamar ante un fallo de la aplicación y que nosotros podamos precisar rápidamente donde está el error. Es por ello importante que el sistema de logs sea configurable para que podamos aumentar o disminuir la cantidad de información volcada en los logs.
También hay que tener en cuenta la posibilidad de procesos concurrentes que debe llevar cada uno su propio log y uno mas general que permita trazar lo ocurrido rápidamente.
La solución a todo esto es la siguiente:
En un módulo declarar una variable de tipo FILE pública.
También es necesaria una variable pública del nivel de logs que queremos realizar. Este nivel de logs se corresponde con la profundidad de niveles en la estructura de las funciones que debemos realizar.
En principio se debe tender a una línea log por función en cada nivel respectivo. En operaciones muy críticas he visto que el número de log tiene dos dígitos. Esto es porque el segundo dígito si es 0 significa que solo va una línea por función, pero si es 1 entonces hay una línea de log a la entrada de cada función y una línea a la salida.
El informe debe mostrar una consistencia documental. Eso significa que a niveles de profundidad mayores, también debe haber un indentado mayor que suele ser un tab.
También hay veces que alguna función no hace una cosa sino varias. En ese caso la función no debería devolver una línea sino varias. La primera mas general indicando la operación realizada y las demás suelen ir con una indentación y algunas flechas del tipo ====> para luego poner las operaciones y valores devueltos (una por línea).
Al iniciar el log se debe incluir siempre unas líneas indicando la siguiente información:
1. Usuario y máquina desde la que se solicita la operación
2. Fecha y hora de inicio del log
3. Ubicación del archivo (path completo).
4. El nombre y versión de la aplicación ejecutada
5. El path donde la aplicación vuelca valores si lo hay
6. El charset del sistema y lenguaje utilizado si compete.
Con esta sencilla regla se podrá deducir los permisos que ese usuario y máquina tenía en ese momento lo cual puede explicar la causa de algún error. La ubicación del archvo es para evitar que se pierda la ubicación que le corresponde si por alguna razón se mueve de su lugar natural.
El nombre del log siempre debe incluir al menos el nombre de la aplicación o función que logea, la fecha y la hora. A menudo también se incluye algún contador o valor de control para distinguir rápidamente el fichero en una gran lista de logs. todos ellos deben llevar la extensión .log
Debe haber un sistema de rotación de logs para evitar en lo posible conservar la información y evitar el llenado de file systems. En sistemas profesionales es muy recomendable dejar /var/log en una partición aparte. Por tanto necesitaremos una variable para definir el volumen ocupado de logs que requerirá la compresión de los archivos. Cada archivo comprimido debe contener el nombre de la aplicación y un contador que indique el orden en que los diversos archivos de log están guardados.
De manera que nuestra clase para la gestión de logs tendrá las siguientes líneas
Y ahora vendría el código.
La función genérica de logear. Cuando damos la orden de logear se comprueba si el ficheor está o no abierto. Si no lo está lo crea, imprime cabeceras y luego las líneas.
La función de cabecera. Esta función se utiliza únicamente al iniciar el log. Decide el nombre del log, crea el archivo e imprime la información correspondiente
private function CabeceraLog()
Dim Nombre as string
Nombre=ObtenerNombre
F=new file
F=open nombre for create
print #F, "------------------------------------------------------------"
print #F,"- Usuario: " & user.name & "/" system.domain & " (" & system.host & ")"
print #F,"- Fecha: " & Format(now,"dd/mm/yyyy hh:mm:ss")
print #F,"- Log: " & nombre
print #F,"- Aplicación: " & gb.application.name & " " & gb.application.version
print #F,"- Path: " gb.application.path
print #F,"- Idioma: " & gb.application.language
print #F,"- Charset: " & gb.application.charset
print #F,"--------------------------------------------------------------"
print #F," "
HoraInicial=now
end
Dim Nombre as string
Nombre=ObtenerNombre
F=new file
F=open nombre for create
print #F, "------------------------------------------------------------"
print #F,"- Usuario: " & user.name & "/" system.domain & " (" & system.host & ")"
print #F,"- Fecha: " & Format(now,"dd/mm/yyyy hh:mm:ss")
print #F,"- Log: " & nombre
print #F,"- Aplicación: " & gb.application.name & " " & gb.application.version
print #F,"- Path: " gb.application.path
print #F,"- Idioma: " & gb.application.language
print #F,"- Charset: " & gb.application.charset
print #F,"--------------------------------------------------------------"
print #F," "
HoraInicial=now
end
Private function ObtenerNombre() as string
Dim PathLog as string, Nombre as string, Contador as integer, NombreAux as string
PathLog=settings["Log/Path",user.home"] '---> obtener el path de una variable de configuración
Contador=1
Nombre=PathLog &/ gb.application.name & "_" & format(now,"ddmmyyyyhhmmss")
NombreAux=Nombre
do while exist(NombreAux & ".log")
NombreAux=Nombre & "_" & Contador
Contador +=1
loop
return nombreAux
end
Dim PathLog as string, Nombre as string, Contador as integer, NombreAux as string
PathLog=settings["Log/Path",user.home"] '---> obtener el path de una variable de configuración
Contador=1
Nombre=PathLog &/ gb.application.name & "_" & format(now,"ddmmyyyyhhmmss")
NombreAux=Nombre
do while exist(NombreAux & ".log")
NombreAux=Nombre & "_" & Contador
Contador +=1
loop
return nombreAux
end
Esta rutina es las mas compleja (no por su complejidad sino por la cuestión conceptual(
Se debe escribir una línea por función así que en un esquema de programación como este:
Este esquema tiene un nivel máximo de log de 4. En nivel 0 corresponde a main y el nivel cuatro corresponde a funcion XXXX. Si se indica un nivel de log de valor 2 debería logearse únicamente lo que ocurra en Main y Funcion X.
Por tanto para cada nivel de profunidad también se corresponde con un nivel de indentación en el log.
También ocurre que una función puede obtener varios valores. Si se desea es posible que una función imprima varias líneas. Es por ello que la función de imprimir línea no recibe como parámetro un string sino un array de strings donde cada elemento es una línea a imprimir.
En este caso la primera línea tiene a la izquierda la hora. Las demás sufren una indentación y tienen una flecha.
private Sub Cuerpo(F as File, NivelLog as integer, Linea as string[])
Dim Hora as string, Contador as integer, Indentado as string
Hora="[" & format(now,"hh:mm:ss") & "] "
For Contador=1 to NivelLog '---> crear el indentado para el nivel correspondiente
Indentado &= gb.tab
loop
For Contador=0 to Linea.max '---> si es una línea normal poner la hora y el texto. Si no, poner la flecha y el texto
print #F,iif(Contador=0,Hora & Indentado & Texto[0],Indentado & "=>" & texto[contador])
next
end
Dim Hora as string, Contador as integer, Indentado as string
Hora="[" & format(now,"hh:mm:ss") & "] "
For Contador=1 to NivelLog '---> crear el indentado para el nivel correspondiente
Indentado &= gb.tab
loop
For Contador=0 to Linea.max '---> si es una línea normal poner la hora y el texto. Si no, poner la flecha y el texto
print #F,iif(Contador=0,Hora & Indentado & Texto[0],Indentado & "=>" & texto[contador])
next
end
De esta forma en nuestra aplicación pondremos una línea indicando la operación que realizamos
log(nivel,"texto")
Donde el nivel será en main el 0, en funcion X el 1, en funcion XX el 2, en función XXX el 3.
Caso de que una función tuviera que logear varias líneas
texto.add("liena 1")
texto.add("linea 2")
texto.add("linea 3")
log(Nivel,Texto)
texto.add("linea 2")
texto.add("linea 3")
log(Nivel,Texto)