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>
138 lines
2.9 KiB
Go
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,
|
|
}
|
|
}
|