本次提交包含: - B-01: HTTP Server 添加超时配置 (ReadTimeout/WriteTimeout/IdleTimeout/MaxHeaderBytes) - 添加结构化日志包 internal/log/ (B-02 部分完成) - 添加 Review 报告文档 - 添加系统性修复方案文档 - 添加最佳实践审核报告文档 - 更新任务清单和执行板 测试验证: - TestServerHasTimeoutConfiguration 通过 关联文档: - docs/2026-06-01-SYSTEMATIC-REVIEW-REPORT.md - docs/2026-06-01-SYSTEMATIC-REPAIR-PLAN.md - docs/2026-06-01-BEST-PRACTICE-AUDIT-REPORT.md
138 lines
2.9 KiB
Go
138 lines
2.9 KiB
Go
// Package log provides structured logging using slog.
|
|
// It supports JSON output, configurable log levels, and sensitive field sanitization.
|
|
package log
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"log/slog"
|
|
)
|
|
|
|
var logger *slog.Logger
|
|
|
|
// sensitiveFields contains field names that should be sanitized in logs
|
|
var sensitiveFields = []string{
|
|
"token",
|
|
"password",
|
|
"secret",
|
|
"key",
|
|
"credential",
|
|
"auth",
|
|
"api_key",
|
|
"api_secret",
|
|
"private_key",
|
|
"access_token",
|
|
"refresh_token",
|
|
}
|
|
|
|
// Init initializes the logger with JSON handler and INFO level
|
|
func Init() {
|
|
InitWithLevel("INFO")
|
|
}
|
|
|
|
// InitWithLevel initializes the logger with specified level
|
|
func InitWithLevel(level string) {
|
|
levelVar := parseLevel(level)
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: levelVar,
|
|
ReplaceAttr: sanitizeAttrs,
|
|
}
|
|
|
|
handler := slog.NewJSONHandler(os.Stdout, opts)
|
|
logger = slog.New(handler)
|
|
slog.SetDefault(logger)
|
|
}
|
|
|
|
// parseLevel parses string level to slog.Level
|
|
func parseLevel(level string) slog.Level {
|
|
switch strings.ToUpper(level) {
|
|
case "DEBUG":
|
|
return slog.LevelDebug
|
|
case "INFO":
|
|
return slog.LevelInfo
|
|
case "WARN", "WARNING":
|
|
return slog.LevelWarn
|
|
case "ERROR":
|
|
return slog.LevelError
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
}
|
|
|
|
// sanitizeAttrs sanitizes sensitive fields in log attributes
|
|
func sanitizeAttrs(groups []string, a slog.Attr) slog.Attr {
|
|
// Check if attribute key contains sensitive field name
|
|
keyLower := strings.ToLower(a.Key)
|
|
for _, field := range sensitiveFields {
|
|
if strings.Contains(keyLower, field) {
|
|
return slog.String(a.Key, "[REDACTED]")
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// IsSensitive checks if a field name is sensitive
|
|
func IsSensitive(field string) bool {
|
|
fieldLower := strings.ToLower(field)
|
|
for _, f := range sensitiveFields {
|
|
if strings.Contains(fieldLower, f) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Info logs an info level message
|
|
func Info(msg string, args ...any) {
|
|
slog.Info(msg, args...)
|
|
}
|
|
|
|
// Error logs an error level message
|
|
func Error(msg string, args ...any) {
|
|
slog.Error(msg, args...)
|
|
}
|
|
|
|
// Debug logs a debug level message
|
|
func Debug(msg string, args ...any) {
|
|
slog.Debug(msg, args...)
|
|
}
|
|
|
|
// Warn logs a warning level message
|
|
func Warn(msg string, args ...any) {
|
|
slog.Warn(msg, args...)
|
|
}
|
|
|
|
// Logger returns the underlying logger
|
|
func Logger() *slog.Logger {
|
|
return logger
|
|
}
|
|
|
|
// WithContext returns a logger with context
|
|
func WithContext(ctx context.Context) *slog.Logger {
|
|
// Extract trace_id from context if present
|
|
if traceID, ok := ctx.Value("trace_id").(string); ok {
|
|
return logger.With("trace_id", traceID)
|
|
}
|
|
return logger
|
|
}
|
|
|
|
// RequestLogger returns a logger with request fields
|
|
func RequestLogger(method, path, clientIP string) *slog.Logger {
|
|
return logger.With(
|
|
"method", method,
|
|
"path", path,
|
|
"client_ip", clientIP,
|
|
"time", time.Now().UTC(),
|
|
)
|
|
}
|
|
|
|
// Fatal logs an error message and exits with code 1
|
|
func Fatal(msg string, args ...any) {
|
|
slog.Error(msg, args...)
|
|
os.Exit(1)
|
|
}
|