1. 構造化されたロギング
1.1.スログ (Go 1.21+ 標準ライブラリ)
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 プロファイリングが利用可能 | ☐ |
| DevOps | 多段階の Docker ビルド | ☐ |
| DevOps | CI/CD パイプライン | ☐ |
| DevOps | データベースの移行は自動化されています | ☐ |
| DevOps | Kubernetes マニフェスト | ☐ |
「Golang: From Basics to Advanced」シリーズの完了おめでとうございます! Go の基礎、同時実行性、Web 開発からマイクロサービス、実稼働デプロイメントまでをマスターしました。練習を続けて実際のプロジェクトを構築してください。