Como utilizar logs estruturados com log/slog

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.

Leia mais »

Como utilizar o framework Echo

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.

package main

import (
    "net/http"
    
    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
    })
    e.Logger.Fatal(e.Start(":1323"))
}
Leia mais »
green tree

Qual a diferença entre reflect.TypeOf e reflect.ValueOf

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.

Leia mais »
man love people woman

Como criar templates para arquivos JSON, YAML e TXT

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.

package main

func main() {
}
Leia mais »
high angle view of people on bicycle

Como resolver race condition com sync/atomic

Algum tempo atrás, publicamos um post aqui no blog explicando como resolver race condition utilizando mutex e channels.

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)
}
Leia mais »
red and yellow hatchback axa crash tests

Como usar testify para escrever testes

Se você ainda não conhece, Testify é um conjunto de ferramentas para nos auxiliar na hora de escrever testes e mocks.

Por ser uma “casquinha” feita em cima dos próprios testes do Go, a utilização desse package não deve causar grandes problemas.

Na data em que escrevo esse post, o Testify é dividido em 4 packages:

Para começar, considerando o código abaixo, vamos ver como o package assert pode nos ajudar.

Leia mais »

Formatando strings para logs e mensagens

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.

Leia mais »

Criando Command Line Interface (CLI) com Cobra

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”.

Leia mais »

Carregando configurações com Viper

Na semana passada, falamos sobre como carregar configurações de um arquivo TOML.

Hoje vamos ver como carregar configurações de arquivos JSON, TOML, YAML, HCL, envfile e Java properties usando uma única dependência, Viper.

Viper é um package utilizado por grande projetos como Hugo, Docker Notary e doctl.

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.

{
  "databases": {
    "mysql": {
      "host": "localhost",
      "port": 3306,
      "user": "admin",
      "pass": "1234abc?"
    },
    "mongodb": {
      "host": "localhost",
      "port": 27017,
      "user": "logs",
      "pass": "log123"
    }
  },
  "environment": "production"
}

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.

func main() {
    fmt.Println(viper.GetString("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.

func main() {
    fmt.Println(viper.GetString("environment"))
    fmt.Println(viper.GetString("databases.mysql.host"))
}

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, &amp;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.

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}

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.

viper.SetDefault("environment", "development")
viper.SetDefault("databases.mysql.host", "localhost")
viper.SetDefault("databases.mongodb.host", "localhost")

Feito isso, remova as entradas do arquivo de configurações e execute o go run main.go.

Como você pode notar, ao executar o go run main.go os dois primeiros prints funcionaram normal, já o print da struct não exibiu o host.

Isso acontece pois só definimos um valor padrão para a chave databases.mysql.host e não para databases.mysql.

Espero que tenham gostado.

Deixem suas dúvidas nos comentários.

Até a próxima


Se inscreva na nossa newsletter

* indicates required