Implantação e Otimização de Projeto
Lição 28: Implantação e Otimização de Projeto
Analogia do Mundo Real
Imagine que você abriu um restaurante. As receitas estão escritas (o código está pronto), mas para realmente começar a operar, você precisa:
- Localização e reforma — A compilação cruzada permite que a mesma receita funcione em diferentes cidades (rodando em diferentes plataformas)
- Design da cozinha — Builds multi-stage Docker são como levar apenas ferramentas de cozinha para a cozinha, sem móveis extras
- Manual do funcionário — A configuração por variáveis de ambiente é como diferentes filiais usando horários diferentes, mas o formato do manual é o mesmo
- Câmeras de segurança — O gerenciamento de logs registra o processo de preparação de cada prato, rastreável quando surgem problemas
- Relatório de verificação de saúde — O profiling de desempenho é como um checkup regular, encontrando qual parte está lenta
- Procedimento de fechamento — O encerramento gracioso é como não expulsar clientes que ainda estão comendo no horário de fechamento, esperando que terminem antes de fechar
Conceitos Fundamentais
| Conceito | Descrição |
|---|---|
| Compilação cruzada | Compilar executáveis para uma plataforma em outra plataforma |
| Build multi-stage | Build Docker separando ambientes de compilação e runtime, reduzindo tamanho da imagem |
| Variáveis de ambiente | Configurar comportamento do programa sem modificar código |
| Logging estruturado | Usar log/slog para saída de logs em formato parseável por máquina |
| Profiling de desempenho | Usar pprof para identificar gargalos de CPU e memória |
| Encerramento gracioso | Após receber sinal de término, esperar requisições em andamento completarem antes de sair |
Sintaxe Básica e Uso
1. Compilação Cruzada
Go suporta nativamente compilação cruzada, basta definir as variáveis de ambiente GOOS e GOARCH:
# Compilar versão Linux amd64 (executar no Windows/Mac)
GOOS=linux GOARCH=amd64 go build -o myapp-linux .
# Compilar versão Windows (executar no Linux/Mac)
GOOS=windows GOARCH=amd64 go build -o myapp.exe .
# Compilar versão macOS ARM (M1/M2)
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin .
CGO é desabilitado por padrão. Se você depende de bibliotecas C, precisa instalar toolchains de compilação cruzada.
2. Build Multi-Stage Docker
# ---- Etapa de Build ----
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
# ---- Etapa de Runtime ----
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
3. Configuração por Variáveis de Ambiente
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
// Ler variáveis de ambiente com valores padrão
port := getEnv("APP_PORT", "8080")
dbHost := getEnv("DB_HOST", "localhost")
debug := getEnv("DEBUG", "false")
fmt.Printf("Configuração de inicialização: porta=%s, db=%s, debug=%s\n", port, dbHost, debug)
}
// getEnv obtém uma variável de ambiente, retorna valor padrão se não definida
func getEnv(key, defaultVal string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultVal
}
.env com a biblioteca godotenv, mas não commite arquivos .env no controle de versão.
4. Logging Estruturado (log/slog)
O Go 1.21 introduziu a biblioteca padrão log/slog, suportando saída em formato JSON:
package main
import (
"log/slog"
"os"
)
func main() {
// Criar handler em formato JSON
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
// Usar logging estruturado
logger.Info("Serviço iniciado",
"port", 8080,
"env", "produção",
)
logger.Error("Falha na conexão com banco de dados",
"host", "db.example.com",
"error", "conexão recusada",
)
}
slog suporta definir um logger global padrão via slog.SetDefault(), que pode ser usado uniformemente em todo o projeto.
5. Profiling de Desempenho (pprof)
package main
import (
"fmt"
"net/http"
_ "net/http/pprof" // Import registra rotas pprof
"time"
)
func main() {
// Iniciar serviço pprof em uma porta separada
go func() {
fmt.Println("pprof escutando em :6060")
http.ListenAndServe(":6060", nil)
}()
// Sua lógica de negócio principal
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(10 * time.Millisecond)
fmt.Fprintln(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil)
}
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 pode coletar 30 segundos de dados de CPU.
6. Encerramento Gracioso
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second) // Simular requisição longa
fmt.Fprintln(w, "Processamento completo")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Iniciar servidor em uma goroutine
go func() {
fmt.Println("Servidor escutando em :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
fmt.Printf("Servidor saiu anormalmente: %v\n", err)
}
}()
// Esperar sinal de interrupção
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("\nSinal de encerramento recebido, saindo graciosamente...")
// Dar às requisições em andamento até 10 segundos para completar
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Printf("Encerramento forçado: %v\n", err)
} else {
fmt.Println("Servidor encerrado graciosamente")
}
}
server.Shutdown() para de aceitar novas conexões e espera as conexões existentes terminarem de processar antes de retornar.
Exemplo: Script Básico de Compilação Cruzada (Dificuldade ⭐)
Crie um script que compile binários para múltiplas plataformas de uma vez:
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
)
// Lista de plataformas alvo
type Target struct {
OS string
Arch string
}
func main() {
targets := []Target{
{"linux", "amd64"},
{"linux", "arm64"},
{"darwin", "amd64"},
{"darwin", "arm64"},
{"windows", "amd64"},
}
outputDir := "dist"
os.MkdirAll(outputDir, 0755)
for _, t := range targets {
outputName := fmt.Sprintf("myapp-%s-%s", t.OS, t.Arch)
if t.OS == "windows" {
outputName += ".exe"
}
outputPath := filepath.Join(outputDir, outputName)
fmt.Printf("Compilando: %s/%s -> %s\n", t.OS, t.Arch, outputPath)
cmd := exec.Command("go", "build", "-o", outputPath, ".")
cmd.Env = append(os.Environ(),
"GOOS="+t.OS,
"GOARCH="+t.Arch,
"CGO_ENABLED=0",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Falha na compilação %s/%s: %v\n", t.OS, t.Arch, err)
}
}
fmt.Println("Todas as compilações concluídas!")
}
Executar:
go run build.go
Estrutura do diretório de saída:
dist/
├── myapp-darwin-amd64
├── myapp-darwin-arm64
├── myapp-linux-amd64
├── myapp-linux-arm64
└── myapp-windows-amd64.exe
Exemplo: Serviço HTTP com Configuração por Variáveis de Ambiente (Dificuldade ⭐⭐)
Demonstra um esquema completo de configuração por variáveis de ambiente:
package main
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
)
// Config struct de configuração da aplicação
type Config struct {
Port int `json:"port"`
Env string `json:"env"`
LogLevel string `json:"log_level"`
DBHost string `json:"db_host"`
DBPort int `json:"db_port"`
IdleTimeout int `json:"idle_timeout"` // segundos
}
// LoadConfig carrega configuração das variáveis de ambiente
func LoadConfig() Config {
return Config{
Port: getEnvInt("APP_PORT", 8080),
Env: getEnvStr("APP_ENV", "development"),
LogLevel: getEnvStr("LOG_LEVEL", "info"),
DBHost: getEnvStr("DB_HOST", "localhost"),
DBPort: getEnvInt("DB_PORT", 5432),
IdleTimeout: getEnvInt("IDLE_TIMEOUT", 30),
}
}
func getEnvStr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func getEnvInt(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return fallback
}
func (c Config) String() string {
data, _ := json.MarshalIndent(c, "", " ")
return string(data)
}
func main() {
cfg := LoadConfig()
// Configurar nível de log baseado no ambiente
var level slog.Level
switch cfg.LogLevel {
case "debug":
level = slog.LevelDebug
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
}))
slog.SetDefault(logger)
logger.Info("Configuração da aplicação carregada", "config", cfg.String())
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
"env": cfg.Env,
})
})
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
logger.Info("Requisição recebida", "method", r.Method, "path", r.URL.Path)
fmt.Fprintf(w, "Olá do ambiente %s!", cfg.Env)
})
server := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Handler: mux,
IdleTimeout: time.Duration(cfg.IdleTimeout) * time.Second,
}
go func() {
logger.Info("Serviço iniciado", "port", cfg.Port, "env", cfg.Env)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
logger.Error("Erro no serviço", "error", err)
os.Exit(1)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info("Encerrando serviço...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
logger.Info("Serviço encerrado")
}
Iniciar:
# Usar configuração padrão
go run main.go
# Configuração personalizada
APP_PORT=3000 APP_ENV=production LOG_LEVEL=debug go run main.go
Testar requisições:
# Health check
curl http://localhost:8080/health
# Página inicial
curl http://localhost:8080/
Exemplo: Solução Completa de Implantação Docker (Dificuldade ⭐⭐⭐)
Uma solução de implantação completa incluindo Dockerfile, docker-compose e Makefile:
Estrutura do projeto:
myproject/
├── main.go
├── go.mod
├── go.sum
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── .env.example
main.go:
package main
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
"syscall"
"time"
)
type AppConfig struct {
Port string
Env string
LogLevel string
PprofPort string
}
func loadConfig() AppConfig {
return AppConfig{
Port: getEnv("APP_PORT", "8080"),
Env: getEnv("APP_ENV", "production"),
LogLevel: getEnv("LOG_LEVEL", "info"),
PprofPort: getEnv("PPROF_PORT", "6060"),
}
}
func getEnv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
func setupLogger(level string) *slog.Logger {
var lvl slog.Level
switch level {
case "debug":
lvl = slog.LevelDebug
case "warn":
lvl = slog.LevelWarn
case "error":
lvl = slog.LevelError
default:
lvl = slog.LevelInfo
}
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl}))
}
func main() {
cfg := loadConfig()
logger := setupLogger(cfg.LogLevel)
slog.SetDefault(logger)
// Iniciar pprof (apenas em não-produção ou quando explicitamente habilitado)
if cfg.Env != "production" || os.Getenv("ENABLE_PPROF") == "true" {
go func() {
logger.Info("Serviço pprof iniciado", "port", cfg.PprofPort)
http.ListenAndServe(":"+cfg.PprofPort, nil)
}()
}
mux := http.NewServeMux()
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "ok",
"env": cfg.Env,
"goroutine": runtime.NumGoroutine(),
"uptime": time.Since(startTime).String(),
})
})
mux.HandleFunc("GET /api/info", func(w http.ResponseWriter, r *http.Request) {
logger.Info("requisição de info", "remote", r.RemoteAddr)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"app": "myproject",
"version": version,
"go": runtime.Version(),
"env": cfg.Env,
})
})
mux.HandleFunc("POST /api/data", func(w http.ResponseWriter, r *http.Request) {
// Simular processamento de dados
time.Sleep(100 * time.Millisecond)
logger.Info("Processamento de dados completo", "content_length", r.ContentLength)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"result": "created"})
})
server := &http.Server{
Addr: ":" + cfg.Port,
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Lógica de encerramento gracioso
done := make(chan struct{})
go func() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
sig := <-quit
logger.Info("Sinal de encerramento recebido", "signal", sig)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Parar pprof
// Nota: pprof da biblioteca padrão não tem Shutdown, depende da saída do processo
if err := server.Shutdown(ctx); err != nil {
logger.Error("Falha no encerramento do servidor", "error", err)
}
close(done)
}()
logger.Info("Serviço iniciado",
"port", cfg.Port,
"env", cfg.Env,
"version", version,
)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
logger.Error("Servidor saiu anormalmente", "error", err)
os.Exit(1)
}
<-done
logger.Info("Serviço totalmente encerrado")
}
var (
startTime = time.Now()
version = "1.0.0"
)
Dockerfile:
# ========== Etapa de Build ==========
FROM golang:1.22-alpine AS builder
# Instalar git (go mod pode precisar)
RUN apk add --no-cache git
WORKDIR /build
# Copiar arquivos de dependência primeiro, aproveitando camadas de cache do Docker
COPY go.mod go.sum ./
RUN go mod download
# Copiar fonte e compilar
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server .
# ========== Etapa de Runtime ==========
FROM alpine:3.19
# Instalar certificados CA (para requisições HTTPS) e dados de fuso horário
RUN apk --no-cache add ca-certificates tzdata
# Criar usuário não-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copiar binário da etapa de build
COPY --from=builder /app/server .
COPY --from=builder /build/.env.example .env.example
# Executar como usuário não-root
USER appuser
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["./server"]
docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- APP_ENV=production
- LOG_LEVEL=info
- APP_PORT=8080
- ENABLE_PPROF=false
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Makefile:
APP_NAME := myproject
VERSION := 1.0.0
.PHONY: build run clean docker docker-run docker-stop
# Build local
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME) .
# Executar local
run: build
./bin/$(APP_NAME)
# Limpar
clean:
rm -rf bin/
# Build imagem Docker
docker:
docker build -t $(APP_NAME):$(VERSION) .
# Iniciar container Docker
docker-run:
docker-compose up -d
# Parar container Docker
docker-stop:
docker-compose down
# Compilar todas as plataformas
cross-build:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-linux-amd64 .
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-darwin-amd64 .
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP_NAME)-windows-amd64.exe .
.env.example:
APP_ENV=production
APP_PORT=8080
LOG_LEVEL=info
ENABLE_PPROF=false
PPROF_PORT=6060
Fluxo de build e implantação:
# 1. Teste local
make run
# 2. Build imagem Docker
make docker
# 3. Iniciar container
make docker-run
# 4. Testar serviço
curl http://localhost:8080/health
curl http://localhost:8080/api/info
# 5. Ver logs
docker-compose logs -f
# 6. Parar serviço
make docker-stop
Cenário 1: Logging e Monitoramento de Produção
Em um serviço web real, os logs precisam ser coletados em um sistema centralizado de logging:
package main
import (
"context"
"log/slog"
"net/http"
"os"
"time"
)
// RequestLogger middleware: registra informações detalhadas para cada requisição
func RequestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Envolver ResponseWriter para capturar código de status
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
slog.Info("Requisição HTTP",
"method", r.Method,
"path", r.URL.Path,
"status", rw.statusCode,
"duration_ms", time.Since(start).Milliseconds(),
"remote_addr", r.RemoteAddr,
"user_agent", r.UserAgent(),
)
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// RecoveryMiddleware captura panics e os registra
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
slog.Error("Panic no processamento de requisição",
"error", err,
"path", r.URL.Path,
"method", r.Method,
)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
// Produção usa formato JSON para facilitar parsing pelo sistema de coleta de logs
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
mux := http.NewServeMux()
mux.HandleFunc("GET /api/users", func(w http.ResponseWriter, r *http.Request) {
// Simular lógica de negócio
ctx := r.Context()
slog.DebugContext(ctx, "Consultando lista de usuários", "page", 1)
// ...
})
// Cadeia de middleware: Recovery -> Logger -> Router
handler := RecoveryMiddleware(RequestLogger(mux))
server := &http.Server{Addr: ":8080", Handler: handler}
slog.Info("Serviço iniciado", "addr", server.Addr)
server.ListenAndServe()
}
Após executar, cada requisição gera logs estruturados:
{"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"Requisição HTTP","method":"GET","path":"/api/users","status":200,"duration_ms":5,"remote_addr":"127.0.0.1","user_agent":"curl/7.68.0"}
Cenário 2: Usando pprof para Localizar Gargalos de Desempenho
Quando a resposta do serviço fica lenta, use pprof para diagnóstico:
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"runtime"
"strings"
"time"
)
func main() {
// Serviço principal
mux := http.NewServeMux()
// Simular um endpoint com problemas de desempenho
mux.HandleFunc("GET /api/search", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
result := slowSearch(query) // Função intencionalmente lenta
fmt.Fprint(w, result)
})
// pprof registra no http.DefaultServeMux padrão, precisa de porta separada
go func() {
fmt.Println("pprof: http://localhost:6060/debug/pprof/")
http.ListenAndServe(":6060", nil)
}()
fmt.Println("Serviço principal: http://localhost:8080")
http.ListenAndServe(":8080", mux)
}
// slowSearch simula uma função intensiva de CPU com problemas de alocação de memória
func slowSearch(query string) string {
var results []string
// Intencionalmente alocar memória repetidamente em um loop
for i := 0; i < 100000; i++ {
data := fmt.Sprintf("item_%d_%s", i, query) // Alocar nova string a cada iteração
if strings.Contains(data, query) {
results = append(results, data)
}
}
// Simular atraso adicional
time.Sleep(50 * time.Millisecond)
return fmt.Sprintf("Encontrados %d resultados", len(results))
}
Usando pprof para análise:
# 1. Instalar ferramenta pprof (se ainda não instalada)
go install github.com/google/pprof@latest
# 2. Coletar 30 segundos de perfil de CPU
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 3. Ver na interface interativa do pprof
(pprof) top # Ver funções mais intensivas de CPU
(pprof) list slowSearch # Ver custo de linhas específicas de código
(pprof) web # Gerar visualização (requer graphviz)
# 4. Ver alocação de memória
go tool pprof http://localhost:6060/debug/pprof/heap
# 5. Ver contagem de goroutines (detectar vazamentos)
go tool pprof http://localhost:6060/debug/pprof/goroutine
# 6. Usar navegador para ver todas as métricas
# Abrir http://localhost:6060/debug/pprof/
❓ Perguntas Frequentes
P1: O que fazer ao encontrar cgo: exec gcc: exec: "gcc": not found durante compilação cruzada?
Isso acontece porque o código usa CGO. Se você não precisa de dependências de bibliotecas C, defina CGO_ENABLED=0:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp .
Se você realmente precisa de CGO, precisa instalar toolchains de compilação cruzada para a plataforma alvo (como gcc-aarch64-linux-gnu).
P2: Imagem Docker muito grande, como reduzir ainda mais o tamanho?
Use scratch como imagem base (sem ferramentas de sistema):
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
CMD ["/server"]
Combinado com remoção de informações de debug durante a compilação: go build -ldflags="-s -w". -s remove a tabela de símbolos, -w remove informações de debug DWARF, tipicamente reduzindo o tamanho em 20-30%.
P3: Como log/slog se compara a bibliotecas de logging de terceiros (como zap, zerolog)?
A vantagem do log/slog é ser uma biblioteca padrão sem dependências adicionais, e seu desempenho é suficiente para a maioria dos cenários. Se sua aplicação tem volume extremamente alto de logs (dezenas de milhares por segundo), o design de zero-alocação do zap será mais rápido. Para novos projetos, comece com slog; considere trocar apenas se encontrar gargalos de desempenho.
P4: Durante o encerramento gracioso, como garantir que conexões WebSocket sejam tratadas corretamente?
http.Server.Shutdown() apenas lida com requisições HTTP. Para conexões de longa duração (WebSocket), você precisa gerenciar o pool de conexões você mesmo:
var connections sync.Map // Armazenar conexões ativas
// Registrar quando nova conexão chega
connections.Store(conn, true)
// No encerramento, iterar todas as conexões e enviar frames de fechamento
connections.Range(func(key, value any) bool {
wsConn := key.(*websocket.Conn)
wsConn.WriteMessage(websocket.CloseMessage, closeMsg)
wsConn.Close()
return true
})
📖 Resumo
Nesta lição aprendemos o fluxo completo de desenvolvimento à implantação para projetos Go:
- Compilação cruzada permite compilar binários para plataforma alvo em qualquer plataforma. Go suporta nativamente — basta definir
GOOSeGOARCH - Builds multi-stage Docker separam o ambiente de compilação do ambiente de runtime, reduzindo significativamente o tamanho da imagem e melhorando a segurança
- Configuração por variáveis de ambiente é uma melhor prática do 12-Factor App, permitindo que o mesmo código se adapte a diferentes ambientes
log/slogfornece logging estruturado a nível de biblioteca padrão, com formato JSON para facilitar parsing pelo sistema de coleta de logspprofé a ferramenta embutida de análise de desempenho do Go, capaz de localizar problemas de CPU, memória, goroutines e outros- Encerramento gracioso garante que após receber sinal de término, o serviço espere requisições em andamento completarem antes de sair, evitando perda de dados
Após dominar essas habilidades, você pode implantar projetos Go em ambientes de produção de forma segura e eficiente.
📝 Exercícios
Exercício 1: Escrever um Script de Compilação Cruzada
Escreva um programa Go que compile automaticamente o projeto atual para plataformas linux/amd64, darwin/arm64, windows/amd64, com nomes de arquivo de saída contendo informações de versão e plataforma.
Exercício 2: Adicionar Middleware de Logging de Requisição
Adicione middleware de logging a um serviço HTTP que registre para cada requisição: método, caminho, código de status, tempo de resposta, IP do cliente. Use log/slog para saída em formato JSON.
Exercício 3: Dockerize Seu Projeto
Escreva um Dockerfile e docker-compose.yml completos para qualquer projeto que você escreveu nas lições anteriores (ex: API Todo), com os seguintes requisitos:
- Usar builds multi-stage
- Imagem final baseada em
alpineouscratch - Incluir health checks
- Executar como usuário não-root
Próxima Lição
Após completar esta lição, você dominou as habilidades principais para implantação de projetos Go. Em seguida entraremos na fase de projeto prático: LiÇÃO 29: Projeto Prático (Parte 1)



