日志插件
glog 插件实现。
zap 日志插件
zap Option
internal/logger/zap_option.go
package logger
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type ZapOption func(o *zapOption)
type zapOption struct {
encoder zapcore.EncoderConfig
level zap.AtomicLevel
opts []zap.Option
ws []zapcore.WriteSyncer
}
func Encoder(encoder zapcore.EncoderConfig) ZapOption {
return func(o *zapOption) { o.encoder = encoder }
}
func Level(level zap.AtomicLevel) ZapOption {
return func(o *zapOption) { o.level = level }
}
func Option(opts ...zap.Option) ZapOption {
return func(o *zapOption) { o.opts = opts }
}
func WriteSyncer(ws ...zapcore.WriteSyncer) ZapOption {
return func(o *zapOption) { o.ws = ws }
}
zap Logger
internal/logger/zap.go
package logger
import (
"fmt"
"github.com/camry/g/glog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var _ glog.Logger = (*ZapLogger)(nil)
// ZapLogger 实现 glog.Logger
type ZapLogger struct {
log *zap.Logger
}
// NewZapLogger 返回 zap 日志记录器。
func NewZapLogger(opts ...ZapOption) *ZapLogger {
opt := &zapOption{}
for _, o := range opts {
o(opt)
}
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(opt.encoder),
zapcore.NewMultiWriteSyncer(opt.ws...),
opt.level,
)
zapLogger := zap.New(core, opt.opts...)
return &ZapLogger{log: zapLogger}
}
// Log 实现 glog.Logger 接口。
func (l *ZapLogger) Log(level glog.Level, keyvals ...any) error {
if len(keyvals) == 0 || len(keyvals)%2 != 0 {
l.log.Warn(fmt.Sprint("keyvals must appear in pairs: ", keyvals))
return nil
}
// Zap.Field 出现 keyvals 对时使用。
var data []zap.Field
for i := 0; i < len(keyvals); i += 2 {
data = append(data, zap.Any(fmt.Sprint(keyvals[i]), fmt.Sprint(keyvals[i+1])))
}
switch level {
case glog.LevelDebug:
l.log.Debug("", data...)
case glog.LevelInfo:
l.log.Info("", data...)
case glog.LevelWarn:
l.log.Warn("", data...)
case glog.LevelError:
l.log.Error("", data...)
}
return nil
}
func (l *ZapLogger) Close() error {
return l.log.Sync()
}
gorm 日志插件
gorm Logger
package logger
import (
"context"
"errors"
"fmt"
"time"
"github.com/camry/g/glog"
"gorm.io/gorm/logger"
"gorm.io/gorm/utils"
)
type GormLogger struct {
*logger.Config
GLog *glog.Helper
infoStr, warnStr, errStr string
traceStr, traceErrStr, traceWarnStr string
}
func NewGormLogger(gl glog.Logger, config *logger.Config) *GormLogger {
var (
infoStr = "%s\n[info] "
warnStr = "%s\n[warn] "
errStr = "%s\n[error] "
traceStr = "%s\n[%.3fms] [rows:%v] %s"
traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s"
)
if config.Colorful {
infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset
warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + logger.Reset
errStr = logger.Magenta + "%s\n" + logger.Reset + logger.Red + "[error] " + logger.Reset
traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset
traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
}
return &GormLogger{
GLog: glog.NewHelper(gl),
Config: config,
infoStr: infoStr,
warnStr: warnStr,
errStr: errStr,
traceStr: traceStr,
traceWarnStr: traceWarnStr,
traceErrStr: traceErrStr,
}
}
func (l *GormLogger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *l
newLogger.LogLevel = level
return &newLogger
}
func (l *GormLogger) Info(ctx context.Context, msg string, i ...any) {
if l.LogLevel >= logger.Info {
l.GLog.Infof(l.infoStr+msg, i...)
}
}
func (l *GormLogger) Warn(ctx context.Context, msg string, i ...any) {
if l.LogLevel >= logger.Warn {
l.GLog.Warnf(l.warnStr+msg, i...)
}
}
func (l *GormLogger) Error(ctx context.Context, msg string, i ...any) {
if l.LogLevel >= logger.Error {
l.GLog.Errorf(l.errStr+msg, i...)
}
}
func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
if l.LogLevel <= logger.Silent {
return
}
elapsed := time.Since(begin)
switch {
case err != nil && l.LogLevel >= logger.Error && (!errors.Is(err, logger.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
sql, rows := fc()
if rows == -1 {
l.GLog.Errorf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.GLog.Errorf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn:
sql, rows := fc()
slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
if rows == -1 {
l.GLog.Warnf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.GLog.Warnf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case l.LogLevel == logger.Info:
sql, rows := fc()
if rows == -1 {
l.GLog.Infof(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.GLog.Infof(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
}
}
gorm 配置
func NewDb(cfg *config.Config, l glog.Logger) *gorm.DB {
dsn := fmt.Sprintf(`%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local`,
cfg.Db.User, cfg.Db.Pass, cfg.Db.Host, cfg.Db.Port, cfg.Db.Name, cfg.Db.Charset,
)
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{
Logger: logger.NewGormLogger(l, &gormLogger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: gormLogger.Warn,
IgnoreRecordNotFoundError: false,
Colorful: false,
}), // 设置日志处理器
SkipDefaultTransaction: true, // 禁用默认事务
PrepareStmt: true, // 缓存预编译语句
})
if err != nil {
panic(err)
}
return db
}
gin 日志中间件
Gin Logger 中间件
package middleware
import (
"fmt"
"net/http"
"runtime/debug"
"time"
"github.com/camry/g/glog"
"github.com/gin-gonic/gin"
)
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
req := c.Request
res := c.Writer
fields := []any{
"remote_ip", c.ClientIP(),
"latency", time.Now().Sub(start),
"host", req.Host,
"request", fmt.Sprintf("%s %s", req.Method, req.RequestURI),
"status", res.Status(),
"size", res.Size(),
"user_agent", req.UserAgent(),
"errors", c.Errors.ByType(gin.ErrorTypePrivate).String(),
"raw", req.URL.RawQuery,
}
n := res.Status()
switch {
case n >= 500:
fields = append(fields, "msg", "Server error")
glog.Errorw(fields...)
case n >= 400:
fields = append(fields, "msg", "Client error")
glog.Warnw(fields...)
case n >= 300:
fields = append(fields, "msg", "Redirection")
glog.Infow(fields...)
default:
fields = append(fields, "msg", "Success")
glog.Infow(fields...)
}
}
}
func GinRecover() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
glog.Errorf(`panic: %v`, r)
glog.Errorf(`stack: %v`, string(debug.Stack()))
debug.PrintStack()
c.String(http.StatusInternalServerError, "Internal Server Error")
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
Gin 路由中间件配置
func NewRouter(cfg *config.Config, logger glog.Logger, sn *snowflake.Node, bt *table.BattleTable) *gin.Engine {
gin.SetMode(cfg.App.Env)
router := gin.Default()
router.Use(middleware.GinLogger(), middleware.GinRecover())
homeC := controllers.NewHome(cfg, logger, bt, sn)
router.GET("/", homeC.Index)
router.GET("/home/debug", homeC.Debug)
return router
}