Indice
Clue fournit une instrumentation complète pour les microservices Go construits sur OpenTelemetry. Bien que conçu pour s’intégrer de manière transparente avec Goa, Clue fonctionne avec n’importe quel service Go HTTP ou gRPC.
Pourquoi Clue ?
Clue résout un problème courant dans les microservices : vous avez besoin de journaux détaillés lorsque les choses vont mal, mais vous ne voulez pas payer le coût de tout enregistrer en permanence.
L’approche de Clue : mettre en mémoire tampon les messages de logs et ne les écrire que lorsqu’une erreur se produit ou que la requête est en cours de traçage. Les requêtes réussies et non tracées ne génèrent aucune sortie de journal. Lorsque des erreurs se produisent, vous obtenez le contexte complet de ce qui a conduit à l’échec.
Cette simple décision de conception réduit considérablement le volume des journaux tout en préservant les informations de débogage dont vous avez besoin.
Aperçu du paquet
| Package | Objectif |
|---|---|
clue | OpenTelemetry configuration - un seul appel pour configurer les métriques et le traçage |
log | Journalisation structurée basée sur le contexte avec mise en mémoire tampon intelligente |
health Points d’extrémité de contrôle de santé pour Kubernetes et les systèmes d’orchestration | health debug Les points d’extrémité de contrôle de santé |
debug | Débogage en cours d’exécution - basculement des journaux de débogage, points d’extrémité pprof |
mock | Générer et configurer des doubles de test pour les dépendances |
interceptors | Intercepteurs Goa pour le traçage des messages de flux individuels |
Installation
N’installez que les paquets dont vous avez besoin :
go get goa.design/clue/clue
go get goa.design/clue/log
go get goa.design/clue/health
go get goa.design/clue/debug
go get goa.design/clue/mock
go get goa.design/clue/interceptors
Le paquet de logs
Le paquet log est construit autour du paquet context.Context de Go. Vous initialisez un contexte de journalisation une seule fois et vous le transmettez à votre application. Toutes les fonctions de journalisation prennent ce contexte comme premier argument.
Démarrage rapide
import "goa.design/clue/log"
func main() {
// Initialize the logging context
ctx := log.Context(context.Background())
// Log a message
log.Printf(ctx, "server starting on port %d", 8080)
// Log structured key-value pairs
log.Print(ctx, log.KV{K: "event", V: "startup"}, log.KV{K: "port", V: 8080})
}
Comprendre la mise en mémoire tampon
C’est la fonction clé de Clue. Il existe deux types de fonctions logarithmiques :
Fonctions immédiates - écrivent directement sur la sortie :
Print(),Printf()- écrivent toujours immédiatementError(),Errorf()- effacer le tampon, puis écrireFatal(),Fatalf()- rincer la mémoire tampon, écrire, puis quitter
Fonctions tamponnées - stockage en mémoire jusqu’à ce que la mémoire soit vidée :
Info(),Infof()- mise en mémoire tampon du messageWarn(),Warnf()- mise en mémoire tampon du messageDebug(),Debugf()- mise en mémoire tampon si le débogage est activé
La mémoire tampon se vide automatiquement lorsque
Error()ouFatal()est appelé- La requête est tracée (détectée via le contexte OpenTelemetry span)
- Le mode débogage est activé
Exemple : Pourquoi c’est important
func HandleRequest(ctx context.Context, req *Request) error {
log.Infof(ctx, "received request for user %s", req.UserID) // buffered
user, err := db.GetUser(ctx, req.UserID)
if err != nil {
// Error flushes the buffer - you see BOTH log lines
log.Errorf(ctx, err, "failed to get user")
return err
}
log.Infof(ctx, "user found: %s", user.Name) // buffered
// Request succeeds - no logs written (buffer discarded)
return nil
}
Pour une demande réussie : zéro sortie de journal. Pour une requête échouée : contexte complet.
Ajout de contexte avec With()
Construisez un contexte de journalisation au fur et à mesure que les requêtes transitent par votre service :
func HandleOrder(ctx context.Context, orderID string) error {
// Add order ID to all subsequent logs
ctx = log.With(ctx, log.KV{K: "order_id", V: orderID})
log.Info(ctx, log.KV{K: "msg", V: "processing order"})
// Output includes: order_id=abc123 msg="processing order"
return processPayment(ctx)
}
func processPayment(ctx context.Context) error {
// order_id is already in context
log.Info(ctx, log.KV{K: "msg", V: "charging card"})
// Output includes: order_id=abc123 msg="charging card"
return nil
}
Paires clé-valeur
Il existe deux façons de spécifier des paires clé-valeur :
// KV - deterministic order, slice-backed
log.Print(ctx,
log.KV{K: "user", V: "alice"},
log.KV{K: "action", V: "login"},
log.KV{K: "ip", V: "192.168.1.1"},
)
// Fields - map-backed, order not guaranteed
log.Print(ctx, log.Fields{
"user": "alice",
"action": "login",
"ip": "192.168.1.1",
})
Utilisez KV lorsque l’ordre des champs du journal est important (plus facile à analyser). Utilisez Fields lorsque ce n’est pas le cas.
Les valeurs peuvent être des chaînes, des nombres, des booléens, nil ou des tranches de ces types.
Formats des journaux
Clue détecte automatiquement les terminaux et sélectionne le format approprié :
// Explicit format selection
ctx := log.Context(context.Background(), log.WithFormat(log.FormatJSON))
FormatText (par défaut pour les non-terminaux) - style logfmt :
time=2024-01-15T10:30:00Z level=info user=alice action=login
FormatTerminal (par défaut pour les terminaux) - horodatage coloré et relatif :
INFO[0042] user=alice action=login
FormatJSON - JSON structuré :
{"time":"2024-01-15T10:30:00Z","level":"info","user":"alice","action":"login"}
Custom format:
func myFormat(e *log.Entry) []byte {
return []byte(fmt.Sprintf("[%s] %v\n", e.Severity, e.KeyVals))
}
ctx := log.Context(context.Background(), log.WithFormat(myFormat))
Ajout d’identifiants de trace et de portée
Connecter les journaux aux traces distribuées :
ctx := log.Context(context.Background(),
log.WithFormat(log.FormatJSON),
log.WithFunc(log.Span), // Adds trace_id and span_id to every log
)
Sortie :
{"time":"...","level":"info","trace_id":"abc123","span_id":"def456","msg":"hello"}
Ajout de l’emplacement du fichier
Pour le débogage, ajoutez le fichier source et les numéros de ligne :
ctx := log.Context(context.Background(), log.WithFileLocation())
La sortie comprend : file=mypackage/handler.go:42
Middleware HTTP
L’intergiciel HTTP a deux fonctions :
- Copier l’enregistreur de votre contexte de base dans le contexte de chaque requête
- Il enregistre le début et la fin de la requête avec la méthode, l’URL, le statut et la durée
func main() {
ctx := log.Context(context.Background())
handler := http.HandlerFunc(myHandler)
handler = log.HTTP(ctx)(handler) // Note: returns middleware, then apply
http.ListenAndServe(":8080", handler)
}
Options:
// Skip logging for certain paths (e.g., health checks)
handler = log.HTTP(ctx, log.WithPathFilter(regexp.MustCompile(`^/healthz$`)))(handler)
// Disable request logging entirely (still sets up context)
handler = log.HTTP(ctx, log.WithDisableRequestLogging())(handler)
// Disable request ID generation
handler = log.HTTP(ctx, log.WithDisableRequestID())(handler)
Intercepteurs gRPC
Pour les serveurs gRPC :
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(log.UnaryServerInterceptor(ctx)),
grpc.ChainStreamInterceptor(log.StreamServerInterceptor(ctx)),
)
Pour les clients gRPC :
conn, err := grpc.Dial(addr,
grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(log.StreamClientInterceptor()),
)
Enregistrement des clients HTTP
Enveloppez les transports HTTP pour enregistrer les demandes sortantes :
client := &http.Client{
Transport: log.Client(http.DefaultTransport),
}
// With OpenTelemetry tracing
client := &http.Client{
Transport: log.Client(
otelhttp.NewTransport(http.DefaultTransport),
),
}
Intégration de Goa
Ajouter les noms des services et des méthodes aux journaux :
endpoints := genservice.NewEndpoints(svc)
endpoints.Use(log.Endpoint) // Adds goa.service and goa.method to context
Personnalisation des clés de journalisation
Toutes les clés de journalisation sont des variables de paquetage que vous pouvez remplacer :
log.MessageKey = "message" // default: "msg"
log.ErrorMessageKey = "error" // default: "err"
log.TimestampKey = "timestamp" // default: "time"
log.SeverityKey = "severity" // default: "level"
log.TraceIDKey = "traceId" // default: "trace_id"
log.SpanIDKey = "spanId" // default: "span_id"
Adaptateur pour d’autres enregistreurs
// Standard library log.Logger compatible
stdLogger := log.AsStdLogger(ctx)
// AWS SDK logger
awsLogger := log.AsAWSLogger(ctx)
// logr.LogSink (for Kubernetes controllers, etc.)
sink := log.ToLogrSink(ctx)
// Goa middleware logger
goaLogger := log.AsGoaMiddlewareLogger(ctx)
Le paquet d’indices
Le paquet clue configure OpenTelemetry avec des valeurs par défaut raisonnables en un seul appel de fonction.
Configuration de base
import (
"goa.design/clue/clue"
"goa.design/clue/log"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
)
func main() {
ctx := log.Context(context.Background())
// Create exporters
spanExporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure())
if err != nil {
log.Fatal(ctx, err)
}
metricExporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint("localhost:4317"),
otlpmetricgrpc.WithInsecure())
if err != nil {
log.Fatal(ctx, err)
}
// Configure OpenTelemetry
cfg, err := clue.NewConfig(ctx, "myservice", "1.0.0", metricExporter, spanExporter)
if err != nil {
log.Fatal(ctx, err)
}
clue.ConfigureOpenTelemetry(ctx, cfg)
}
Échantillonnage adaptatif
Clue comprend un échantillonneur adaptatif qui ajuste automatiquement le taux d’échantillonnage en fonction du volume de trafic. Cela permet d’éviter que le stockage des traces ne soit saturé lors des pics de trafic.
Paramètres par défaut :
- Taux d’échantillonnage maximum: 2 traces par seconde
- Taille de l’échantillon: 10 requêtes entre les ajustements
cfg, err := clue.NewConfig(ctx, "myservice", "1.0.0", metricExporter, spanExporter,
clue.WithMaxSamplingRate(100), // Up to 100 traces/second
clue.WithSampleSize(50), // Adjust rate every 50 requests
)
Fonctions d’aide à l’exportation
Clue fournit des fonctions d’aide qui créent des exportateurs avec une gestion appropriée des arrêts :
// gRPC exporters
metricExporter, shutdown, err := clue.NewGRPCMetricExporter(ctx,
otlpmetricgrpc.WithEndpoint("localhost:4317"))
defer shutdown()
spanExporter, shutdown, err := clue.NewGRPCSpanExporter(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"))
defer shutdown()
// HTTP exporters
metricExporter, shutdown, err := clue.NewHTTPMetricExporter(ctx,
otlpmetrichttp.WithEndpoint("localhost:4318"))
defer shutdown()
spanExporter, shutdown, err := clue.NewHTTPSpanExporter(ctx,
otlptracehttp.WithEndpoint("localhost:4318"))
defer shutdown()
Options de configuration
cfg, err := clue.NewConfig(ctx, "myservice", "1.0.0", metricExporter, spanExporter,
clue.WithMaxSamplingRate(100),
clue.WithSampleSize(50),
clue.WithReaderInterval(30 * time.Second), // Metric export interval
clue.WithPropagators(propagation.TraceContext{}), // Custom propagators
clue.WithResource(resource.NewWithAttributes(...)), // Additional resource attributes
clue.WithErrorHandler(myErrorHandler),
)
Désactivation des mesures ou du traçage
Passez nil pour l’exportateur dont vous n’avez pas besoin :
// Tracing only, no metrics
cfg, err := clue.NewConfig(ctx, "myservice", "1.0.0", nil, spanExporter)
// Metrics only, no tracing
cfg, err := clue.NewConfig(ctx, "myservice", "1.0.0", metricExporter, nil)
Le paquet santé
Le paquet health crée des points de contrôle de santé qui rendent compte des dépendances des services.
Utilisation de base
import "goa.design/clue/health"
func main() {
checker := health.NewChecker()
mux := http.NewServeMux()
mux.Handle("/healthz", health.Handler(checker))
mux.Handle("/livez", health.Handler(checker))
}
Vérification des dépendances
Utilisez NewPinger pour vérifier les services qui exposent des points de terminaison de santé :
checker := health.NewChecker(
health.NewPinger("database-service", "db.internal:8080"),
health.NewPinger("cache-service", "cache.internal:8080"),
health.NewPinger("auth-api", "auth.example.com:443", health.WithScheme("https")),
)
Pinger options:
health.NewPinger("service", "host:port",
health.WithScheme("https"), // Default: "http"
health.WithPath("/health"), // Default: "/livez"
health.WithTimeout(5 * time.Second), // Default: no timeout
health.WithTransport(customTransport),
)
Contrôles de santé personnalisés
Mettre en œuvre l’interface Pinger pour les contrôles personnalisés :
type DBChecker struct {
db *sql.DB
}
func (c *DBChecker) Name() string {
return "postgresql"
}
func (c *DBChecker) Ping(ctx context.Context) error {
return c.db.PingContext(ctx)
}
// Usage
checker := health.NewChecker(&DBChecker{db: db})
Format de la réponse
Le gestionnaire renvoie JSON par défaut, XML si demandé :
Santé (HTTP 200):
{
"uptime": 3600,
"version": "abc123",
"status": {
"postgresql": "OK",
"redis": "OK"
}
}
Malsain (HTTP 503):
{
"uptime": 3600,
"version": "abc123",
"status": {
"postgresql": "OK",
"redis": "NOT OK"
}
}
Définir la version au moment de la construction :
health.Version = "v1.2.3" // Or use ldflags: -X goa.design/clue/health.Version=v1.2.3
Le paquet de débogage
Le paquet debug permet de résoudre les problèmes d’exécution sans redéploiement.
La journalisation dynamique de débogage
Monter un point de terminaison pour basculer les journaux de débogage au moment de l’exécution :
mux := http.NewServeMux()
debug.MountDebugLogEnabler(mux) // Mounts at /debug
Contrôler les journaux de débogage via HTTP :
# Check current state
curl http://localhost:8080/debug
# {"debug-logs":"off"}
# Enable debug logging
curl "http://localhost:8080/debug?debug-logs=on"
# {"debug-logs":"on"}
# Disable debug logging
curl "http://localhost:8080/debug?debug-logs=off"
# {"debug-logs":"off"}
Important: Le point de terminaison ne contrôle qu’un drapeau. Vous devez utiliser l’intergiciel de débogage pour qu’il prenne effet :
// For HTTP servers
handler = debug.HTTP()(handler)
// For gRPC servers
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(debug.UnaryServerInterceptor()),
grpc.ChainStreamInterceptor(debug.StreamServerInterceptor()),
)
Options:
debug.MountDebugLogEnabler(mux,
debug.WithPath("/api/debug"), // Default: "/debug"
debug.WithQuery("logging"), // Default: "debug-logs"
debug.WithOnValue("enable"), // Default: "on"
debug.WithOffValue("disable"), // Default: "off"
)
pprof Endpoints
Monter les points d’extrémité de profilage de Go :
debug.MountPprofHandlers(mux) // Mounts at /debug/pprof/
Points d’accès disponibles :
/debug/pprof/- Page d’index/debug/pprof/heap- Profil du tas/debug/pprof/goroutine- Profil Goroutine/debug/pprof/profile- Profil CPU (30s par défaut)/debug/pprof/trace- Trace d’exécution/debug/pprof/allocs,/debug/pprof/block,/debug/pprof/mutex, etc.
⚠️ Avertissement de sécurité: N’exposez pas les points de terminaison pprof publiquement. Ils révèlent des informations sensibles sur votre application.
debug.MountPprofHandlers(mux, debug.WithPrefix("/internal/pprof/"))
Journalisation des charges utiles pour Goa
Enregistre les données utiles des requêtes et des réponses lorsque le débogage est activé :
endpoints := genservice.NewEndpoints(svc)
endpoints.Use(debug.LogPayloads()) // Only logs when debug enabled
endpoints.Use(log.Endpoint)
Options:
debug.LogPayloads(
debug.WithMaxSize(2048), // Max bytes to log, default: 1024
debug.WithFormat(debug.FormatJSON), // Custom formatter
debug.WithClient(), // Prefix keys with "client-" for client-side logging
)
Adaptateur Muxer Goa
Pour le muxer HTTP de Goa :
mux := goahttp.NewMuxer()
debug.MountDebugLogEnabler(debug.Adapt(mux))
debug.MountPprofHandlers(debug.Adapt(mux))
Le paquet fictif
Le paquet mock permet de créer des doubles de test pour les dépendances avec un support pour les séquences d’appel et les mocks permanents.
Concepts
Séquences: Définir les appels attendus dans l’ordre. Chaque appel à Next() renvoie la fonction suivante dans la séquence.
Mocks permanents: renvoient toujours la même fonction, utilisés lorsque les séquences sont épuisées ou lorsque l’ordre n’a pas d’importance.
Création d’un Mock
type MockUserService struct {
*mock.Mock
t *testing.T
}
func NewMockUserService(t *testing.T) *MockUserService {
return &MockUserService{mock.New(), t}
}
func (m *MockUserService) GetUser(ctx context.Context, id string) (*User, error) {
if f := m.Next("GetUser"); f != nil {
return f.(func(context.Context, string) (*User, error))(ctx, id)
}
m.t.Error("unexpected GetUser call")
return nil, errors.New("unexpected call")
}
func (m *MockUserService) AddGetUser(f func(context.Context, string) (*User, error)) {
m.Add("GetUser", f)
}
func (m *MockUserService) SetGetUser(f func(context.Context, string) (*User, error)) {
m.Set("GetUser", f)
}
Utilisation des Mocks dans les tests
func TestOrderService(t *testing.T) {
userMock := NewMockUserService(t)
// Add sequence: first call returns user, second returns error
userMock.AddGetUser(func(ctx context.Context, id string) (*User, error) {
return &User{ID: id, Name: "Alice"}, nil
})
userMock.AddGetUser(func(ctx context.Context, id string) (*User, error) {
return nil, errors.New("not found")
})
svc := NewOrderService(userMock)
// First call succeeds
_, err := svc.CreateOrder(ctx, "user1", items)
require.NoError(t, err)
// Second call fails
_, err = svc.CreateOrder(ctx, "user2", items)
require.Error(t, err)
// Verify all expected calls were made
if userMock.HasMore() {
t.Error("not all expected calls were made")
}
}
Mocks permanents
Utilisez Set() pour les appels qui doivent toujours se comporter de la même manière :
userMock.SetGetUser(func(ctx context.Context, id string) (*User, error) {
return &User{ID: id, Name: "Test User"}, nil
})
Les séquences ont la priorité sur les objets fantaisie permanents. Une fois la séquence épuisée, Next() renvoie l’image permanente.
Générateur de mock (cmg)
Génère automatiquement des mocks à partir d’interfaces :
go install goa.design/clue/mock/cmd/cmg@latest
# Generate mocks for all interfaces in a package
cmg gen ./services/...
# With testify assertions
cmg gen --testify ./services/...
Les mocks générés sont placés dans un sous-répertoire mocks/ à côté du fichier source.
Le paquet d’intercepteurs
Le paquet interceptors fournit des intercepteurs Goa pour tracer les messages individuels dans les RPCs en streaming. Contrairement à l’instrumentation standard d’OpenTelemetry (qui trace le flux entier), ces intercepteurs propagent le contexte de trace à travers chaque message.
Quand utiliser
Utilisez ces intercepteurs lorsque vous en avez besoin :
- Traçage par message dans des flux de longue durée
- Tracer le contexte du flux entre le client et le serveur par le biais de messages de flux
- Corrélation et synchronisation des messages individuels
Configuration de la conception
Dans votre conception Goa, définissez des intercepteurs avec des attributs TraceMetadata :
var TraceBidirectionalStream = Interceptor("TraceBidirectionalStream", func() {
WriteStreamingPayload(func() {
Attribute("TraceMetadata", MapOf(String, String))
})
ReadStreamingPayload(func() {
Attribute("TraceMetadata", MapOf(String, String))
})
WriteStreamingResult(func() {
Attribute("TraceMetadata", MapOf(String, String))
})
ReadStreamingResult(func() {
Attribute("TraceMetadata", MapOf(String, String))
})
})
Appliquer aux méthodes de streaming :
Method("Chat", func() {
StreamingPayload(ChatMessage)
StreamingResult(ChatResponse)
ClientInterceptor(TraceBidirectionalStream)
ServerInterceptor(TraceBidirectionalStream)
})
Mise en œuvre
Dans vos implémentations d’intercepteurs, appelez les fonctions fournies :
import "goa.design/clue/interceptors"
// Client-side
func (i *ClientInterceptors) TraceBidirectionalStream(
ctx context.Context,
info *genservice.TraceBidirectionalStreamInfo,
next goa.Endpoint,
) (any, error) {
return interceptors.TraceBidirectionalStreamClient(ctx, info, next)
}
// Server-side
func (i *ServerInterceptors) TraceBidirectionalStream(
ctx context.Context,
info *genservice.TraceBidirectionalStreamInfo,
next goa.Endpoint,
) (any, error) {
return interceptors.TraceBidirectionalStreamServer(ctx, info, next)
}
Extraction du contexte de la trace à partir des messages reçus
Puisque les interfaces de flux générées par Goa ne renvoient pas de contexte, utilisez les fonctions d’aide :
func (s *Service) Chat(ctx context.Context, stream genservice.ChatServerStream) error {
for {
ctx = interceptors.SetupTraceStreamRecvContext(ctx)
msg, err := stream.RecvWithContext(ctx)
if err != nil {
return err
}
ctx = interceptors.GetTraceStreamRecvContext(ctx)
// ctx now contains trace context from the received message
log.Info(ctx, log.KV{K: "received", V: msg.Text})
}
}
Ou utilisez le wrapper pour un code plus propre :
wrapped := interceptors.WrapTraceBidirectionalStreamServerStream(stream)
for {
ctx, msg, err := wrapped.RecvAndReturnContext(ctx)
if err != nil {
return err
}
// ctx contains trace context
}
Exemple complet
Un service Goa entièrement instrumenté :
package main
import (
"context"
"net/http"
"goa.design/clue/clue"
"goa.design/clue/debug"
"goa.design/clue/health"
"goa.design/clue/log"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
genservice "myapp/gen/myservice"
)
func main() {
// 1. Initialize logging context with trace correlation
ctx := log.Context(context.Background(),
log.WithFormat(log.FormatJSON),
log.WithFunc(log.Span))
// 2. Configure OpenTelemetry
spanExporter, _ := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure())
metricExporter, _ := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithInsecure())
cfg, _ := clue.NewConfig(ctx, "myservice", "1.0.0", metricExporter, spanExporter)
clue.ConfigureOpenTelemetry(ctx, cfg)
// 3. Create service and endpoints
svc := NewService()
endpoints := genservice.NewEndpoints(svc)
endpoints.Use(debug.LogPayloads()) // Log payloads when debug enabled
endpoints.Use(log.Endpoint) // Add service/method to logs
// 4. Create HTTP handler with middleware stack
handler := genservice.NewHandler(endpoints)
handler = otelhttp.NewHandler(handler, "myservice") // OpenTelemetry
handler = debug.HTTP()(handler) // Debug log control
handler = log.HTTP(ctx)(handler) // Request logging
// 5. Mount on mux
mux := http.NewServeMux()
mux.Handle("/", handler)
// 6. Mount operational endpoints
debug.MountDebugLogEnabler(mux)
debug.MountPprofHandlers(mux)
mux.Handle("/healthz", health.Handler(
health.NewChecker(
health.NewPinger("database", dbAddr),
),
))
// 7. Start server
log.Printf(ctx, "starting server on :8080")
http.ListenAndServe(":8080", mux)
}
Meilleures pratiques
Journalisation
**Utilisez
Info()pour le traitement des demandes,Print()pour les événements du cycle de vie ** Les journaux des demandes doivent être mis en mémoire tampon ; les journaux de démarrage/arrêt doivent être écrits immédiatement.**Utilisez
log.With()pour ajouter des ID et des métadonnées dès que vous les avez.**Utilisez
log.WithFunc(log.Span)pour que les journaux puissent être corrélés avec les traces.
Contrôles de santé
Vérifiez les dépendances réelles. Ne vous contentez pas de renvoyer 200. Vérifier les connexions à la base de données, les services en aval.
**Un bilan de santé qui se bloque est pire qu’un bilan de santé qui échoue.
**Utilisez
/livezpour l’état de santé de base du processus,/readyzpour les contrôles de dépendance complets.
Débogage
N’exposez jamais pprof publiquement. Utilisez un port interne séparé ou une politique de réseau.
Concevoir pour le basculement de débogage. Structurer la journalisation de sorte que le mode débogage révèle des informations utiles sans pour autant submerger.
Voir aussi
- Guide de production - Modèles de déploiement en production
- Clue GitHub Repository - Code source et exemple de météo
- Documentation OpenTelemetry - Concepts OpenTelemetry