This commit is contained in:
2026-08-14 16:34:17 +08:00
parent e0b0e1d8c8
commit f63acae353
53 changed files with 2488 additions and 350 deletions
+30 -7
View File
@@ -11,15 +11,38 @@ import (
// file handle open for the active day and rotates on the first write after
// midnight, matching the directory layout consumed by the log viewer.
type DailyWriter struct {
mu sync.Mutex
root string
name string
date string
file *os.File
mu sync.Mutex
root string
name string
date string
file *os.File
retentionDay int
}
func NewDailyWriter(root, name string) *DailyWriter {
return &DailyWriter{root: root, name: name}
func NewDailyWriter(root, name string, retentionDay int) *DailyWriter {
w := &DailyWriter{root: root, name: name, retentionDay: retentionDay}
w.removeExpired()
return w
}
func (w *DailyWriter) removeExpired() {
if w.retentionDay <= 0 {
return
}
entries, err := os.ReadDir(w.root)
if err != nil {
return
}
cutoff := time.Now().AddDate(0, 0, -w.retentionDay)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
date, err := time.Parse("2006-01-02", entry.Name())
if err == nil && date.Before(cutoff) {
_ = os.RemoveAll(filepath.Join(w.root, entry.Name()))
}
}
}
func (w *DailyWriter) Write(value []byte) (int, error) {
+98 -9
View File
@@ -3,6 +3,8 @@ package logging
import (
"log/slog"
"os"
"strings"
"time"
"github.com/go-kratos/kratos/contrib/otel/v3/tracing"
kratoslog "github.com/go-kratos/kratos/v3/log"
@@ -11,20 +13,107 @@ import (
"go.uber.org/zap/zapcore"
)
type Options struct {
Level, Format, EncodeLevel, Prefix, StacktraceKey string
LogInConsole, ShowLine bool
RetentionDay int
FileOnlyModules []string
}
type moduleFilterCore struct {
zapcore.Core
fileOnly map[string]struct{}
mod string
}
func (c *moduleFilterCore) With(fields []zapcore.Field) zapcore.Core {
mod := c.mod
if value := moduleField(fields); value != "" {
mod = value
}
return &moduleFilterCore{Core: c.Core.With(fields), fileOnly: c.fileOnly, mod: mod}
}
func (c *moduleFilterCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.Enabled(entry.Level) {
return checked.AddCore(entry, c)
}
return checked
}
func (c *moduleFilterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
mod := c.mod
if value := moduleField(fields); value != "" {
mod = value
}
if _, excluded := c.fileOnly[mod]; excluded {
return nil
}
return c.Core.Write(entry, fields)
}
func moduleField(fields []zapcore.Field) string {
for _, field := range fields {
if field.Key == "mod" {
return field.String
}
}
return ""
}
// NewZapLogger adapts a Zap core to the slog logger used by Kratos v3.
// The file layout remains compatible with the administration log viewer.
func NewZapLogger(root, filename string, attrs ...any) (*slog.Logger, func()) {
file := NewDailyWriter(root, filename)
func NewZapLogger(root, filename string, options Options, attrs ...any) (*slog.Logger, func()) {
file := NewDailyWriter(root, filename, options.RetentionDay)
encoder := zap.NewProductionEncoderConfig()
encoder.EncodeTime = zapcore.ISO8601TimeEncoder
encoder.EncodeLevel = zapcore.LowercaseLevelEncoder
core := zapcore.NewCore(
zapcore.NewJSONEncoder(encoder),
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(file)),
zap.NewAtomicLevelAt(zap.InfoLevel),
encoder.EncodeTime = zapcore.RFC3339NanoTimeEncoder
if options.StacktraceKey != "" {
encoder.StacktraceKey = options.StacktraceKey
}
switch options.EncodeLevel {
case "CapitalLevelEncoder":
encoder.EncodeLevel = zapcore.CapitalLevelEncoder
case "CapitalColorLevelEncoder":
encoder.EncodeLevel = zapcore.CapitalColorLevelEncoder
case "LowercaseColorLevelEncoder":
encoder.EncodeLevel = zapcore.LowercaseColorLevelEncoder
default:
encoder.EncodeLevel = zapcore.LowercaseLevelEncoder
}
var outputEncoder zapcore.Encoder = zapcore.NewJSONEncoder(encoder)
if options.Format != "" && options.Format != "json" {
if options.Prefix != "" {
encoder.EncodeTime = func(value time.Time, output zapcore.PrimitiveArrayEncoder) {
output.AppendString(options.Prefix + value.Format("2006-01-02 15:04:05.000"))
}
}
outputEncoder = zapcore.NewConsoleEncoder(encoder)
}
level := zap.InfoLevel
if parsed := level.Set(strings.ToLower(options.Level)); parsed != nil {
level = zap.DebugLevel
}
levelEnabler := zap.NewAtomicLevelAt(level)
fileCore := zapcore.NewCore(
outputEncoder.Clone(),
zapcore.AddSync(file),
levelEnabler,
)
core := zapcore.Core(fileCore)
if options.LogInConsole {
fileOnly := make(map[string]struct{}, len(options.FileOnlyModules))
for _, module := range options.FileOnlyModules {
fileOnly[module] = struct{}{}
}
consoleCore := zapcore.NewCore(outputEncoder.Clone(), zapcore.AddSync(os.Stdout), levelEnabler)
core = zapcore.NewTee(fileCore, &moduleFilterCore{Core: consoleCore, fileOnly: fileOnly})
}
zapLogger := zap.New(core)
handler := zapslog.NewHandler(zapLogger.Core(), zapslog.WithCaller(true), zapslog.AddStacktraceAt(slog.LevelError))
handlerOptions := []zapslog.HandlerOption{zapslog.AddStacktraceAt(slog.LevelError)}
if options.ShowLine {
handlerOptions = append(handlerOptions, zapslog.WithCaller(true))
}
handler := zapslog.NewHandler(zapLogger.Core(), handlerOptions...)
logger := kratoslog.NewLogger(handler, kratoslog.WithExtractor(tracing.TraceAttrs)).With(attrs...)
cleanup := func() {
_ = zapLogger.Sync()