Algum tempo atrás fizemos um post falando sobre como escrever logs em arquivos. No post em questão, logamos algumas mensagens de forma bem simples. Normalmente esses logs em texto puro são um pouco complicados para filtrar mensagens, já que é preciso muito regex.
Nesse post vamos abordar os logs de uma forma diferente, pois vamos escreve-los em formato JSON.
Para nos auxiliar nessa tarefa, vamos utilizar um package chamado zerolog. Esse package foi criado pelo Olivier Poitrey, atual diretor de engenharia do Netflix.
Gosto muito desse package pois ele é fácil de usar e tem uma performance muito boa.
Sem mais enrolação, vamos instalar o package e começar a brincar com ele.
Sem dúvidas um dos routers mais famosos e utilizados no mundo Golang, hoje vamos ver como implementar uma API utilizando gorilla/mux.
Para quem não conhecer, gorilla/mux ajuda na hora de fazer o match da URL que está sendo chamada com a função que vai tratar aquela URL. Além dessa facilidade, um dos principais benefícios de se utilizar gorilla/mux é que ele implementa a interface http.Handler nativa do Go.
Sem mais delongas, vamos começar implementar nossa API instalando o gorilla/mux.
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.
Muitos programas que escrevemos precisam de informações para configurar uma conexão com banco de dados, enviar informações para uma API de terceiros e etc…
Existem várias formas para nosso programa receber essas informações, dentre elas, a pior sendo colocar as informações diretamente no código, pois, além de ficar difícil para manter as várias configurações por ambiente, existe o fator segurança, já que os dados ficarão expostos no seu Github, Gitlab ou qualquer outro versionador de código que você utilizar.
Outra forma para fazer isso seria utilizando variáveis de ambiente, mas caso você não esteja usando Kubernetes, essa maneira pode ser muito trabalhosa para se manter.
Por isso, nesse post vamos ver como podemos manter essas configurações fora do nosso código usando arquivos toml.
Nesse post vou falar um pouco sobre um dos projetos open source que contribuo, o pREST.
O pREST cria uma api REST expondo as tabelas de um banco postgres de forma rápida, fácil e com uma boa performance.
Embora essa prática pareça um pouco estranha e insegura, para alguns casos ela serve muito bem. Um exemplo seria um serviço interno para sua intranet ou como um micro serviço de CRUD.
Só para deixar bem claro, quando digo que parece insegura, não é que o pREST não trate questões como SQL Injection, mas sim o fato de expor todo um banco e suas tabelas de forma nua e crua.