spark-server/internal/infrastructure/logger/options.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

138 lines
2.9 KiB
Go

package logger
import (
"os"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
// Options control zap sinks. File uses lumberjack rotation; Console is for local debug.
type Options struct {
Level string
Encoding string // json | console (stdout only; files are always JSON)
Console bool
File FileOptions
}
// FileOptions is lumberjack rotation for a JSON log file.
type FileOptions struct {
Path string
MaxSizeMB int
MaxBackups int
MaxAgeDays int
Compress bool
}
type closer struct {
log *zap.Logger
lj *lumberjack.Logger
}
func (c *closer) Close() error {
if c == nil {
return nil
}
if c.log != nil {
_ = c.log.Sync()
}
if c.lj != nil {
return c.lj.Close()
}
return nil
}
// New builds a zap logger. Close the returned func on shutdown to flush and close the file sink.
func New(o *Options) (*zap.Logger, func() error) {
if o == nil {
o = &Options{}
}
applyDefaults(o)
level := parseLevel(o.Level)
var lj *lumberjack.Logger
cores := make([]zapcore.Core, 0, 2)
if o.Console {
cores = append(cores, zapcore.NewCore(stdoutEncoder(o.Encoding), zapcore.AddSync(os.Stdout), level))
}
if o.File.Path != "" {
lj = newLumberjack(o.File)
cores = append(cores, zapcore.NewCore(jsonEncoder(), zapcore.AddSync(lj), level))
}
if len(cores) == 0 {
cores = append(cores, zapcore.NewCore(jsonEncoder(), zapcore.AddSync(os.Stdout), level))
}
log := zap.New(zapcore.NewTee(cores...), zap.AddCaller())
c := &closer{log: log, lj: lj}
return log, c.Close
}
func applyDefaults(o *Options) {
if o.Level == "" {
o.Level = "info"
}
if o.Encoding == "" {
if strings.EqualFold(o.Level, "debug") {
o.Encoding = "console"
} else {
o.Encoding = "json"
}
}
if !o.Console && o.File.Path == "" {
o.Console = true
}
if o.File.Path != "" {
if o.File.MaxSizeMB <= 0 {
o.File.MaxSizeMB = 100
}
if o.File.MaxBackups <= 0 {
o.File.MaxBackups = 7
}
if o.File.MaxAgeDays <= 0 {
o.File.MaxAgeDays = 7
}
}
}
func parseLevel(s string) zapcore.Level {
switch strings.ToLower(s) {
case "debug":
return zapcore.DebugLevel
case "warn", "warning":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
default:
return zapcore.InfoLevel
}
}
func jsonEncoder() zapcore.Encoder {
cfg := zap.NewProductionEncoderConfig()
cfg.TimeKey = "ts"
cfg.EncodeTime = zapcore.ISO8601TimeEncoder
cfg.MessageKey = "msg"
return zapcore.NewJSONEncoder(cfg)
}
func stdoutEncoder(encoding string) zapcore.Encoder {
if strings.EqualFold(encoding, "json") {
return jsonEncoder()
}
cfg := zap.NewDevelopmentEncoderConfig()
cfg.EncodeTime = zapcore.ISO8601TimeEncoder
return zapcore.NewConsoleEncoder(cfg)
}
func newLumberjack(f FileOptions) *lumberjack.Logger {
return &lumberjack.Logger{
Filename: f.Path,
MaxSize: f.MaxSizeMB,
MaxBackups: f.MaxBackups,
MaxAge: f.MaxAgeDays,
Compress: f.Compress,
LocalTime: true,
}
}