Logrus es un registrador estructurado para Go (golang), completamente compatible con API con el registrador de biblioteca estándar.
Logrus está en modo de mantenimiento. No introduciremos nuevas funciones. Simplemente es demasiado difícil hacerlo de una manera que no rompa los proyectos de muchas personas, que es lo último que desea de su biblioteca de Logging (nuevamente...).
Esto no significa que Logrus esté muerto. Logrus seguirá manteniéndose por motivos de seguridad, corrección de errores (compatible con versiones anteriores) y rendimiento (donde estamos limitados por la interfaz).
Creo que la mayor contribución de Logrus es haber desempeñado un papel en el uso generalizado actual del registro estructurado en Golang. No parece haber una razón para hacer una iteración importante y revolucionaria en Logrus V2, ya que la fantástica comunidad de Go las ha construido de forma independiente. Han surgido muchas alternativas fantásticas. Logrus se vería así si hubiera sido rediseñado con lo que sabemos hoy sobre el registro estructurado en Go. Consulte, por ejemplo, Zerolog, Zap y Apex.
¿Ves problemas extraños que distinguen entre mayúsculas y minúsculas? En el pasado era posible importar Logrus tanto en mayúsculas como en minúsculas. Debido al entorno del paquete Go, esto causó problemas en la comunidad y necesitábamos un estándar. Algunos entornos experimentaron problemas con la variante en mayúsculas, por lo que se decidió por la minúscula. Todo lo que use logrus
deberá usar minúsculas: github.com/sirupsen/logrus
. Cualquier paquete que no lo sea debe cambiarse.
Para arreglar Glide, vea estos comentarios. Para obtener una explicación detallada del problema de la carcasa, consulte este comentario.
Muy bien codificado por colores en desarrollo (cuando se adjunta un TTY; de lo contrario, solo texto sin formato):
Con log.SetFormatter(&log.JSONFormatter{})
, para un fácil análisis mediante logstash o Splunk:
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
Con el log.SetFormatter(&log.TextFormatter{})
predeterminado cuando no se adjunta un TTY, la salida es compatible con el formato logfmt:
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
Para garantizar este comportamiento incluso si se adjunta un TTY, configure su formateador de la siguiente manera:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
Si desea agregar el método de llamada como un campo, instruya al registrador a través de:
log . SetReportCaller ( true )
Esto agrega a la persona que llama como "método" así:
{ "animal" : " penguin " , "level" : " fatal " , "method" : " github.com/sirupsen/arcticcreatures.migrate " , "msg" : " a penguin swims by " ,
"time" : " 2014-03-10 19:57:38.562543129 -0400 EDT " }
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
Tenga en cuenta que esto agrega una sobrecarga mensurable: el costo dependerá de la versión de Go, pero está entre el 20 y el 40 % en pruebas recientes con 1.6 y 1.7. Puede validar esto en su entorno mediante puntos de referencia:
go test -bench=.*CallerTracing
El nombre de la organización se cambió a minúsculas y no se volverá a cambiar. Si tiene conflictos de importación debido a la distinción entre mayúsculas y minúsculas, utilice la importación en minúsculas: github.com/sirupsen/logrus
.
La forma más sencilla de utilizar Logrus es simplemente el registrador exportado a nivel de paquete:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
Tenga en cuenta que es completamente compatible con API con el registrador stdlib, por lo que puede reemplazar sus importaciones log
en todas partes con log "github.com/sirupsen/logrus"
y ahora tendrá la flexibilidad de Logrus. Puedes personalizarlo todo lo que quieras:
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init () {
// Log as JSON instead of the default ASCII formatter.
log . SetFormatter ( & log. JSONFormatter {})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
log . SetOutput ( os . Stdout )
// Only log the warning severity or above.
log . SetLevel ( log . WarnLevel )
}
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 122 ,
}). Warn ( "The group's number increased tremendously!" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 100 ,
}). Fatal ( "The ice breaks!" )
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log . WithFields (log. Fields {
"common" : "this is a common field" ,
"other" : "I also should be logged always" ,
})
contextLogger . Info ( "I'll be logged with common and other field" )
contextLogger . Info ( "Me too" )
}
Para un uso más avanzado, como iniciar sesión en varias ubicaciones desde la misma aplicación, también puede crear una instancia de logrus
Logger:
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus . New ()
func main () {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log . Out = os . Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log . WithFields (logrus. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
}
Logrus fomenta un registro cuidadoso y estructurado a través de campos de registro en lugar de mensajes de error largos e imposibles de analizar. Por ejemplo, en lugar de: log.Fatalf("Failed to send event %s to topic %s with key %d")
, deberías registrar lo mucho más reconocible:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
Descubrimos que esta API te obliga a pensar en iniciar sesión de una manera que produzca mensajes de registro mucho más útiles. Hemos estado en innumerables situaciones en las que un solo campo agregado a una declaración de registro que ya estaba allí nos habría ahorrado horas. La llamada WithFields
es opcional.
En general, con Logrus el uso de cualquiera de las funciones de la familia printf
debe verse como una sugerencia de que debe agregar un campo; sin embargo, aún puede usar las funciones de la familia printf
con Logrus.
A menudo es útil tener campos siempre adjuntos a las declaraciones de registro en una aplicación o partes de una. Por ejemplo, es posible que desee registrar siempre request_id
y user_ip
en el contexto de una solicitud. En lugar de escribir log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
en cada línea, puedes crear un logrus.Entry
para pasar:
requestLogger := log . WithFields (log. Fields { "request_id" : request_id , "user_ip" : user_ip })
requestLogger. Info ( "something happened on that request" ) # will log request_id and user_ip
requestLogger . Warn ( "something not great happened" )
Puede agregar ganchos para niveles de registro. Por ejemplo, para enviar errores a un servicio de seguimiento de excepciones en Error
, Fatal
y Panic
, información a StatsD o iniciar sesión en varios lugares simultáneamente, por ejemplo, syslog.
Logrus viene con ganchos incorporados. Agregue esos, o su gancho personalizado, en init
:
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init () {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log . AddHook ( airbrake . NewHook ( 123 , "xyz" , "production" ))
hook , err := logrus_syslog . NewSyslogHook ( "udp" , "localhost:514" , syslog . LOG_INFO , "" )
if err != nil {
log . Error ( "Unable to connect to local syslog daemon" )
} else {
log . AddHook ( hook )
}
}
Nota: El enlace Syslog también admite la conexión al syslog local (por ejemplo, "/dev/log" o "/var/run/syslog" o "/var/run/log"). Para obtener más información, consulte el archivo README del enlace syslog.
Puede encontrar una lista de enlaces de servicio actualmente conocidos en esta página wiki
Logrus tiene siete niveles de registro: seguimiento, depuración, información, advertencia, error, fatal y pánico.
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 logging
log . Fatal ( "Bye." )
// Calls panic() after logging
log . Panic ( "I'm bailing." )
Puede configurar el nivel de registro en un Logger
, luego solo registrará entradas con esa gravedad o cualquier nivel superior:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
Puede resultar útil configurar log.Level = logrus.DebugLevel
en un entorno de depuración o detallado si su aplicación lo tiene.
Nota: Si desea diferentes niveles de registro para el registro global ( log.SetLevel(...)
) y syslog, consulte el archivo README del enlace syslog.
Además de los campos agregados con WithField
o WithFields
algunos campos se agregan automáticamente a todos los eventos de registro:
time
. La marca de tiempo en la que se creó la entrada.msg
. El mensaje de registro pasó a {Info,Warn,Error,Fatal,Panic}
después de la llamada AddFields
. Por ejemplo, Failed to send event.
level
. El nivel de registro. Por ejemplo info
. Logrus no tiene noción del medio ambiente.
Si desea que los ganchos y formateadores solo se utilicen en entornos específicos, debe encargarse de ello usted mismo. Por ejemplo, si su aplicación tiene una variable global Environment
, que es una representación de cadena del entorno, podría hacer:
import (
log "github.com/sirupsen/logrus"
)
func init () {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log . SetFormatter ( & log. JSONFormatter {})
} else {
// The TextFormatter is default, you don't actually have to do this.
log . SetFormatter ( & log. TextFormatter {})
}
}
Esta configuración es la forma en que se pretendía usar logrus
, pero JSON en producción solo es útil si agrega registros con herramientas como Splunk o Logstash.
Los formateadores de registro integrados son:
logrus.TextFormatter
. Registra el evento en colores si la salida estándar es un tty; en caso contrario, sin colores.ForceColors
en true
. Para forzar que no haya salida en color incluso si hay un TTY, establezca el campo DisableColors
en true
. Para Windows, consulte github.com/mattn/go-colorable.DisableLevelTruncation
en true
.PadLevelText
en true
habilita este comportamiento, agregando relleno al texto del nivel.logrus.JSONFormatter
. Registra campos como JSON.Formateadores de registro de terceros:
FluentdFormatter
. Da formato a las entradas que Kubernetes y Google Container Engine pueden analizar.GELF
. Formatea las entradas para que cumplan con la especificación GELF 1.1 de Graylog.logstash
. Registra campos como eventos de Logstash.prefixed
. Muestra la fuente de entrada del registro junto con un diseño alternativo.zalgo
. Invocando el poder de Zalgo.nested-logrus-formatter
. Convierte campos de Logrus en una estructura anidada.powerful-logrus-formatter
. obtenga el nombre del archivo, el número de línea del registro y el nombre de la última función al imprimir el registro; Guarde el registro en archivos.caption-json-formatter
. Se agregó el formateador json del mensaje de logrus con un título legible por humanos. Puede definir su formateador implementando la interfaz Formatter
, que requiere un método Format
. Format
toma una *Entry
. entry.Data
es un tipo Fields
( map[string]interface{}
) con todos sus campos, así como los predeterminados (consulte la sección Entradas más arriba):
type MyJSONFormatter struct {
}
log . SetFormatter ( new ( MyJSONFormatter ))
func ( f * MyJSONFormatter ) Format ( entry * Entry ) ([] byte , error ) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized , err := json . Marshal ( entry . Data )
if err != nil {
return nil , fmt . Errorf ( "Failed to marshal fields to JSON, %w" , err )
}
return append ( serialized , 'n' ), nil
}
io.Writer
Logrus se puede transformar en un io.Writer
. Ese escritor es el final de un io.Pipe
y es su responsabilidad cerrarlo.
w := logger . Writer ()
defer w . Close ()
srv := http. Server {
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog : log . New ( w , "" , 0 ),
}
Cada línea escrita para ese escritor se imprimirá de la forma habitual, utilizando formateadores y ganchos. El nivel para esas entradas es info
.
Esto significa que podemos anular el registrador de biblioteca estándar fácilmente:
logger := logrus . New ()
logger . Formatter = & logrus. JSONFormatter {}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log . SetOutput ( logger . Writer ())
Logrus no proporciona la rotación de registros. La rotación de registros debe realizarse mediante un programa externo (como logrotate(8)
) que pueda comprimir y eliminar entradas de registros antiguas. No debería ser una característica del registrador a nivel de aplicación.
Herramienta | Descripción |
---|---|
Compañero de Logrus | Logrus mate es una herramienta para que Logrus administre registradores. Puede inicializar el nivel del registrador, conectarlo y formatearlo mediante el archivo de configuración; el registrador se generará con diferentes configuraciones en diferentes entornos. |
Ayudante de víbora de Logrus | ¡Un ayudante alrededor de Logrus para envolver con spf13/Viper para cargar la configuración con colmillos! Y para simplificar la configuración de Logrus, utilice algún comportamiento de Logrus Mate. muestra |
Logrus tiene una función integrada para afirmar la presencia de mensajes de registro. Esto se implementa a través del enlace test
y proporciona:
test.NewLocal
y test.NewGlobal
) que básicamente solo agrega el gancho test
test.NewNullLogger
) que solo registra mensajes de registro (y no genera ninguno): import (
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething ( t * testing. T ){
logger , hook := test . NewNullLogger ()
logger . Error ( "Helloerror" )
assert . Equal ( t , 1 , len ( hook . Entries ))
assert . Equal ( t , logrus . ErrorLevel , hook . LastEntry (). Level )
assert . Equal ( t , "Helloerror" , hook . LastEntry (). Message )
hook . Reset ()
assert . Nil ( t , hook . LastEntry ())
}
Logrus puede registrar una o más funciones que se llamarán cuando se registre cualquier mensaje de nivel fatal
. Los controladores registrados se ejecutarán antes de que logrus realice un os.Exit(1)
. Este comportamiento puede resultar útil si las personas que llaman necesitan cerrar sin problemas. A diferencia de una llamada panic("Something went wrong...")
que puede interceptarse con una recover
diferida, una llamada a os.Exit(1)
no puede interceptarse.
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
De forma predeterminada, Logger está protegido por un mutex para escrituras simultáneas. El mutex se mantiene al llamar a ganchos y escribir registros. Si está seguro de que dicho bloqueo no es necesario, puede llamar a logger.SetNoLock() para desactivar el bloqueo.
La situación en la que no es necesario bloquear incluye:
No tiene ganchos registrados o las llamadas a ganchos ya son seguras para subprocesos.
Escribir en logger.Out ya es seguro para subprocesos, por ejemplo:
logger.Out está protegido por cerraduras.
logger.Out es un controlador os.File abierto con el indicador O_APPEND
y cada escritura es inferior a 4k. (Esto permite la escritura multiproceso/multiproceso)
(Consulte http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)