Saiba como registrar no Go
Os logs desempenham uma função significativa em um programa porque se tornam uma fonte de informações que você pode verificar quando algo dá errado. Normalmente, quando ocorre um erro, os usuários finais veem apenas uma mensagem que indica que há um problema com o programa. Da perspectiva do desenvolvedor, precisamos de mais informações do que uma mensagem de erro simples. Isso ocorre principalmente porque queremos reproduzir o problema para escrever uma correção adequada. Neste módulo, você saberá como o registro em log funciona no Go. Você também aprenderá algumas práticas que sempre deve implementar.
O pacote log
Para começar, o Go oferece um pacote padrão simples para trabalhar com logs. Você pode usá-lo de maneira semelhante a como usa o pacote fmt
. O pacote padrão não fornece níveis de log nem permite que você configure agentes separados para cada pacote. Se precisar escrever configurações de registro em log mais complexas, você poderá fazer isso usando uma estrutura de registros. Abordaremos as estruturas de registros mais tarde.
Está é a maneira mais fácil de usar logs:
import (
"log"
)
func main() {
log.Print("Hey, I'm a log!")
}
Ao executar o código acima, você obtém esta saída:
2020/12/19 13:39:17 Hey, I'm a log!
Por padrão, a função log.Print()
inclui a data e a hora como o prefixo da mensagem de log. Você pode obter o mesmo comportamento usando fmt.Print()
, mas pode fazer outras coisas com o pacote log
, como enviar logs para um arquivo. Examinaremos a funcionalidade do pacote log
com mais detalhes posteriormente.
Use a função log.Fatal()
para registrar um erro e encerrar o programa, como se tivesse usado os.Exit(1)
. Para experimentar, vamos usar este snippet de código:
package main
import (
"fmt"
"log"
)
func main() {
log.Fatal("Hey, I'm an error log!")
fmt.Print("Can you see me?")
}
Ao executar o código acima, você obtém esta saída:
2020/12/19 13:53:19 Hey, I'm an error log!
exit status 1
Observe como a última linha, fmt.Print("Can you see me?")
, não é executada. Isso ocorre porque a chamada de função log.Fatal()
interrompe o programa. Você obtém um comportamento semelhante quando usa a função log.Panic()
, que também chama a função panic()
, dessa forma:
package main
import (
"fmt"
"log"
)
func main() {
log.Panic("Hey, I'm an error log!")
fmt.Print("Can you see me?")
}
Ao executar o código acima, você obtém esta saída:
2020/12/19 13:53:19 Hey, I'm an error log!
panic: Hey, I'm an error log!
goroutine 1 [running]:
log.Panic(0xc000060f58, 0x1, 0x1)
/usr/local/Cellar/go/1.15.5/libexec/src/log/log.go:351 +0xae
main.main()
/Users/christian/go/src/helloworld/logs.go:9 +0x65
exit status 2
Você ainda está recebendo a mensagem de log, mas agora também recebe o rastreamento de pilha do erro.
Outra função essencial é log.SetPrefix()
. Você pode usá-la para adicionar um prefixo às mensagens de log do programa. Por exemplo, você pode usar este snippet de código:
package main
import (
"log"
)
func main() {
log.SetPrefix("main(): ")
log.Print("Hey, I'm a log!")
log.Fatal("Hey, I'm an error log!")
}
Ao executar o código acima, você obtém esta saída:
main(): 2021/01/05 13:59:58 Hey, I'm a log!
main(): 2021/01/05 13:59:58 Hey, I'm an error log!
exit status 1
Você define o prefixo uma vez e seus logs incluirão informações como o nome da função da qual o log veio.
Você pode explorar outras funções no site do Go.
Registrando em log em um arquivo
Além de imprimir logs no console, talvez você queira enviar os logs a um arquivo para poder processá-los mais tarde ou em tempo real.
Por que você desejaria enviar logs para um arquivo? Primeiro, talvez você queira ocultar informações específicas de seus usuários finais. Eles podem não estar interessados ou você pode estar expondo informações confidenciais. Quando tem logs em arquivos, você pode centralizar todos os logs em uma mesma localização e correlacioná-los com outros eventos. Este padrão é típico para ter aplicativos distribuídos que podem ser efêmeros, como contêineres.
Vamos usar o seguinte código para testar o envio de logs a um arquivo:
package main
import (
"log"
"os"
)
func main() {
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
log.SetOutput(file)
log.Print("Hey, I'm a log!")
}
Ao executar o código anterior, você não verá nada no console. Em seu diretório, você deve ver um novo arquivo chamado info.log que contém os logs que você enviou usando a função log.Print()
. Observe que você precisa começar criando ou abrindo um arquivo e configurando o pacote log
para enviar toda a saída para um arquivo. Em seguida, você pode continuar usando a função log.Print()
como faria normalmente.
Estruturas de registro em log
Por fim, pode haver ocasiões em que as funções do pacote log
não são suficientes. Talvez você ache útil usar uma estrutura de registros em vez de escrever as suas bibliotecas. Algumas estruturas de registros para o Go são o Logrus, o zerolog, o zap e o Apex.
Vamos explorar o que podemos fazer com o zerolog.
Primeiramente, você precisa instalar o pacote. Se já está trabalhando nesta série, você provavelmente está usando módulos do Go, portanto, não precisa fazer nada. Por via das dúvidas, você pode executar este comando em sua estação de trabalho para instalar as bibliotecas zerolog:
go get -u github.com/rs/zerolog/log
Agora, use este snippet de código para experimentar:
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Print("Hey! I'm a log message!")
}
Ao executar o código acima, você obtém esta saída:
{"level":"debug","time":1609855453,"message":"Hey! I'm a log message!"}
Observe como você só precisa incluir os nomes de importação corretos e pode continuar usando a função log.Print()
como faria normalmente. Além disso, observe como a saída é alterada para o formato JSON. O JSON é um formato útil para os logs quando você executa pesquisas em uma localização centralizada.
Outro recurso útil é que você pode incluir dados de contexto rapidamente, desta forma:
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Debug().
Int("EmployeeID", 1001).
Msg("Getting employee information")
log.Debug().
Str("Name", "John").
Send()
}
Ao executar o código acima, você obtém esta saída:
{"level":"debug","EmployeeID":1001,"time":1609855731,"message":"Getting employee information"}
{"level":"debug","Name":"John","time":1609855731}
Observe como adicionamos um contexto à ID do funcionário. Ela se torna parte da logline como outra propriedade. Além disso, é importante destacar que os campos que você inclui são fortemente tipados.
Você pode implementar outros recursos com o zerolog, por exemplo, usar o registro em log nivelado, usar rastreamentos de pilha formatados e usar mais de uma instância do agente para gerenciar saídas diferentes. Para obter mais informações, confira o site do GitHub.