为什么日志是应用系统的灵魂呢?一个应用系统稳定的运行,业务逻辑正常时,它的外在表现就那么的光鲜靓丽,而当日志设计不合理,系统出现问题排查时才发现,内在的灵魂是那么的肮脏,残酷的事实让你明白『可远观而不可亵玩焉』。
package log
import (
"context"
"fmt"
"net/url"
"os"
"regexp"
"strconv"
"github.com/rs/zerolog"
"github.com/vicanso/beginner/util"
mask "github.com/vicanso/go-mask"
)
// 日志中值的最大长度
var logFieldValueMaxSize = 30
var logMask = mask.New(
// 指定哪些日志需要处理为***
mask.RegExpOption(regexp.MustCompile(`password`)),
// 指定长度截断(如果不希望截断的,则可添加自定义处理)
mask.MaxLengthOption(logFieldValueMaxSize),
// 手机号码中间4位不展示
mask.CustomMaskOption(regexp.MustCompile(`mobile`), func(key, value string) string {
size := len(value)
if size < 8 {
return value
}
return value[0:size-8] + "****" + value[size-4:]
}),
)
type entLogger struct{}
func (el *entLogger) Log(args ...interface{}) {
Info(context.Background()).
Msg(fmt.Sprint(args...))
}
var defaultLogger = newLogger()
// newLogger 初始化logger
func newLogger() *zerolog.Logger {
// 如果要节约日志空间,可以配置
zerolog.TimestampFieldName = "t"
zerolog.LevelFieldName = "l"
zerolog.TimeFieldFormat = "2006-01-02T15:04:05.999Z07:00"
var l zerolog.Logger
if util.IsDevelopment() {
l = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).
With().
Timestamp().
Logger()
} else {
l = zerolog.New(os.Stdout).
Level(zerolog.InfoLevel).
With().
Timestamp().
Logger()
}
// 如果有配置指定日志级别,则以配置指定的输出
logLevel := os.Getenv("LOG_LEVEL")
if logLevel != "" {
lv, _ := strconv.Atoi(logLevel)
l = l.Level(zerolog.Level(lv))
}
return &l
}
func fillTraceInfos(ctx context.Context, e *zerolog.Event) *zerolog.Event {
traceID := util.GetTraceID(ctx)
// 设置trace id,方便标记当前链路的日志
if traceID != "" {
e.Str("traceID", traceID)
}
account := util.GetAccount(ctx)
if account == "" {
return e
}
// 记录客户信息
return e.Str("account", account)
}
func Info(ctx context.Context) *zerolog.Event {
return fillTraceInfos(ctx, defaultLogger.Info())
}
func Error(ctx context.Context) *zerolog.Event {
return fillTraceInfos(ctx, defaultLogger.Error())
}
func Debug(ctx context.Context) *zerolog.Event {
return fillTraceInfos(ctx, defaultLogger.Debug())
}
func Warn(ctx context.Context) *zerolog.Event {
return fillTraceInfos(ctx, defaultLogger.Warn())
}
// URLValues create a url.Values log event
func URLValues(query url.Values) *zerolog.Event {
if len(query) == 0 {
return zerolog.Dict()
}
return zerolog.Dict().Fields(logMask.URLValues(query))
}
// Struct create a struct log event
func Struct(data interface{}) *zerolog.Event {
if data == nil {
return zerolog.Dict()
}
m, _ := logMask.Struct(data)
return zerolog.Dict().Fields(m)
}
// NewEntLogger create a ent logger
func NewEntLogger() *entLogger {
return &entLogger{}
}
初始化日志实例的处理逻辑比较简单,根据不同的运行环境使用不同的配置以及日志输出级别等。每个日志函数均需要指定context,用于添加trace信息(如账号等),处理逻辑在fillTraceInfos
函数中。
package main
import (
"github.com/vicanso/beginner/log"
"github.com/vicanso/elton"
)
func main() {
e := elton.New()
addr := ":7001"
log.Info(context.Background()).
Str("addr", addr).
Msg("server is running")
// 监听端口
err := e.ListenAndServe(addr)
// 如果失败则直接panic,因为程序无法提供服务
if err != nil {
log.Error(context.Background()).
Err(err).
Msg("server listen fail")
panic(err)
}
}