Lançado recentemente, o Go 1.21 trouxe um novo package padrão na linguagem, o log/slog. Embora Go tenha um package de logs desde seu lançamento, ao longo dos anos, ficou claro que as aplicações Go precisavam de logs estruturados.
Diferente do formato padrão em texto puro, logs estruturados utilizam o formato chave=valor, facilitando muito na hora de fazer parse, buscar e analisar os logs.
Embora existam vários packages open source que desempenham esse papel, sendo o logrus um dos mais famosos, todos os anos, a comunidade continuava pedindo ao time do Go que essa funcionalidade fosse adicionada ao package oficial da linguagem.
Nesse post, vou explorar um pouco desse package, mostrando as principais funções e formas de utilização.
Mesmo sem saber ainda se será uma série ou não, nesse post, trago uma visão geral sobre um dos frameworks mais populares do mundo Go, o Echo.
Em sua documentação, o framework Echo se autodenomina como de alta performance, extensível e minimalista. Além dessas características, podemos dizer também que é de fácil implementação. Veja o exemplo abaixo.
Se você é novo no mundo Go, muito provavelmente ainda não tenha trabalhado diretamente com o package reflect. Na verdade, é completamente normal pessoas que já trabalhem com a linguagem a algum tempo e ainda não tenham utilizado tal package.
Isso por que esse package tem uma utilidade muito específica. Não que todos os packages não tenham, ou pelo menos deveriam ter. Mas no caso do reflect, sua especificidade é tão grande, que mesmo em sistemas que o utilizam, ele provavelmente fica em alguma parte obscura, complexa e pouco mantida.
Tendo dito tudo isso e, ao mesmo tempo falado muito pouco, nesse post explicarei tudo o que você precisa saber sobre o package reflect e suas funções TypeOf e ValueOf.
Reflect
Antes de entender o que as funções TypeOf e ValueOf tem a oferecer, falemos um pouco sobre a finalidade do package reflect.
Um package padrão do Go e que, no meu ponto de vista, é pouco explorado, é o text/template. Com a ajuda desse package, podemos preencher os valores em qualquer template no formato texto, como por exemplo, arquivos JSON, YAML e TXT.
Para exemplificar melhor, vamos criar um arquivo YAML com o nome template.yaml e o seguinte conteúdo.
name: {{ .Name }}
age: {{ .Age }}
Com o template criado, podemos iniciar nosso main.go.
Embora o conteúdo daquele post continue sendo válido, para alguns casos mais simples de race condition, como por exemplo o do post, podemos utilizar o package sync/atomic para nos auxiliar.
Para dar o ponta pé inicial, vamos escrever um código que não irá funcionar corretamente por haver race condition.
package main
import (
"fmt"
"sync"
)
func main() {
var total int64
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for c := 0; c < 1000; c++ {
total += int64(c)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(total)
}
Require (github.com/stretchr/testify/require): Fornece as mesmas funções que o assert. A diferença é que ao invés de retornar um boolean, ao encontrar um erro, ele para os testes com um FailNow.
Suite (github.com/stretchr/testify/suite): Fornece funcionalidades para que você possa criar os testes utilizando uma abordagem de orientação a objetos, com métodos setup e teardown acoplados a sua struct.
Para começar, considerando o código abaixo, vamos ver como o package assert pode nos ajudar.
Se você já trabalha com Go a algum tempo, muito provavelmente você já conhece e utiliza o %s, %d e o \n. Pois bem, nesse post vamos abordar os principais os verbos disponíveis para te ajudar na hora de formatar uma string em Go.
Se você é novo em Go, saiba que os verbos que vamos abordar nesse post podem ser utilizados com as funções Printf, Sprintf e Errorf do package fmt, assim como as funções Fatalf, Panicf e Printf do package log.
Para começar, vamos a um exemplo bem simples utilizando somente os dois verbos que já comentamos. Digamos que nosso programa, sempre que alguém pede um novo café, exibe o nome da pessoa e a quantidade de café que ela consumiu no dia.
func main() {
name := "Tiago"
coffee := 5
fmt.Printf("Olá %s, você já bebeu %d cafés hoje", name, coffee)
}
Ao ser executado, o %s será substituido pelo conteúdo da váriavel name, e o %d pela vafiável coffee.
Go é uma linguagem muito versátil, o que nos possibilita criar vários tipos de programas. Dentre os tipos, um dos que nos ajuda muito no dia a dia são os do tipo Command Line Interface ou só CLI, como normalmente chamamos.
Hoje vamos ver como utilizar um package open source chamado Cobra para nos ajudar nessa tarefa. Vamos utiliza-lo por ele ser usado por grandes implementações de CLI como por exemplo Kubernetes, Hugo e GitHub CLI.
Primeira coisa que vamos fazer é instalar o package.
$ go get -u github.com/spf13/cobra/cobra
Agora vamos criar um arquivo hello.go, onde vamos criar um comando para retornar um “Olá + parâmetro passado”.
Além de ler todos os tipos de arquivo mencionado acima, ele também consegue ler variáveis de ambiente, etcd, Consul, flags de linhas de comando e buffers.
Sem mais delongas, vamos iniciar nosso tutorial instalando essa dependência em nosso projeto.
$ go get github.com/spf13/viper
Agora que já temos a dependência em nossa máquina, vamos criar um arquivo JSON com nome configs.json para armazenar nossas configurações.
Feito isso, vamos criar um arquivo main.go para carregar as configurações. Como queremos carregar as configurações assim que o programa iniciar, vamos adicionar uma função init com as funções do Viper dentro dela.
func init() {
viper.SetConfigName("configs") // nome do arquivo que queremos carregar
viper.SetConfigType("json") // extensão do arquivo
viper.AddConfigPath(".") // caminho onde está o arquivo
viper.AddConfigPath("/etc/") // caminho alternativo onde está o arquivo
err := viper.ReadInConfig() // lê o arquivo e carrega seu conteúdo
if err != nil {
panic(fmt.Errorf("Erro fatal no arquivo de configuração: %w \n", err))
}
}
Essa função init é uma função reservado do Go que é chamada durante a inicialização do programa, ou seja, antes da função main.
E por falar em main, vamos criar nossa função main e printar o valor que temos na chave environment.
Para recuperar valores que estão encadeados, tudo que precisamos fazer é chamar todas as chaves usando um . entre elas, como podemos ver no exemplo abaixo, onde vamos printar o host do mysql.
No entanto, ficar acessando item por item de uma configuração de banco de dados ou algo mais complexo pode ser bem chato e repetitivo. Para mitigar esse problema, podemos criar uma struct e carregar toda a configuração direto para ela.
type MysqlCfg struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Pass string `json:"pass"`
}
func main() {
fmt.Println(viper.GetString("environment"))
fmt.Println(viper.GetString("databases.mysql.host"))
b, err := json.Marshal(viper.Get("databases.mysql"))
if err != nil {
panic(err)
}
var mysql MysqlCfg
err = json.Unmarshal(b, &mysql)
if err != nil {
panic(err)
}
fmt.Println(mysql)
}
Simples não?!
Nos dois exemplos, usamos funções do Viper para retornar valores carregados do arquivo de configuração, sendo o primeiro o GetString que retorna o dado como string e o segundo o Get, que retorna o dado como uma interface{}.
Mas é claro que não existem somente essas duas funções, por isso, abaixo vou deixar a lista de todas as funções que o Viper diponibiliza para recuperar valores do arquivo carregado.
Para encerrar, vou mostrar uma função que nos ajuda a deixar valores padrão já configurado na aplicação, para caso a configuração não seja colocada no arquivo.
Vamos adicionar as seguintes linhas no final da nossa função init.