1. 結構化日誌記錄
1.1. slog(Go 1.21+ stdlib)
package main
import (
"context"
"log/slog"
"os"
)
func setupLogger(env string) *slog.Logger {
var handler slog.Handler
if env == "production" {
// JSON format for production (structured, parseable)
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true,
})
} else {
// Text format for development (human-readable)
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
}
logger := slog.New(handler)
slog.SetDefault(logger)
return logger
}
func main() {
logger := setupLogger(os.Getenv("APP_ENV"))
// Basic logging
logger.Info("Server starting",
"port", 8080,
"version", "1.2.3",
)
// With context
logger.InfoContext(ctx, "Request processed",
"method", "GET",
"path", "/api/users",
"status", 200,
"duration_ms", 45,
)
// Grouped attributes
logger.Info("User action",
slog.Group("user",
slog.Uint64("id", 123),
slog.String("email", "[email protected]"),
),
slog.Group("request",
slog.String("method", "POST"),
slog.String("path", "/api/orders"),
),
)
// Child logger with persistent fields
reqLogger := logger.With(
"request_id", "abc-123",
"user_id", 42,
)
reqLogger.Info("Processing order")
reqLogger.Info("Order created", "order_id", 100)
}
1.2.日誌中間件
func LoggingMiddleware(logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
// Process request
c.Next()
duration := time.Since(start)
status := c.Writer.Status()
attrs := []slog.Attr{
slog.String("method", c.Request.Method),
slog.String("path", path),
slog.Int("status", status),
slog.Duration("duration", duration),
slog.String("client_ip", c.ClientIP()),
slog.String("user_agent", c.Request.UserAgent()),
}
if requestID, exists := c.Get("request_id"); exists {
attrs = append(attrs, slog.String("request_id", requestID.(string)))
}
level := slog.LevelInfo
if status >= 500 {
level = slog.LevelError
} else if status >= 400 {
level = slog.LevelWarn
}
logger.LogAttrs(c.Request.Context(), level, "HTTP Request", attrs...)
}
}
2. 普羅米修斯指標
go get github.com/prometheus/client_golang
package metrics
import (
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
},
[]string{"method", "path"},
)
activeConnections = promauto.NewGauge(prometheus.GaugeOpts{
Name: "http_active_connections",
Help: "Number of active HTTP connections",
})
dbQueryDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "Database query duration",
Buckets: prometheus.DefBuckets,
},
[]string{"operation", "table"},
)
)
func MetricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
activeConnections.Inc()
start := time.Now()
c.Next()
activeConnections.Dec()
duration := time.Since(start).Seconds()
status := strconv.Itoa(c.Writer.Status())
httpRequestsTotal.WithLabelValues(c.Request.Method, c.FullPath(), status).Inc()
httpRequestDuration.WithLabelValues(c.Request.Method, c.FullPath()).Observe(duration)
}
}
// Expose metrics endpoint
// r.GET("/metrics", gin.WrapH(promhttp.Handler()))
3.Kubernetes部署
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
labels:
app: go-api
spec:
replicas: 3
selector:
matchLabels:
app: go-api
template:
metadata:
labels:
app: go-api
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
containers:
- name: go-api
image: ghcr.io/myorg/go-api:latest
ports:
- containerPort: 8080
env:
- name: APP_ENV
value: production
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: go-api
spec:
selector:
app: go-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: go-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: go-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
4. 配置管理
package config
import (
"log"
"os"
"strconv"
"time"
)
type Config struct {
App AppConfig
DB DBConfig
Redis RedisConfig
JWT JWTConfig
}
type AppConfig struct {
Env string
Port int
}
type DBConfig struct {
Host string
Port int
User string
Password string
Name string
SSLMode string
MaxConns int
}
type RedisConfig struct {
Addr string
Password string
DB int
}
type JWTConfig struct {
AccessSecret string
RefreshSecret string
AccessTTL time.Duration
RefreshTTL time.Duration
}
func Load() *Config {
return &Config{
App: AppConfig{
Env: getEnv("APP_ENV", "development"),
Port: getEnvInt("APP_PORT", 8080),
},
DB: DBConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnvInt("DB_PORT", 5432),
User: getEnv("DB_USER", "app"),
Password: getEnvRequired("DB_PASSWORD"),
Name: getEnv("DB_NAME", "appdb"),
SSLMode: getEnv("DB_SSLMODE", "disable"),
MaxConns: getEnvInt("DB_MAX_CONNS", 25),
},
Redis: RedisConfig{
Addr: getEnv("REDIS_ADDR", "localhost:6379"),
Password: getEnv("REDIS_PASSWORD", ""),
DB: getEnvInt("REDIS_DB", 0),
},
JWT: JWTConfig{
AccessSecret: getEnvRequired("JWT_ACCESS_SECRET"),
RefreshSecret: getEnvRequired("JWT_REFRESH_SECRET"),
AccessTTL: 15 * time.Minute,
RefreshTTL: 7 * 24 * time.Hour,
},
}
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func getEnvRequired(key string) string {
v := os.Getenv(key)
if v == "" {
log.Fatalf("Required env var %s is not set", key)
}
return v
}
func getEnvInt(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
i, err := strconv.Atoi(v)
if err != nil {
log.Fatalf("Invalid int value for %s: %s", key, v)
}
return i
}
return fallback
}
5. 生產檢查表
| 類別 | 專案 | 狀態 |
|---|---|---|
| 安全性 | 已啟用 HTTPS/TLS | ⑨ |
| 安全性 | JWT 來自環境變數的秘密 | ⑨ |
| 安全性 | CORS 正確配置 | ⑨ |
| 安全性 | 已啟用速率限制 | ⑨ |
| 安全性 | 所有端點上的輸入驗證 | ⑨ |
| 安全性 | SQL注入預防(參數化查詢) | ⑨ |
| 可靠性 | 優雅關機 | ⑨ |
| 可靠性 | 健康檢查(活躍度+準備度) | ⑨ |
| 可靠性 | 外部呼叫斷路器 | ⑨ |
| 可靠性 | 使用指數退避重試 | ⑨ |
| 可靠性 | 連接池(DB、Redis、HTTP) | ⑨ |
| 可觀察性 | 結構化日誌記錄 (JSON) | ⑨ |
| 可觀察性 | 普羅米修斯指標 | ⑨ |
| 可觀察性 | 分散式追蹤 | ⑨ |
| 可觀察性 | 錯誤跟踪(哨兵) | ⑨ |
| 效能 | 回應緩存(Redis) | ⑨ |
| 效能 | 資料庫索引 | ⑨ |
| 效能 | pprof 分析可用 | ⑨ |
| 開發營運 | 多階段 Docker 建置 | ⑨ |
| 開發營運 | CI/CD 管道 | ⑨ |
| 開發營運 | 資料庫遷移是自動化的 | ⑨ |
| 開發營運 | Kubernetes 清單 | ⑨ |
恭喜您完成 Golang:從基礎知識到進階系列! 您已經掌握了 Go 基礎、並發性、微服務的 Web 開發以及生產部署。繼續練習並建立真實的項目!