DEV Community

Cover image for Cómo escribir archivos de log en Go
Fabián Karaben
Fabián Karaben

Posted on

Cómo escribir archivos de log en Go

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!")
  }
}
Enter fullscreen mode Exit fullscreen mode

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!

Enter fullscreen mode Exit fullscreen mode

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!")
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!")
}
Enter fullscreen mode Exit fullscreen mode

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!")
}
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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)