Files
sub2api-cn-relay-manager/internal/log/log.go
phamnazage-jpg 91fa5d6ab4 fix(review): 完成系统性 Review 修复方案 - Task B-01 HTTP Server 超时配置
本次提交包含:
- 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
2026-06-01 22:02:01 +08:00

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)
}