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 como Apache o Nginx se 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 denominadas logging level y logging facility. El logging level es un valor que especifica la gravedad de la entrada de logging. Hay varios niveles, incluidos debug
, info
, notice
, warning
, err
, crit
, alert
, y emerg
, en orden inverso de gravedad.
El paquete de logging de la biblioteca estándar de Go no admite trabajar con logging levels.
La logging facility es como una categoría utilizada para registrar información. El valor de la parte de logging facility puede ser auth
, authpriv
, cron
, daemon
, kern
, lpr
, mail
, mark
, news
, syslog
, user
,
UUCP
, local0
, local1
, local2
, local3
, local4
, local5
, local6
o local7
, 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 paquete log
envía mensajes de log a la salida de error estándar del sistema. Parte del paquete log
es el paquete log/syslog
, que te permite enviar mensajes de log al servidor syslog
de tu máquina. Aunque de forma predeterminada el log se escribe en la salida de error estándar, el uso de log.SetOutput()
modifica ese comportamiento. La lista de funciones para enviar datos de registro incluye log.Printf()
, log.Print()
, log.Println()
, log.Fatalf()
, log.Fatalln()
, log.Panic()
, log.Panicln()
y log.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 a syslog.New()
con la opción syslog.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ón log.SetOutput()
. El proceso se ilustra en el siguiente código.
package main
import (
"log"
"log/syslog"
)
func main() {
sysLog, err := syslog.New(syslog.LOG_SYSLOG, "systemLog.go")
if err != 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 ejecutas journalctl -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!
Funciones log.Fatal()
y log.Panic()
La función log.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 a log.Fatal()
finaliza un programa Go en el punto donde se llamó a log.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ón log.Fatal()
, log.Panic()
imprime un mensaje personalizado e inmediatamente finaliza el programa Go.
Veamos un ejemplo de la utilización de ambas funciones.
package main
import (
"log"
"os"
)
func main() {
if len(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.
package main
import (
"fmt"
"log"
"os"
"path"
)
func main() {
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)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
iLog := log.New(f, "iLog ", log.LstdFlags)
iLog.Println("Hello there!")
iLog.Println("Mastering Go 4th edition!")
}
La llamada a os.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, usando Println()
.
Añadir el número de línea a las entradas de log
La funcionalidad deseada se implementa con el uso de log.Lshortfile
en los parámetros de log.New()
o SetFlags()
. El indicador log.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 usas log.Llongfile
en lugar de log.Lshortfile
, obtendrás la ruta completa del archivo fuente de Go.
Veamos un ejemplo.
package main
import (
"fmt"
"log"
"os"
"path"
)
func main() {
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)
if err != nil {
fmt.Println(err)
return
}
defer f.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ón io.MultiWriter()
es la que nos permite escribir en múltiples destinos, que en este caso son un archivo llamado myLog.log
y la salida de error estándar.
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
flag := os.O_APPEND | os.O_CREATE | os.O_WRONLY
file, err := os.OpenFile("myLog.log", flag, 0644)
if err != nil {
fmt.Println(err)
os.Exit(0)
}
defer file.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 paquete log
de la biblioteca estándar es muy interesante y en muchas ocaciones es todo lo que necesitamos para nuestros desarrollos en Go.
Top comments (0)