Logrus é um registrador estruturado para Go (golang), totalmente compatível com API com o registrador de biblioteca padrão.
Logrus está em modo de manutenção. Não apresentaremos novos recursos. É simplesmente muito difícil de fazer de uma forma que não interrompa os projetos de muitas pessoas, que é a última coisa que você deseja da sua biblioteca Logging (de novo...).
Isso não significa que Logrus esteja morto. Logrus continuará a ser mantido para segurança, correções de bugs (compatíveis com versões anteriores) e desempenho (onde somos limitados pela interface).
Acredito que a maior contribuição da Logrus é ter desempenhado um papel no uso generalizado atual da exploração madeireira estruturada em Golang. Não parece haver uma razão para fazer uma iteração importante e inovadora no Logrus V2, já que a fantástica comunidade Go os construiu de forma independente. Muitas alternativas fantásticas surgiram. Logrus seria assim se tivesse sido redesenhado com o que sabemos sobre registro estruturado em Go hoje. Confira, por exemplo, Zerolog, Zap e Apex.
Está vendo problemas estranhos que diferenciam maiúsculas de minúsculas? No passado, era possível importar Logrus em letras maiúsculas e minúsculas. Devido ao ambiente do pacote Go, isso causou problemas na comunidade e precisávamos de um padrão. Alguns ambientes tiveram problemas com a variante maiúscula, então a minúscula foi decidida. Tudo que usa logrus
precisará usar letras minúsculas: github.com/sirupsen/logrus
. Qualquer pacote que não seja, deve ser alterado.
Para corrigir o Glide, veja estes comentários. Para uma explicação detalhada do problema do invólucro, consulte este comentário.
Bem codificado por cores no desenvolvimento (quando um TTY está anexado, caso contrário, apenas texto simples):
Com log.SetFormatter(&log.JSONFormatter{})
, para fácil análise por 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"}
Com o log.SetFormatter(&log.TextFormatter{})
padrão quando um TTY não está anexado, a saída é compatível com o 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 garantir esse comportamento mesmo se um TTY estiver anexado, configure seu formatador da seguinte forma:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
Se você deseja adicionar o método de chamada como um campo, instrua o registrador via:
log . SetReportCaller ( true )
Isso adiciona o chamador como 'método' da seguinte forma:
{ "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
Observe que isso adiciona sobrecarga mensurável - o custo dependerá da versão do Go, mas está entre 20 e 40% em testes recentes com 1.6 e 1.7. Você pode validar isso em seu ambiente por meio de benchmarks:
go test -bench=.*CallerTracing
O nome da organização foi alterado para letras minúsculas e não será alterado novamente. Se você estiver tendo conflitos de importação devido à distinção entre maiúsculas e minúsculas, use a importação em minúsculas: github.com/sirupsen/logrus
.
A maneira mais simples de usar o Logrus é simplesmente o registrador exportado em nível de pacote:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
Observe que ele é totalmente compatível com API com o logger stdlib, então você pode substituir suas importações log
em qualquer lugar pelo log "github.com/sirupsen/logrus"
e agora você terá a flexibilidade do Logrus. Você pode personalizar tudo o que quiser:
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 uso mais avançado, como registrar em vários locais do mesmo aplicativo, você também pode criar uma instância do 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 incentiva o registro cuidadoso e estruturado por meio de campos de registro em vez de mensagens de erro longas e intransitáveis. Por exemplo, em vez de: log.Fatalf("Failed to send event %s to topic %s with key %d")
, você deve registrar o muito mais detectável:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
Descobrimos que essa API força você a pensar no registro de uma forma que produza mensagens de registro muito mais úteis. Já estivemos em inúmeras situações em que apenas um único campo adicionado a uma instrução de log que já estava lá nos pouparia horas. A chamada WithFields
é opcional.
Em geral, com o Logrus, o uso de qualquer uma das funções da família printf
deve ser visto como uma dica de que você deve adicionar um campo, no entanto, você ainda pode usar as funções da família printf
com o Logrus.
Muitas vezes é útil ter campos sempre anexados às instruções de log em um aplicativo ou em partes de um. Por exemplo, você pode querer sempre registrar request_id
e user_ip
no contexto de uma solicitação. Em vez de escrever log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
em cada linha, você pode criar um logrus.Entry
para passar:
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" )
Você pode adicionar ganchos para níveis de registro. Por exemplo, para enviar erros para um serviço de rastreamento de exceções em Error
, Fatal
e Panic
, informações para StatsD ou registrar em vários locais simultaneamente, por exemplo, syslog.
Logrus vem com ganchos integrados. Adicione esses, ou seu gancho personalizado, em 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: O gancho do Syslog também suporta conexão com o syslog local (Ex. "/dev/log" ou "/var/run/syslog" ou "/var/run/log"). Para obter detalhes, verifique o README do gancho do syslog.
Uma lista de ganchos de serviço atualmente conhecidos pode ser encontrada nesta página wiki
Logrus possui sete níveis de registro: Trace, Debug, Info, Warning, Error, Fatal e 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." )
Você pode definir o nível de registro em Logger
, então ele registrará apenas entradas com essa gravidade ou qualquer coisa acima dela:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
Pode ser útil definir log.Level = logrus.DebugLevel
em um ambiente de depuração ou detalhado se seu aplicativo tiver isso.
Nota: Se você deseja diferentes níveis de log para log global ( log.SetLevel(...)
) e syslog, verifique o README do gancho syslog.
Além dos campos adicionados com WithField
ou WithFields
alguns campos são adicionados automaticamente a todos os eventos de registro:
time
. O carimbo de data/hora em que a entrada foi criada.msg
. A mensagem de registro passada para {Info,Warn,Error,Fatal,Panic}
após a chamada AddFields
. Por exemplo, Failed to send event.
level
. O nível de registro. Ex: info
. Logrus não tem noção de meio ambiente.
Se você deseja que ganchos e formatadores sejam usados apenas em ambientes específicos, você mesmo deve cuidar disso. Por exemplo, se seu aplicativo tiver uma variável global Environment
, que é uma representação em string do ambiente, você poderia fazer:
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 {})
}
}
Essa configuração é como logrus
foi planejado para ser usado, mas JSON em produção só é útil se você agregar logs com ferramentas como Splunk ou Logstash.
Os formatadores de registro integrados são:
logrus.TextFormatter
. Registra o evento em cores se stdout for tty, caso contrário, sem cores.ForceColors
como true
. Para forçar nenhuma saída colorida, mesmo que haja um TTY, defina o campo DisableColors
como true
. Para Windows, consulte github.com/mattn/go-colorable.DisableLevelTruncation
como true
.PadLevelText
como true
permite esse comportamento, adicionando preenchimento ao texto do nível.logrus.JSONFormatter
. Registra campos como JSON.Formatadores de registro de terceiros:
FluentdFormatter
. Formata entradas que podem ser analisadas pelo Kubernetes e pelo Google Container Engine.GELF
. Formata as entradas para que estejam em conformidade com a especificação GELF 1.1 do Graylog.logstash
. Registra campos como eventos do Logstash.prefixed
. Exibe a origem da entrada de log junto com o layout alternativo.zalgo
. Invocando o Poder de Zalgo.nested-logrus-formatter
. Converte campos logrus em uma estrutura aninhada.powerful-logrus-formatter
. obtenha fileName, número da linha do log e o nome da última função ao imprimir o log; Salve o log em arquivos.caption-json-formatter
. Formatador json de mensagem do logrus com legenda legível adicionada. Você pode definir seu formatador implementando a interface Formatter
, exigindo um método Format
. Format
leva um *Entry
. entry.Data
é um tipo Fields
( map[string]interface{}
) com todos os seus campos, bem como os padrão (consulte a seção Entradas acima):
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 pode ser transformado em um io.Writer
. Esse escritor é o fim de um io.Pipe
e é sua responsabilidade fechá-lo.
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 linha escrita para esse escritor será impressa da maneira usual, usando formatadores e ganchos. O nível para essas entradas é info
.
Isso significa que podemos substituir facilmente o registrador de biblioteca padrão:
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 ())
A rotação de log não é fornecida com Logrus. A rotação de log deve ser feita por um programa externo (como logrotate(8)
) que pode compactar e excluir entradas de log antigas. Não deve ser um recurso do criador de logs no nível do aplicativo.
Ferramenta | Descrição |
---|---|
Logrus Mate | Logrus mate é uma ferramenta para Logrus gerenciar loggers, você pode inicializar o nível do logger, gancho e formatador por arquivo de configuração, o logger será gerado com diferentes configurações em diferentes ambientes. |
Ajudante da Víbora Logrus | Um ajudante em torno do Logrus para usar spf13/Viper para carregar a configuração com presas! E para simplificar a configuração do Logrus use algum comportamento do Logrus Mate. amostra |
Logrus possui um recurso integrado para confirmar a presença de mensagens de log. Isso é implementado por meio do gancho test
e fornece:
test.NewLocal
e test.NewGlobal
) que basicamente apenas adiciona o gancho test
test.NewNullLogger
) que apenas registra mensagens de log (e não gera nenhuma): 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 pode registrar uma ou mais funções que serão chamadas quando alguma mensagem de nível fatal
for registrada. Os manipuladores registrados serão executados antes que logrus execute um os.Exit(1)
. Esse comportamento pode ser útil se os chamadores precisarem encerrar normalmente. Ao contrário de uma chamada panic("Something went wrong...")
que pode ser interceptada com uma recover
adiada, uma chamada para os.Exit(1)
não pode ser interceptada.
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
Por padrão, o Logger é protegido por um mutex para gravações simultâneas. O mutex é mantido ao chamar ganchos e gravar logs. Se tiver certeza de que esse bloqueio não é necessário, você pode chamar logger.SetNoLock() para desabilitar o bloqueio.
A situação em que o bloqueio não é necessário inclui:
Você não tem ganchos registrados ou a chamada de ganchos já é thread-safe.
Escrever em logger.Out já é thread-safe, por exemplo:
logger.Out é protegido por bloqueios.
logger.Out é um manipulador os.File aberto com o sinalizador O_APPEND
e cada gravação é menor que 4k. (Isso permite gravação multithread/multiprocesso)
(Consulte http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)