Valores de context: Quando e Quando Não
context.WithValue carrega metadados de escopo de requisição por pilhas de chamadas sem precisar passar dez parâmetros de string por cada função.
Busque em todas as páginas da documentação
context.WithValue carrega metadados de escopo de requisição por pilhas de chamadas sem precisar passar dez parâmetros de string por cada função.
Usado corretamente, os valores contêm IDs de rastreamento e principais de autenticação; usado incorretamente, eles se tornam um mapa global para dados de negócio opcionais.
Armazene metadados de infraestrutura transversais em context com chaves tipadas não exportadas.
Passe entradas de negócio como parâmetros explícitos de função.
Nunca coloque segredos em valores de context sem entender que eles podem vazar através de logs e introspecção.
Cartão de receita de referência rápida - pronto para copiar e colar.
type ctxKey int
const (
requestIDKey ctxKey = iota
userKey
)
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func RequestID(ctx context.Context) string {
v, _ := ctx.Value(requestIDKey).(string)
return v
}Quando usar isso:
package main
import (
"context"
"fmt"
"log/slog"
)
type ctxKey string
const (
requestIDKey ctxKey = "requestID"
userKey ctxKey = "user"
)
type User struct {
ID string
Role string
}
func withMeta(ctx context.Context, reqID string, u User) context.Context {
ctx = context.WithValue(ctx, requestIDKey, reqID)
return context.WithValue(ctx, userKey, u)
}
func logAction(ctx context.Context, action string) {
attrs := []any{slog.String("action", action)}
if id, ok := ctx.Value(requestIDKey).(string); ok && id != "" {
attrs = append(attrs, slog.String("request_id", id))
}
if u, ok := ctx.Value(userKey).(User); ok && u.ID != "" {
attrs = append(attrs, slog.String("user_id", u.ID))
}
slog.Info("audit", attrs...)
}
func serviceCall(ctx context.Context) {
logAction(ctx, "billing.sync")
}
func main() {
ctx := withMeta(context.Background(), "req-7f3a", User{ID: "u-42", Role: "admin"})
serviceCall(ctx)
fmt.Println("done")
}O que isso demonstra:
Value percorre ponteiros pais até que uma chave corresponda.WithValue sombreiam chaves pais com o mesmo tipo e valor.WithValue.| Aceitável | Inaceitável |
|---|---|
| ID de requisição / rastreamento | Handles de banco de dados |
| Snapshot do principal autenticado | Payloads de requisição grandes |
| Locale ou slug de tenant | Filtros de consulta opcionais |
| Handle de logger ou tracer (com cuidado) | Structs de configuração |
| Flags de política de deadline (raro) | Segredos em texto plano |
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u := UserFromJWT(r.Header.Get("Authorization"))
ctx := context.WithValue(r.Context(), userKey, u)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Prefira tipos de valor pequenos copiados por valor ou ponteiros imutáveis.
// Package auth é dono de suas chaves - outros pacotes não podem colidir
package auth
type key struct{}
var userKey = key{}
func User(ctx context.Context) (User, bool) {
u, ok := ctx.Value(userKey).(User)
return u, ok
}"user". Solução: chaves tipadas não exportadas por pacote.*sql.DB via construtores, não context.WithValue(ctx, key, nil) ainda armazena uma entrada. Solução: omita a chave quando ausente ou use tipos ponteiro.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Parâmetro de struct explícito | API clara com poucos campos | Metadados profundos apenas de middleware |
context.Value | IDs transversais entre pacotes | Entradas de lógica de negócio |
| Estilo thread-local (não em Go) | N/A em Go | - |
| Baggage do OpenTelemetry | Propagação de rastreamento padrão | Monólito simples sem rastreamento |
| Cabeçalhos HTTP relidos na folha | Microsserviços sem estado | Caminhos quentes chamando os mesmos metadados repetidamente |
Se precisar de mais do que um punhado, reconsidere uma struct de requisição explícita.
Valores de context devem permanecer pequenos e estáveis.
Algumas equipes fazem isso; outras injetam loggers via construtores.
Se armazenado, use uma interface restrita e evite estado mutável.
Sim - use construtores auxiliares em _test.go para anexar metadados de teste.
Mantém as chaves de produção não exportadas.
Eles não são criptografados ou controlados por acesso.
Não trate context como um cofre de segredos.
Metadados gRPC são de nível de rede; valores de context são em processo.
Interceptors frequentemente copiam metadados para context para handlers.
Não automaticamente - serialize campos escolhidos em cabeçalhos ou metadados explicitamente.
Recrie valores no middleware receptor.
Cancelamento e deadlines são recursos de primeiro nível do context.
Valores são auxiliares - não os use para sinalizar parada.
Funções auxiliares tipadas (User(ctx)) são idiomáticas.
Genéricos raramente simplificam além de funções simples.
Não - contexts filhos herdam o cancelamento do pai.
Valores adicionam uma camada sem alterar o comportamento de Done.
golangci-lint e checklists de revisão sinalizam chaves de string e padrões de context-como-configuração.
Aplique tipos de chave de propriedade do pacote em guias de estilo.
Versões de Stack: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026