Logrus est un logger structuré pour Go (golang), entièrement compatible API avec le logger de bibliothèque standard.
Logrus est en mode maintenance. Nous n'introduireons pas de nouvelles fonctionnalités. C'est tout simplement trop difficile à faire d'une manière qui ne brise pas les projets de nombreuses personnes, ce qui est la dernière chose que vous attendez de votre bibliothèque Logging (encore une fois...).
Cela ne veut pas dire que Logrus est mort. Logrus continuera à être maintenu pour des raisons de sécurité, de corrections de bugs (rétrocompatibles) et de performances (là où nous sommes limités par l'interface).
Je pense que la plus grande contribution de Logrus est d'avoir joué un rôle dans l'utilisation généralisée aujourd'hui de l'exploitation forestière structurée à Golang. Il ne semble pas y avoir de raison de faire une itération majeure dans Logrus V2, puisque la fantastique communauté Go les a construites de manière indépendante. De nombreuses alternatives fantastiques ont vu le jour. Logrus ressemblerait à cela s'il avait été repensé avec ce que nous savons aujourd'hui sur la journalisation structurée dans Go. Découvrez, par exemple, Zerolog, Zap et Apex.
Vous voyez des problèmes étranges sensibles à la casse ? Dans le passé, il était possible d'importer Logrus en majuscules et en minuscules. En raison de l'environnement du package Go, cela a causé des problèmes dans la communauté et nous avions besoin d'un standard. Certains environnements ont rencontré des problèmes avec la variante majuscule, c'est pourquoi la variante minuscule a été choisie. Tout ce qui utilise logrus
devra utiliser les minuscules : github.com/sirupsen/logrus
. Tout paquet qui ne l'est pas doit être modifié.
Pour corriger Glide, consultez ces commentaires. Pour une explication détaillée du problème du boîtier, voir ce commentaire.
Joliment codé par couleur en cours de développement (lorsqu'un ATS est joint, sinon juste du texte brut) :
Avec log.SetFormatter(&log.JSONFormatter{})
, pour une analyse facile par logstash ou 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"}
Avec le log.SetFormatter(&log.TextFormatter{})
par défaut lorsqu'aucun TTY n'est joint, la sortie est compatible avec le format 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
Pour garantir ce comportement même si un TTY est joint, configurez votre formateur comme suit :
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
Si vous souhaitez ajouter la méthode appelante en tant que champ, instruisez le logger via :
log . SetReportCaller ( true )
Cela ajoute l'appelant en tant que « méthode », comme ceci :
{ "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
Notez que cela ajoute une surcharge mesurable – le coût dépendra de la version de Go, mais se situe entre 20 et 40 % dans les tests récents avec 1.6 et 1.7. Vous pouvez valider cela dans votre environnement via des benchmarks :
go test -bench=.*CallerTracing
Le nom de l'organisation a été modifié en minuscules et ne sera pas modifié. Si vous rencontrez des conflits d'importation en raison du respect de la casse, veuillez utiliser l'importation en minuscules : github.com/sirupsen/logrus
.
La manière la plus simple d'utiliser Logrus est simplement d'utiliser le logger exporté au niveau du package :
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
Notez qu'il est entièrement compatible avec l'API du logger stdlib, vous pouvez donc remplacer vos importations log
partout par log "github.com/sirupsen/logrus"
et vous aurez désormais la flexibilité de Logrus. Vous pouvez le personnaliser autant que vous le souhaitez :
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" )
}
Pour une utilisation plus avancée, telle que la connexion à plusieurs emplacements à partir de la même application, vous pouvez également créer une instance 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 encourage une journalisation minutieuse et structurée via des champs de journalisation au lieu de messages d'erreur longs et impossibles à analyser. Par exemple, au lieu de : log.Fatalf("Failed to send event %s to topic %s with key %d")
, vous devez enregistrer ce qui est beaucoup plus détectable :
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
Nous avons constaté que cette API vous oblige à réfléchir à la journalisation de manière à produire des messages de journalisation beaucoup plus utiles. Nous avons été dans d'innombrables situations où un seul champ ajouté à une instruction de journal déjà présente nous aurait fait gagner des heures. L'appel WithFields
est facultatif.
En général, avec Logrus, l'utilisation de l'une des fonctions printf
-family doit être considérée comme un indice que vous devez ajouter un champ, cependant, vous pouvez toujours utiliser les fonctions printf
-family avec Logrus.
Il est souvent utile d'avoir des champs toujours attachés aux instructions de journalisation dans une application ou des parties de celle-ci. Par exemple, vous souhaiterez peut-être toujours enregistrer request_id
et user_ip
dans le contexte d'une requête. Au lieu d'écrire log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
sur chaque ligne, vous pouvez créer un logrus.Entry
à transmettre à la place :
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" )
Vous pouvez ajouter des hooks pour les niveaux de journalisation. Par exemple, pour envoyer des erreurs à un service de suivi des exceptions sur Error
, Fatal
et Panic
, des informations à StatsD ou pour vous connecter à plusieurs endroits simultanément, par exemple syslog.
Logrus est livré avec des crochets intégrés. Ajoutez-les, ou votre hook personnalisé, dans 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 )
}
}
Remarque : le hook Syslog prend également en charge la connexion au syslog local (Ex. "/dev/log" ou "/var/run/syslog" ou "/var/run/log"). Pour plus de détails, veuillez consulter le hook README de Syslog.
Une liste des hooks de service actuellement connus peut être trouvée sur cette page wiki
Logrus dispose de sept niveaux de journalisation : Trace, Debug, Info, Warning, Error, Fatal et Panic.
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." )
Vous pouvez définir le niveau de journalisation sur un Logger
, il n'enregistrera alors que les entrées avec cette gravité ou tout ce qui est supérieur :
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
Il peut être utile de définir log.Level = logrus.DebugLevel
dans un environnement de débogage ou détaillé si votre application en dispose.
Remarque : Si vous souhaitez différents niveaux de journalisation pour la journalisation globale ( log.SetLevel(...)
) et syslog, veuillez consulter le hook syslog README.
Outre les champs ajoutés avec WithField
ou WithFields
certains champs sont automatiquement ajoutés à tous les événements de journalisation :
time
. L'horodatage de la création de l'entrée.msg
. Le message de journalisation transmis à {Info,Warn,Error,Fatal,Panic}
après l'appel AddFields
. Par exemple, Failed to send event.
level
. Le niveau de journalisation. Par exemple info
. Logrus n'a aucune notion d'environnement.
Si vous souhaitez que les hooks et les formateurs ne soient utilisés que dans des environnements spécifiques, vous devez vous en occuper vous-même. Par exemple, si votre application possède une variable globale Environment
, qui est une représentation sous forme de chaîne de l'environnement, vous pouvez effectuer :
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 {})
}
}
Cette configuration correspond à la façon dont logrus
était destiné à être utilisé, mais JSON en production n'est généralement utile que si vous effectuez une agrégation de journaux avec des outils comme Splunk ou Logstash.
Les formateurs de journalisation intégrés sont :
logrus.TextFormatter
. Enregistre l'événement en couleurs si la sortie standard est un terminal, sinon sans couleurs.ForceColors
sur true
. Pour forcer l'absence de sortie colorée même s'il existe un TTY, définissez le champ DisableColors
sur true
. Pour Windows, consultez github.com/mattn/go-colorable.DisableLevelTruncation
sur true
.PadLevelText
sur true
active ce comportement, en ajoutant un remplissage au texte du niveau.logrus.JSONFormatter
. Enregistre les champs au format JSON.Formateurs de journalisation tiers :
FluentdFormatter
. Formate les entrées qui peuvent être analysées par Kubernetes et Google Container Engine.GELF
. Formate les entrées afin qu'elles soient conformes à la spécification GELF 1.1 de Graylog.logstash
. Enregistre les champs en tant qu'événements Logstash.prefixed
. Affiche la source d’entrée du journal ainsi qu’une présentation alternative.zalgo
. Invoquer le pouvoir de Zalgo.nested-logrus-formatter
. Convertit les champs Logrus en une structure imbriquée.powerful-logrus-formatter
. obtenir le nom du fichier, le numéro de ligne du journal et le nom de la dernière fonction lors de l'impression du journal ; Enregistrez le journal dans des fichiers.caption-json-formatter
. Formateur JSON de message de Logrus avec légende lisible par l'homme ajoutée. Vous pouvez définir votre formateur en implémentant l'interface Formatter
, nécessitant une méthode Format
. Format
prend un *Entry
. entry.Data
est un type Fields
( map[string]interface{}
) avec tous vos champs ainsi que ceux par défaut (voir la section Entrées ci-dessus) :
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 peut être transformé en io.Writer
. Cet écrivain est la fin d'un io.Pipe
et il est de votre responsabilité de le fermer.
w := logger . Writer ()
defer w . Close ()
srv := http. Server {
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog : log . New ( w , "" , 0 ),
}
Chaque ligne écrite sur cet écrivain sera imprimée de la manière habituelle, à l'aide de formateurs et de hooks. Le niveau de ces entrées est info
.
Cela signifie que nous pouvons facilement remplacer le logger de bibliothèque standard :
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 ())
La rotation des journaux n'est pas fournie avec Logrus. La rotation des journaux doit être effectuée par un programme externe (comme logrotate(8)
) qui peut compresser et supprimer les anciennes entrées de journal. Cela ne devrait pas être une fonctionnalité de l'enregistreur au niveau de l'application.
Outil | Description |
---|---|
Compagnon du Logrus | Logrus mate est un outil permettant à Logrus de gérer les enregistreurs, vous pouvez initialiser le niveau de l'enregistreur, le hook et le formateur par fichier de configuration, l'enregistreur sera généré avec différentes configurations dans différents environnements. |
Aide-vipère du Logrus | Un assistant autour de Logrus à envelopper avec spf13/Viper pour charger la configuration avec Fangs ! Et pour simplifier la configuration de Logrus, utilisez certains comportements de Logrus Mate. échantillon |
Logrus dispose d'une fonction intégrée pour affirmer la présence de messages de journal. Ceci est implémenté via le hook test
et fournit :
test.NewLocal
et test.NewGlobal
) qui ajoute simplement le crochet test
test.NewNullLogger
) qui enregistre simplement les messages du journal (et n'en génère aucun) : 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 peut enregistrer une ou plusieurs fonctions qui seront appelées lorsqu'un message de niveau fatal
est enregistré. Les gestionnaires enregistrés seront exécutés avant que logrus n'effectue un os.Exit(1)
. Ce comportement peut être utile si les appelants doivent s'arrêter correctement. Contrairement à un appel panic("Something went wrong...")
qui peut être intercepté avec une recover
différée, un appel à os.Exit(1)
ne peut pas être intercepté.
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
Par défaut, Logger est protégé par un mutex pour les écritures simultanées. Le mutex est maintenu lors de l'appel de hooks et de l'écriture de journaux. Si vous êtes sûr qu'un tel verrouillage n'est pas nécessaire, vous pouvez appeler logger.SetNoLock() pour désactiver le verrouillage.
Les situations dans lesquelles le verrouillage n'est pas nécessaire incluent :
Vous n'avez aucun hook enregistré ou l'appel des hooks est déjà thread-safe.
L'écriture dans logger.Out est déjà thread-safe, par exemple :
logger.Out est protégé par des verrous.
logger.Out est un gestionnaire os.File ouvert avec l'indicateur O_APPEND
et chaque écriture est inférieure à 4 Ko. (Cela permet l'écriture multi-thread/multi-processus)
(Reportez-vous à http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)