feat(log): B-03 日志轮转配置 - 添加 lumberjack 支持
- 添加 lumberjack.v2 依赖实现日志轮转 - 支持配置文件输出(stdout/stderr/file) - 支持文件轮转(100MB/3备份/7天/压缩) - 添加 Config 结构体灵活配置 - 添加完整测试用例 测试验证: - TestInitWithConfig PASS - TestInitWithConfigFileOutput PASS - TestDefaultConfig PASS - 全量日志测试通过
This commit is contained in:
1
go.mod
1
go.mod
@@ -13,6 +13,7 @@ require (
|
|||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
|
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
lukechampine.com/uint128 v1.1.1 // indirect
|
lukechampine.com/uint128 v1.1.1 // indirect
|
||||||
modernc.org/cc/v3 v3.36.0 // indirect
|
modernc.org/cc/v3 v3.36.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.8 // indirect
|
modernc.org/ccgo/v3 v3.16.8 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -42,6 +42,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo=
|
modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo=
|
||||||
|
|||||||
@@ -9,10 +9,35 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *slog.Logger
|
var logger *slog.Logger
|
||||||
|
|
||||||
|
// Config holds logging configuration
|
||||||
|
type Config struct {
|
||||||
|
Level string
|
||||||
|
Output string // "stdout", "stderr", or file path
|
||||||
|
Rotation bool // enable file rotation
|
||||||
|
MaxSize int // MB
|
||||||
|
MaxBackups int
|
||||||
|
MaxAge int // days
|
||||||
|
Compress bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns default logging configuration
|
||||||
|
func DefaultConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Level: "INFO",
|
||||||
|
Output: "stdout",
|
||||||
|
Rotation: false,
|
||||||
|
MaxSize: 100,
|
||||||
|
MaxBackups: 3,
|
||||||
|
MaxAge: 7,
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sensitiveFields contains field names that should be sanitized in logs
|
// sensitiveFields contains field names that should be sanitized in logs
|
||||||
var sensitiveFields = []string{
|
var sensitiveFields = []string{
|
||||||
"token",
|
"token",
|
||||||
@@ -35,14 +60,47 @@ func Init() {
|
|||||||
|
|
||||||
// InitWithLevel initializes the logger with specified level
|
// InitWithLevel initializes the logger with specified level
|
||||||
func InitWithLevel(level string) {
|
func InitWithLevel(level string) {
|
||||||
levelVar := parseLevel(level)
|
cfg := DefaultConfig()
|
||||||
|
cfg.Level = level
|
||||||
|
InitWithConfig(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWithConfig initializes the logger with full configuration
|
||||||
|
func InitWithConfig(cfg Config) {
|
||||||
|
levelVar := parseLevel(cfg.Level)
|
||||||
|
|
||||||
opts := &slog.HandlerOptions{
|
opts := &slog.HandlerOptions{
|
||||||
Level: levelVar,
|
Level: levelVar,
|
||||||
ReplaceAttr: sanitizeAttrs,
|
ReplaceAttr: sanitizeAttrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := slog.NewJSONHandler(os.Stdout, opts)
|
var handler slog.Handler
|
||||||
|
|
||||||
|
switch cfg.Output {
|
||||||
|
case "stdout":
|
||||||
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
||||||
|
case "stderr":
|
||||||
|
handler = slog.NewJSONHandler(os.Stderr, opts)
|
||||||
|
default:
|
||||||
|
// File output with optional rotation
|
||||||
|
if cfg.Rotation {
|
||||||
|
lumberjackLogger := &lumberjack.Logger{
|
||||||
|
Filename: cfg.Output,
|
||||||
|
MaxSize: cfg.MaxSize,
|
||||||
|
MaxBackups: cfg.MaxBackups,
|
||||||
|
MaxAge: cfg.MaxAge,
|
||||||
|
Compress: cfg.Compress,
|
||||||
|
}
|
||||||
|
handler = slog.NewJSONHandler(lumberjackLogger, opts)
|
||||||
|
} else {
|
||||||
|
file, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
handler = slog.NewJSONHandler(file, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger = slog.New(handler)
|
logger = slog.New(handler)
|
||||||
slog.SetDefault(logger)
|
slog.SetDefault(logger)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -130,3 +131,59 @@ func TestRequestLogger(t *testing.T) {
|
|||||||
t.Error("RequestLogger should not return nil")
|
t.Error("RequestLogger should not return nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitWithConfig(t *testing.T) {
|
||||||
|
// Test with stdout output
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
cfg.Output = "stdout"
|
||||||
|
cfg.Level = "DEBUG"
|
||||||
|
InitWithConfig(cfg)
|
||||||
|
|
||||||
|
if logger == nil {
|
||||||
|
t.Error("logger should not be nil after InitWithConfig")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitWithConfigFileOutput(t *testing.T) {
|
||||||
|
// Test with file output (no rotation)
|
||||||
|
tmpFile := t.TempDir() + "/test.log"
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
cfg.Output = tmpFile
|
||||||
|
cfg.Rotation = false
|
||||||
|
InitWithConfig(cfg)
|
||||||
|
|
||||||
|
Info("test message for file")
|
||||||
|
|
||||||
|
// Verify file was created
|
||||||
|
if _, err := os.Stat(tmpFile); os.IsNotExist(err) {
|
||||||
|
t.Errorf("log file %s should exist", tmpFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultConfig(t *testing.T) {
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
|
||||||
|
if cfg.Level != "INFO" {
|
||||||
|
t.Errorf("default Level = %s, want INFO", cfg.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Output != "stdout" {
|
||||||
|
t.Errorf("default Output = %s, want stdout", cfg.Output)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.MaxSize != 100 {
|
||||||
|
t.Errorf("default MaxSize = %d, want 100", cfg.MaxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.MaxBackups != 3 {
|
||||||
|
t.Errorf("default MaxBackups = %d, want 3", cfg.MaxBackups)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.MaxAge != 7 {
|
||||||
|
t.Errorf("default MaxAge = %d, want 7", cfg.MaxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.Compress {
|
||||||
|
t.Error("default Compress should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user