Monitorando Sites com Go: Criando seu Próprio Health Check CLI
Tabela de Conteúdo
Se você precisa verificar se um endpoint interno, uma API ou um site está no ar, você tem duas opções: configurar um Zabbix/Prometheus da vida (o que pode ser um canhão para matar uma mosca) ou rodar um script rápido.
Hoje nós vamos seguir o caminho de criar um script rapido utilizando Go.
Vamos construir um CLI que recebe uma lista de URLs e nos diz o status e o tempo de resposta de cada uma.
Parte 1: O código básico (sequencial)
A ideia é simples: iterar sobre uma lista de argumentos e fazer um GET.
Vamos usar o pacote net/http que já existe na biblioteca padrão (stdlib). Importante: sempre configure um Timeout. O cliente HTTP padrão do Go não tem timeout, o que pode travar sua CLI se o servidor do outro lado estiver zumbi.
Crie um arquivo healthcheck.go:
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func main() {
// Recebemos os argumentos
urls := os.Args[1:]
// Validamos se existem argumentos
if len(urls) == 0 {
fmt.Println("Uso: ./healthcheck <url1> <url2>")
os.Exit(1)
}
// Criamos um cliente HTTP customizado para ter controle do timeout
client := http.Client{
Timeout: 10 * time.Second,
}
fmt.Println("Iniciando verificações...")
// Iteramos sobre os argumentos
for _, url := range urls {
check(client, url)
}
}
// Função que verifica um único URL
func check(client http.Client, url string) {
inicio := time.Now()
resp, err := client.Get(url)
if err != nil {
fmt.Printf("[ERRO] %s está inacessível: %v\n", url, err)
return
}
// A conexão é fechada
defer resp.Body.Close()
// time.Since calcula a diferença automaticamente
duracao := time.Since(inicio)
// Exibbimos o status e o tempo de resposta
fmt.Printf("[%d] %s - %v\n", resp.StatusCode, url, duracao)
}
Rodando
go run healthcheck.go https://google.com https://github.com
Funciona? Sim. É rápido? Depende. Se você tiver 50 sites e cada um demorar 2 segundos, seu script vai levar 100 segundos.
Parte 2: O modo “Turbo” (Concorrência com Goroutines)
Aqui é onde o Go fica interessante. Vamos mudar o código para checar todos os sites ao mesmo tempo.
Em vez de chamar check() direto, vamos usar a palavra-chave go para criar uma Goroutine (uma thread leve gerenciada pelo Go/Runtime). E para saber quando tudo terminou, usaremos um sync.WaitGroup.
package main
import (
"fmt"
"net/http"
"os"
"sync"
"time"
)
func main() {
urls := os.Args[1:]
var wg sync.WaitGroup // Contador de tarefas
client := http.Client{Timeout: 10 * time.Second}
for _, url := range urls {
wg.Add(1) // Avisamos que tem mais uma tarefa
// Lançamos a função em background
go func(u string) {
defer wg.Done() // Avisa quando terminar
check(client, u)
}(url)
}
wg.Wait() // Bloqueia até todas as tarefas terminarem
}
func check(client http.Client, url string) {
inicio := time.Now()
resp, err := client.Get(url)
if err != nil {
fmt.Printf("[ERRO] %s: %s\n", url, err)
return
}
defer resp.Body.Close()
fmt.Printf("[%d] %s - %v\n", resp.StatusCode, url, time.Since(inicio))
}
Agora, se você tiver 50 sites, o tempo total será equivalente ao site mais lento, e não a soma de todos.
Compilando nosso código
Para compilar é simples, basta executarmos o seguinte comando:
go build -o healthcheck healthcheck.go
Agora é só executar:
./healthcheck https://google.com https://github.com https://chcdc.com.br
Aqui temos algumas observações:
- http.Client vs DefaultClient: Nunca use
http.Get()direto em produção para coisas críticas, pois ele não tem timeout. Se o site travar a conexão, sua thread fica presa para sempre. - Defer: O comando
defer resp.Body.Close()garante que a conexão seja finalizada e não vaze memória, mesmo se a função retornar no meio. - Simplicidade: Note que não instalamos nenhuma biblioteca externa (
npm installoupip install). Tudo está nastdlibdo Go.
Simples assim! :)