1. Structured Logging
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. Logging Middleware
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. Prometheus Metrics
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 Deployment
# 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. Configuration Management
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. Production Checklist
| Category | Item | Status |
|---|---|---|
| Security | HTTPS/TLS enabled | ☐ |
| Security | JWT secrets from env vars | ☐ |
| Security | CORS properly configured | ☐ |
| Security | Rate limiting enabled | ☐ |
| Security | Input validation on all endpoints | ☐ |
| Security | SQL injection prevention (parameterized queries) | ☐ |
| Reliability | Graceful shutdown | ☐ |
| Reliability | Health checks (liveness + readiness) | ☐ |
| Reliability | Circuit breakers for external calls | ☐ |
| Reliability | Retry with exponential backoff | ☐ |
| Reliability | Connection pooling (DB, Redis, HTTP) | ☐ |
| Observability | Structured logging (JSON) | ☐ |
| Observability | Prometheus metrics | ☐ |
| Observability | Distributed tracing | ☐ |
| Observability | Error tracking (Sentry) | ☐ |
| Performance | Response caching (Redis) | ☐ |
| Performance | Database indexes | ☐ |
| Performance | pprof profiling available | ☐ |
| DevOps | Multi-stage Docker build | ☐ |
| DevOps | CI/CD pipeline | ☐ |
| DevOps | Database migrations automated | ☐ |
| DevOps | Kubernetes manifests | ☐ |
Chúc mừng bạn đã hoàn thành series Golang: Từ Cơ bản đến Nâng cao! Bạn đã nắm vững từ Go fundamentals, concurrency, web development đến microservices và production deployment. Hãy tiếp tục thực hành và xây dựng các dự án thực tế!