Registro en Go: Elegir un sistema y utilizarlo

Eres relativamente nuevo en el lenguaje Go. Probablemente lo estás usando para escribir una aplicación web o un servidor, y necesitas crear un archivo de registro. Así que, haces una búsqueda rápida en la web y encuentras que hay un montón de opciones para el registro en Go. ¿Cómo sabes cuál elegir? Este artículo le equipará para responder a esa pregunta.

Echaremos un vistazo al paquete logincorporado y determinaremos para qué proyectos es adecuado antes de explorar otras soluciones de registro que prevalecen en el ecosistema Go.

Qué registrar

No necesito decirte lo importante que es el registro. Los registros son utilizados por cada aplicación web de producción para ayudar a los desarrolladores y a las operaciones:

  • Detectar errores en el código de la aplicación
  • Descubrir problemas de rendimiento
  • Hacer un análisis post-mortem de las interrupciones e incidentes de seguridad

Los datos que realmente registre dependerán del tipo de aplicación que esté construyendo. La mayoría de las veces, tendrá alguna variación en lo siguiente:

  • La marca de tiempo para cuando se produjo un evento o se generó un registro
  • Niveles de registro como depuración, error o información
  • Datos contextuales para ayudar a entender lo que sucedió y hacer posible reproducir fácilmente la situación

Lo que no se debe registrar

En general, no debe registrar ninguna forma de datos comerciales sensibles o información de identificación personal. Esto incluye, pero no se limita a:

  • Nombres
  • Direcciones IP
  • Números de tarjetas de crédito

Estas restricciones pueden hacer que los registros sean menos útiles desde una perspectiva de ingeniería, pero hacen que su aplicación sea más segura. En muchos casos, regulaciones como GDPR e HIPAA pueden prohibir el registro de datos personales.

Presentación del paquete de registro

La biblioteca estándar de Go tiene un paquete log incorporado que proporciona la mayoría de las características básicas de registro. Aunque no tiene niveles de registro (como depuración, advertencia o error), todavía proporciona todo lo necesario para obtener una estrategia básica de registro configurada.

Aquí está el ejemplo más básico de registro:

package mainimport "log"func main() { log.Println("Hello world!")}

El código anterior imprime el texto «¡Hola mundo!» al error estándar, pero también incluye la fecha y la hora, lo cual es útil para filtrar los mensajes de registro por fecha.

2019/12/09 17:21:53 Hello world!

Por defecto, el paquete log imprime en el flujo de salida de error estándar (stderr), pero puedes hacer que escriba en archivos locales o en cualquier destino que soporte la interfaz io.Writer. También añade una marca de tiempo al mensaje de registro sin ninguna configuración adicional.

Logging a un archivo

Si necesita almacenar los mensajes de registro en un archivo, puede hacerlo creando un nuevo archivo o abriendo un archivo existente y estableciéndolo como salida del registro. Aquí hay un ejemplo:

package mainimport ( "log" "os")func main() { // If the file doesn't exist, create it or append to the file file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } log.SetOutput(file) log.Println("Hello world!")}

Cuando ejecutamos el código, lo siguiente se escribe en logs.txt.

2019/12/09 17:22:47 Hello world!

Como se mencionó anteriormente, básicamente puedes dar salida a tus registros a cualquier destino que implemente la interfaz io.Writer, por lo que tienes mucha flexibilidad a la hora de decidir dónde registrar los mensajes en tu aplicación.

Creación de loggers personalizados

Aunque el paquete log implementa un logger predefinido que escribe en el error estándar, podemos crear tipos de loggers personalizados utilizando el método log.New().

Cuando se crea un nuevo logger, es necesario pasar tres argumentos a log.New():

  • out: Cualquier tipo que implemente la interfaz io.Writer, que es donde se escribirán los datos del registro
  • prefix: Una cadena que se añade al principio de cada línea de registro
  • flag: Un conjunto de constantes que nos permiten definir qué propiedades de registro incluir en cada entrada de registro generada por el logger (más sobre esto en la siguiente sección)

Podemos aprovechar esta característica para crear loggers personalizados. Aquí hay un ejemplo que implementa los loggers Info, Warning y Error:

package mainimport ( "log" "os")var ( WarningLogger *log.Logger InfoLogger *log.Logger ErrorLogger *log.Logger)func init() { file, err := os.OpenFile("logs.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) WarningLogger = log.New(file, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)}func main() { InfoLogger.Println("Starting the application...") InfoLogger.Println("Something noteworthy happened") WarningLogger.Println("There is something you should know about") ErrorLogger.Println("Something went wrong")}

Después de crear o abrir el archivo logs.txt en la parte superior de la función init, entonces inicializamos los tres loggers definidos proporcionando el destino de salida, la cadena de prefijo y las banderas de registro.

En la función main, los registradores se utilizan llamando a la función Println, que escribe una nueva entrada de registro en el archivo de registro. Cuando ejecute este programa, se escribirá lo siguiente en logs.txt.

INFO: 2019/12/09 12:01:06 main.go:26: Starting the application...INFO: 2019/12/09 12:01:06 main.go:27: Something noteworthy happenedWARNING: 2019/12/09 12:01:06 main.go:28: There is something you should know aboutERROR: 2019/12/09 12:01:06 main.go:29: Something went wrong

Note que en este ejemplo, estamos registrando en un solo archivo, pero puede usar un archivo separado para cada registrador pasando un archivo diferente al crear el registrador.

Banderas de registro

Puede usar constantes de bandera de registro para enriquecer un mensaje de registro proporcionando información de contexto adicional, como el archivo, número de línea, fecha y hora. Por ejemplo, pasar el mensaje «Algo salió mal» a través de un registrador con una combinación de banderas que se muestra a continuación:

log.Ldate|log.Ltime|log.Lshortfile

imprimirá

2019/12/09 12:01:06 main.go:29: Something went wrong

Desgraciadamente, no hay control sobre el orden en que aparecen o el formato en que se presentan.

Introducción de marcos de registro

Usar el paquete log es genial para el desarrollo local cuando obtener una retroalimentación rápida es más importante que generar registros ricos y estructurados. Más allá de eso, es probable que sea mejor utilizar un marco de registro.

Una de las principales ventajas de utilizar un marco de registro es que ayuda a estandarizar los datos de registro. Esto significa que:

  • Es más fácil leer y entender los datos de registro.
  • Es más fácil reunir registros de varias fuentes y alimentarlos a una plataforma central para ser analizados.

Además, el registro es prácticamente un problema resuelto. ¿Por qué reinventar la rueda?

Elegir un marco de trabajo de registro

Decidir qué marco de trabajo utilizar puede ser un reto, ya que hay varias opciones para elegir.

Los dos marcos de trabajo de registro más populares para Go parecen ser beglog y logrus. La popularidad de glog es sorprendente, ya que no se ha actualizado en varios años. logrus está mejor mantenido y se utiliza en proyectos populares como Docker, por lo que nos centraremos en él.

Cómo empezar con logrus

Instalar logrus es tan sencillo como ejecutar el siguiente comando en tu terminal:

go get "github.com/Sirupsen/logrus"

¡Una cosa genial de logrus es que es completamente compatible con el paquete log de la librería estándar, así que puedes reemplazar tus importaciones de logs en todas partes con log "github.com/sirupsen/logrus" y simplemente funcionará!

Modifiquemos nuestro anterior ejemplo de «hola mundo» que utilizaba el paquete log y utilicemos logrus en su lugar:

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.Println("Hello world!")}

La ejecución de este código produce la salida:

INFO Hello world!

¡No podría ser más fácil!

Logging en JSON

logrus es muy adecuado para el registro estructurado en JSON que – como JSON es un estándar bien definido – hace que sea fácil para los servicios externos para analizar sus registros y también hace que la adición de contexto a un mensaje de registro relativamente sencillo a través del uso de campos, como se muestra a continuación:

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) log.WithFields( log.Fields{ "foo": "foo", "bar": "bar", }, ).Info("Something happened")}

La salida del registro generado será un objeto JSON que incluye el mensaje, el nivel de registro, la marca de tiempo, y los campos incluidos.

{"bar":"bar","foo":"foo","level":"info","msg":"Something happened","time":"2019-12-09T15:55:24+01:00"}

Si no está interesado en la salida de sus registros como JSON, tenga en cuenta que existen varios formateadores de terceros para logrus, que puede ver en su página de Github. Incluso puede escribir su propio formateador si lo prefiere.

Niveles de registro

A diferencia del paquete de registro estándar, logrus soporta niveles de registro.

Logus tiene siete niveles de registro: Trace, Debug, Info, Warn, Error, Fatal y Panic. La gravedad de cada nivel aumenta a medida que se desciende en la lista.

log.Trace("Something very low level.")log.Debug("Useful debugging information.")log.Info("Something noteworthy happened!")log.Warn("You should probably take a look at this.")log.Error("Something failed but I'm not quitting.")// Calls os.Exit(1) after logginglog.Fatal("Bye.")// Calls panic() after logginglog.Panic("I'm bailing.")

Al establecer un nivel de registro en un logger, puede registrar sólo las entradas que necesita dependiendo de su entorno. Por defecto, logrus registrará cualquier cosa que sea Info o superior (Warn, Error, Fatal, o Panic).

package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) log.Debug("Useful debugging information.") log.Info("Something noteworthy happened!") log.Warn("You should probably take a look at this.") log.Error("Something failed but I'm not quitting.")}

La ejecución del código anterior producirá la siguiente salida:

{"level":"info","msg":"Something noteworthy happened!","time":"2019-12-09T16:18:21+01:00"}{"level":"warning","msg":"You should probably take a look at this.","time":"2019-12-09T16:18:21+01:00"}{"level":"error","msg":"Something failed but I'm not quitting.","time":"2019-12-09T16:18:21+01:00"}

Nota que el mensaje de nivel Debug no fue impreso. Para incluirlo en los registros, establezca log.Level igual a log.DebugLevel:

log.SetLevel(log.DebugLevel)

Resumen

En este post, exploramos el uso del paquete de registro incorporado y establecimos que sólo debe utilizarse para aplicaciones triviales o al construir un prototipo rápido. Para todo lo demás, el uso de un marco de registro de la corriente principal es una necesidad.

También vimos maneras de asegurar que la información contenida en sus registros es consistente y fácil de analizar, especialmente cuando se agrega en una plataforma centralizada.

¡Gracias por leer!

Leave a Reply