first commit
This commit is contained in:
275
pkg/logger/logger.go
Normal file
275
pkg/logger/logger.go
Normal file
@ -0,0 +1,275 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultLevel the default log level
|
||||
DefaultLevel = zapcore.InfoLevel
|
||||
|
||||
// DefaultTimeLayout the default time layout;
|
||||
DefaultTimeLayout = time.DateTime
|
||||
)
|
||||
|
||||
// Option custom setup config
|
||||
type Option func(*option)
|
||||
|
||||
type option struct {
|
||||
level zapcore.Level
|
||||
fields map[string]string
|
||||
file io.Writer
|
||||
timeLayout string
|
||||
disableConsole bool
|
||||
disableCaller bool
|
||||
}
|
||||
|
||||
// WithDebugLevel only greater than 'level' will output
|
||||
func WithDebugLevel() Option {
|
||||
return func(opt *option) {
|
||||
opt.level = zapcore.DebugLevel
|
||||
}
|
||||
}
|
||||
|
||||
// WithInfoLevel only greater than 'level' will output
|
||||
func WithInfoLevel() Option {
|
||||
return func(opt *option) {
|
||||
opt.level = zapcore.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// WithWarnLevel only greater than 'level' will output
|
||||
func WithWarnLevel() Option {
|
||||
return func(opt *option) {
|
||||
opt.level = zapcore.WarnLevel
|
||||
}
|
||||
}
|
||||
|
||||
// WithErrorLevel only greater than 'level' will output
|
||||
func WithErrorLevel() Option {
|
||||
return func(opt *option) {
|
||||
opt.level = zapcore.ErrorLevel
|
||||
}
|
||||
}
|
||||
|
||||
// WithField add some field(s) to log
|
||||
func WithField(key, value string) Option {
|
||||
return func(opt *option) {
|
||||
opt.fields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// WithFileP write log to some file
|
||||
func WithFileP(file string) Option {
|
||||
dir := filepath.Dir(file)
|
||||
if err := os.MkdirAll(dir, 0766); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0766)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(opt *option) {
|
||||
opt.file = zapcore.Lock(f)
|
||||
}
|
||||
}
|
||||
|
||||
// WithFileRotationP write log to some file with rotation
|
||||
func WithFileRotationP(file string) Option {
|
||||
dir := filepath.Dir(file)
|
||||
if err := os.MkdirAll(dir, 0766); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(opt *option) {
|
||||
opt.file = &lumberjack.Logger{ // concurrent-safed
|
||||
Filename: file, // 文件路径
|
||||
MaxSize: 128, // 单个文件最大尺寸,默认单位 M
|
||||
MaxBackups: 300, // 最多保留 300 个备份
|
||||
MaxAge: 30, // 最大时间,默认单位 day
|
||||
LocalTime: true, // 使用本地时间
|
||||
Compress: true, // 是否压缩 disabled by default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeLayout custom time format
|
||||
func WithTimeLayout(timeLayout string) Option {
|
||||
return func(opt *option) {
|
||||
opt.timeLayout = timeLayout
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableConsole WithEnableConsole write log to os.Stdout or os.Stderr
|
||||
func WithDisableConsole() Option {
|
||||
return func(opt *option) {
|
||||
opt.disableConsole = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableCaller disable caller field
|
||||
func WithDisableCaller() Option {
|
||||
return func(opt *option) {
|
||||
opt.disableCaller = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewJSONLogger return a json-encoder zap logger,
|
||||
func NewJSONLogger(opts ...Option) (*zap.Logger, error) {
|
||||
opt := &option{level: DefaultLevel, fields: make(map[string]string)}
|
||||
for _, f := range opts {
|
||||
f(opt)
|
||||
}
|
||||
|
||||
timeLayout := DefaultTimeLayout
|
||||
if opt.timeLayout != "" {
|
||||
timeLayout = opt.timeLayout
|
||||
}
|
||||
|
||||
// similar to zap.NewProductionEncoderConfig()
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger", // used by logger.Named(key); optional; useless
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace", // use by zap.AddStacktrace; optional; useless
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器
|
||||
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
enc.AppendString(t.Format(timeLayout))
|
||||
},
|
||||
EncodeDuration: zapcore.MillisDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder, // 全路径编码器
|
||||
}
|
||||
|
||||
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)
|
||||
|
||||
// lowPriority usd by info\debug\warn
|
||||
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= opt.level && lvl < zapcore.ErrorLevel
|
||||
})
|
||||
|
||||
// highPriority usd by error\panic\fatal
|
||||
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= opt.level && lvl >= zapcore.ErrorLevel
|
||||
})
|
||||
|
||||
stdout := zapcore.Lock(os.Stdout) // lock for concurrent safe
|
||||
stderr := zapcore.Lock(os.Stderr) // lock for concurrent safe
|
||||
|
||||
core := zapcore.NewTee()
|
||||
|
||||
if !opt.disableConsole {
|
||||
core = zapcore.NewTee(
|
||||
zapcore.NewCore(jsonEncoder,
|
||||
zapcore.NewMultiWriteSyncer(stdout),
|
||||
lowPriority,
|
||||
),
|
||||
zapcore.NewCore(jsonEncoder,
|
||||
zapcore.NewMultiWriteSyncer(stderr),
|
||||
highPriority,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if opt.file != nil {
|
||||
core = zapcore.NewTee(core,
|
||||
zapcore.NewCore(jsonEncoder,
|
||||
zapcore.AddSync(opt.file),
|
||||
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= opt.level
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
logger := zap.New(core,
|
||||
zap.WithCaller(!opt.disableCaller),
|
||||
zap.ErrorOutput(stderr),
|
||||
)
|
||||
|
||||
for key, value := range opt.fields {
|
||||
logger = logger.WithOptions(zap.Fields(zapcore.Field{Key: key, Type: zapcore.StringType, String: value}))
|
||||
}
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
var _ Meta = (*meta)(nil)
|
||||
|
||||
// Meta key-value
|
||||
type Meta interface {
|
||||
Key() string
|
||||
Value() any
|
||||
meta()
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
key string
|
||||
value any
|
||||
}
|
||||
|
||||
func (m *meta) Key() string {
|
||||
return m.key
|
||||
}
|
||||
|
||||
func (m *meta) Value() any {
|
||||
return m.value
|
||||
}
|
||||
|
||||
func (m *meta) meta() {}
|
||||
|
||||
// NewMeta create meat
|
||||
func NewMeta(key string, value any) Meta {
|
||||
return &meta{key: key, value: value}
|
||||
}
|
||||
|
||||
// WrapMeta wrap meta to zap fields
|
||||
func WrapMeta(err error, metas ...Meta) (fields []zap.Field) {
|
||||
capacity := len(metas) + 1 // namespace meta
|
||||
if err != nil {
|
||||
capacity++
|
||||
}
|
||||
|
||||
fields = make([]zap.Field, 0, capacity)
|
||||
if err != nil {
|
||||
fields = append(fields, zap.Error(err))
|
||||
}
|
||||
|
||||
fields = append(fields, zap.Namespace("meta"))
|
||||
for _, meta := range metas {
|
||||
fields = append(fields, zap.Any(meta.Key(), meta.Value()))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RestyClientLogger use by resty.Client
|
||||
func RestyClientLogger(logger *zap.Logger) *RestyClientLog {
|
||||
return &RestyClientLog{logger: logger}
|
||||
}
|
||||
|
||||
type RestyClientLog struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func (r *RestyClientLog) Errorf(format string, v ...any) {
|
||||
r.logger.Sugar().Errorf(format, v)
|
||||
}
|
||||
|
||||
func (r *RestyClientLog) Warnf(format string, v ...any) {
|
||||
r.logger.Sugar().Warnf(format, v)
|
||||
}
|
||||
|
||||
func (r *RestyClientLog) Debugf(format string, v ...any) {
|
||||
r.logger.Sugar().Debugf(format, v)
|
||||
}
|
Reference in New Issue
Block a user