Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 25s
feat(admin): 新增工会管理功能 feat(activity): 添加活动管理相关服务 feat(user): 实现用户道具卡和积分管理 feat(guild): 新增工会成员管理功能 fix: 修复数据库连接配置 fix: 修正jwtoken导入路径 fix: 解决端口冲突问题 style: 统一代码格式和注释风格 style: 更新项目常量命名 docs: 添加项目框架和开发规范文档 docs: 更新接口文档注释 chore: 移除无用代码和文件 chore: 更新Makefile和配置文件 chore: 清理日志文件 test: 添加道具卡测试脚本
395 lines
9.2 KiB
Go
395 lines
9.2 KiB
Go
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"bindbox-game/internal/repository/mysql/dao"
|
|
"bindbox-game/internal/repository/mysql/model"
|
|
|
|
"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.RFC3339
|
|
)
|
|
|
|
// Option custom setup config
|
|
type Option func(*option)
|
|
|
|
type option struct {
|
|
level zapcore.Level
|
|
fields map[string]string
|
|
file io.Writer
|
|
timeLayout string
|
|
outputInConsole 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
|
|
}
|
|
}
|
|
|
|
// WithOutputInConsole write log to os.Stdout or os.Stderr
|
|
func WithOutputInConsole() Option {
|
|
return func(opt *option) {
|
|
opt.outputInConsole = 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.outputInConsole {
|
|
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.AddCaller(),
|
|
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
|
|
}
|
|
|
|
// CustomLogger 自定义日志记录器
|
|
type customLogger struct {
|
|
db *dao.Query
|
|
logger *zap.Logger
|
|
}
|
|
|
|
var _ CustomLogger = (*customLogger)(nil)
|
|
|
|
type CustomLogger interface {
|
|
i()
|
|
Info(msg string, fields ...zap.Field)
|
|
Debug(msg string, fields ...zap.Field)
|
|
Warn(msg string, fields ...zap.Field)
|
|
Error(msg string, fields ...zap.Field)
|
|
Fatal(msg string, fields ...zap.Field)
|
|
Sync() error
|
|
}
|
|
|
|
// NewCustomLogger 创建自定义日志记录器
|
|
func NewCustomLogger(db *dao.Query, opts ...Option) (CustomLogger, error) {
|
|
logger, err := NewJSONLogger(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &customLogger{
|
|
db: db,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
func (c *customLogger) i() {
|
|
}
|
|
|
|
// fieldsToJSON 将 zap.Field 转换为 JSON 字符串
|
|
func (c *customLogger) fieldsToJSON(msg string, fields []zap.Field) (string, error) {
|
|
// 创建一个 buffer 来存储编码后的 JSON
|
|
buffer := &bytes.Buffer{}
|
|
|
|
// 创建一个 zapcore.Encoder 对象
|
|
encoderConfig := zap.NewProductionEncoderConfig()
|
|
encoder := zapcore.NewJSONEncoder(encoderConfig)
|
|
|
|
// 创建一个 zapcore.Core 对象
|
|
core := zapcore.NewCore(encoder, zapcore.AddSync(buffer), zap.InfoLevel)
|
|
|
|
// 创建一个 zap.Logger 对象
|
|
tempLogger := zap.New(core)
|
|
|
|
// 使用 tempLogger 记录一条日志,只包含 fields
|
|
tempLogger.Info(msg, fields...)
|
|
|
|
// 获取编码后的 JSON 字符串
|
|
jsonStr := buffer.String()
|
|
|
|
// 去掉最后的换行符
|
|
if len(jsonStr) > 0 && jsonStr[len(jsonStr)-1] == '\n' {
|
|
jsonStr = jsonStr[:len(jsonStr)-1]
|
|
}
|
|
|
|
// 解析 JSON 字符串以确保格式正确
|
|
var parsedJSON map[string]interface{}
|
|
err := json.Unmarshal([]byte(jsonStr), &parsedJSON)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// 重新编码为 JSON 字符串
|
|
jsonBytes, err := json.Marshal(parsedJSON)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(jsonBytes), nil
|
|
}
|
|
|
|
// fieldsJsonToDB 将 zap.Field 转换为数据库记录
|
|
func (c *customLogger) fieldsJsonToDB(level, msg string, fields []zap.Field) {
|
|
content := ""
|
|
|
|
jsonFields, err := c.fieldsToJSON(msg, fields)
|
|
if err != nil {
|
|
c.logger.Error("Failed to convert zap fields to JSON", zap.Error(err))
|
|
} else {
|
|
content = jsonFields
|
|
}
|
|
|
|
if c.db != nil {
|
|
if err := c.db.LogOperation.Create(&model.LogOperation{
|
|
Level: level,
|
|
Msg: msg,
|
|
Content: content,
|
|
CreatedAt: time.Now(),
|
|
}); err != nil {
|
|
log.Println(fmt.Sprintf("Failed to create log operation record: %s", err.Error()))
|
|
}
|
|
} else {
|
|
log.Println(level, msg, content)
|
|
}
|
|
}
|
|
|
|
// Info 重写 Info 方法
|
|
func (c *customLogger) Info(msg string, fields ...zap.Field) {
|
|
c.logger.Info(msg, fields...)
|
|
c.fieldsJsonToDB("info", msg, fields)
|
|
}
|
|
|
|
// Debug 调用底层 Debug 方法
|
|
func (c *customLogger) Debug(msg string, fields ...zap.Field) {
|
|
c.logger.Debug(msg, fields...)
|
|
c.fieldsJsonToDB("debug", msg, fields)
|
|
}
|
|
|
|
// Warn 调用底层 Warn 方法
|
|
func (c *customLogger) Warn(msg string, fields ...zap.Field) {
|
|
c.logger.Warn(msg, fields...)
|
|
c.fieldsJsonToDB("warn", msg, fields)
|
|
}
|
|
|
|
// Error 调用底层 Error 方法
|
|
func (c *customLogger) Error(msg string, fields ...zap.Field) {
|
|
c.logger.Error(msg, fields...)
|
|
c.fieldsJsonToDB("error", msg, fields)
|
|
}
|
|
|
|
// Fatal 调用底层 Fatal 方法
|
|
func (c *customLogger) Fatal(msg string, fields ...zap.Field) {
|
|
c.logger.Fatal(msg, fields...)
|
|
c.fieldsJsonToDB("fatal", msg, fields)
|
|
}
|
|
|
|
// Sync 调用底层 Sync 方法
|
|
func (c *customLogger) Sync() error {
|
|
return c.logger.Sync()
|
|
}
|
|
|
|
// With 调用底层 With 方法
|
|
//func (c *customLogger) With(db mysql.Repo, fields ...zap.Field) *customLogger {
|
|
// return &customLogger{db: db, logger: c.logger.With(fields...)}
|
|
//}
|
|
|
|
var _ Meta = (*meta)(nil)
|
|
|
|
// Meta key-value
|
|
type Meta interface {
|
|
Key() string
|
|
Value() interface{}
|
|
meta()
|
|
}
|
|
|
|
type meta struct {
|
|
key string
|
|
value interface{}
|
|
}
|
|
|
|
func (m *meta) Key() string {
|
|
return m.key
|
|
}
|
|
|
|
func (m *meta) Value() interface{} {
|
|
return m.value
|
|
}
|
|
|
|
func (m *meta) meta() {}
|
|
|
|
// NewMeta create meat
|
|
func NewMeta(key string, value interface{}) 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
|
|
}
|