Logging en sistemas UNIX
UNIX tiene sus propios archivos de log para escribir información que viene de los servidores y programas en ejecución. En la mayoría de los sistemas UNIX estos archivos se pueden encontrar en el directorio/var/log
.Sin embargo los archivos de log de algunos servicios populares comoApacheoNginxse pueden encontrar en cualquier lugar, dependiendo de su configuración.
Registrar y almacenar información de log en archivos es una forma práctica de examinar datos e información de tu software de forma asincrónica, ya sea localmente, en un servidor de log central o utilizando otros software como Elasticsearch, Beats y Grafana Loki.
El logging service de UNIX admite dos propiedades denominadaslogging levelylogging facility.Ellogging leveles un valor que especifica la gravedad de la entrada de logging. Hay varios niveles, incluidosdebug
,info
,notice
,warning
,err
,crit
,alert
,yemerg
,en orden inverso de gravedad.
El paquete de logging de la biblioteca estándar de Go no admite trabajar conlogging levels.
Lalogging facilityes como una categoría utilizada para registrar información. El valor de la parte delogging facilitypuede serauth
,authpriv
,cron
,daemon
,kern
,lpr
,mail
,mark
,news
,syslog
,user
,
UUCP
,local0
,local1
,local2
,local3
,local4
,local5
,local6
olocal7
,y se define dentro de/etc/syslog.conf
,/etc/rsyslog.conf
u otro archivo apropiado dependiendo del proceso del servidor utilizado para el logging del sistema en tu máquina UNIX. Esto significa que si una logging facility no se define correctamente, no se manejará; por lo tanto, los mensajes de logging que le envíe podrían ignorarse y, por lo tanto, perderse.
Logging al trabajar en Go
El paquetelog
envía mensajes de log a la salida de error estándar del sistema. Parte del paquetelog
es el paquetelog/syslog
,que te permite enviar mensajes de log al servidorsyslog
de tu máquina. Aunque de forma predeterminada el log se escribe en la salida de error estándar, el uso delog.SetOutput()
modifica ese comportamiento. La lista de funciones para enviar datos de registro incluyelog.Printf()
,log.Print()
,log.Println()
,log.Fatalf()
,log.Fatalln()
,log.Panic()
,log.Panicln()
ylog.Panicf()
.
El logging es para el código de la aplicación, no para el código de bibliotecas. Si estás desarrollando bibliotecas, no realices logging en ellas.
Escribir en el archivo de log principal del sistema es tan fácil como llamar asyslog.New()
con la opciónsyslog.LOG_SYSLOG
.Después de eso, debes decirle a tu programa en Go que toda la información de log va al nuevo registrador; esto se implementa con una llamada a la funciónlog.SetOutput()
.El proceso se ilustra en el siguiente código.
packagemain
import(
"log"
"log/syslog"
)
funcmain(){
sysLog,err:=syslog.New(syslog.LOG_SYSLOG,"systemLog.go")
iferr!=nil{
log.Println(err)
return
}else{
log.SetOutput(sysLog)
log.Print("Everything is fine!")
}
}
La ejecución de este código no genera ningún resultado. Sin embargo, si ejecutasjournalctl -xe
en una máquina Linux, podrás ver entradas como las siguientes:
Jun 08 20:46:05 thinkpad systemLog.go[4412]: 2023/06/08 20:46:05
Everything is fine!
Jun 08 20:46:51 thinkpad systemLog.go[4822]: 2023/06/08 20:46:51
Everything is fine!
Funcioneslog.Fatal()
ylog.Panic()
La funciónlog.Fatal()
se utiliza cuando sucede algo erróneo y solo deseas salir de tu programa lo antes posible después de informar esa mala situación. La llamada alog.Fatal()
finaliza un programa Go en el punto donde se llamó alog.Fatal()
después de imprimir un mensaje de error.
Hay situaciones en las que un programa está a punto de fallar definitivamente y deseas tener la mayor cantidad de información posible sobre la falla;log.Panic()
implica que algo realmente inesperado y desconocido, como no poder encontrar un archivo, ha sucedido. De manera análoga a la funciónlog.Fatal()
,log.Panic()
imprime un mensaje personalizado e inmediatamente finaliza el programa Go.
Veamos un ejemplo de la utilización de ambas funciones.
packagemain
import(
"log"
"os"
)
funcmain(){
iflen(os.Args)!=1{
log.Fatal("Fatal: Hello World!")
}
log.Panic("Panic: Hello World!")
}
La ejecución sin argumentos del código anterior produce la siguiente salida:
$ go run logs.go
2023/06/08 20:48:42 Panic: Hello World!
panic: Panic: Hello World!
goroutine 1 [running]:
log.Panic({0xc000104f60?, 0x0?, 0x0?})
/usr/lib/go/src/log/log.go:384 +0x65
main.main()
/home/mtsouk/code/mGo4th/ch01/logs.go:12 +0x85
exit status 2
Escribir en un archivo de los personalizado
La mayoría de las veces, y especialmente en aplicaciones y servicios que se implementan en producción, es necesario escribir los datos de log en un archivo.
Veamos un ejemplo.
packagemain
import(
"fmt"
"log"
"os"
"path"
)
funcmain(){
LOGFILE:=path.Join(os.TempDir(),"mGo.log")
fmt.Println(LOGFILE)
f,err:=os.OpenFile(LOGFILE,os.O_APPEND|os.O_CREATE|os.O_WRONLY,0644)
iferr!=nil{
fmt.Println(err)
return
}
deferf.Close()
iLog:=log.New(f,"iLog",log.LstdFlags)
iLog.Println("Hello there!")
iLog.Println("Mastering Go 4th edition!")
}
La llamada aos.OpenFile()
crea el archivo de log para escritura, si aún no existe, o lo abre para escritura agregando nuevos datos al final (os.O_APPEND
).
Las últimas tres declaraciones crean un nuevo archivo de log basado en un archivo abierto (f
) y escriben dos mensajes en él, usandoPrintln()
.
Añadir el número de línea a las entradas de log
La funcionalidad deseada se implementa con el uso delog.Lshortfile
en los parámetros delog.New()
oSetFlags()
.El indicadorlog.Lshortfile
agrega el nombre del archivo así como el número de línea de la instrucción Go que imprimió la entrada de log en la propia entrada del registro. Si usaslog.Llongfile
en lugar delog.Lshortfile
,obtendrás la ruta completa del archivo fuente de Go.
Veamos un ejemplo.
packagemain
import(
"fmt"
"log"
"os"
"path"
)
funcmain(){
LOGFILE:=path.Join(os.TempDir(),"mGo.log")
fmt.Println(LOGFILE)
f,err:=os.OpenFile(LOGFILE,os.O_APPEND|os.O_CREATE|os.O_WRONLY,0644)
iferr!=nil{
fmt.Println(err)
return
}
deferf.Close()
LstdFlags:=log.Ldate|log.Lshortfile
iLog:=log.New(f,"LNum",LstdFlags)
iLog.Println("Mastering Go, 4th edition!")
iLog.SetFlags(log.Lshortfile|log.LstdFlags)
iLog.Println("Another log entry!")
}
Escribir en múltiples salidas de log
La funciónio.MultiWriter()
es la que nos permite escribir en múltiples destinos, que en este caso son un archivo llamadomyLog.log
y la salida de error estándar.
packagemain
import(
"fmt"
"io"
"log"
"os"
)
funcmain(){
flag:=os.O_APPEND|os.O_CREATE|os.O_WRONLY
file,err:=os.OpenFile("myLog.log",flag,0644)
iferr!=nil{
fmt.Println(err)
os.Exit(0)
}
deferfile.Close()
w:=io.MultiWriter(file,os.Stderr)
logger:=log.New(w,"myApp:",log.LstdFlags)
logger.Printf("BOOK %d",os.Getpid())
}
Conclusión
Si bien existen paquetes de la comunidad para todo tipo de funcionalidades extra para enriquecer nuestro logging, almacenar las entradas remotamente, etc.; el paquetelog
de la biblioteca estándar es muy interesante y en muchas ocaciones es todo lo que necesitamos para nuestros desarrollos en Go.