kra-new/pkg/logging/zap.go

124 lines
3.7 KiB
Go

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"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
"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, options Options, attrs ...any) (*slog.Logger, func()) {
file := NewDailyWriter(root, filename, options.RetentionDay)
encoder := zap.NewProductionEncoderConfig()
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)
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()
_ = file.Close()
}
return logger, cleanup
}