- 内部定义好的encoder
- Core">type Core
- Encoder">type Encoder
- EncoderConfig">type EncoderConfig
- Level">type Level
- LevelEnabler">type LevelEnabler
- TimeEncoder">type TimeEncoder
- WriteSyncer">type WriteSyncer
- Entry">type Entry
- EntryCaller">type EntryCaller
- Field">type Field
- 例子
文档:https://godoc.org/go.uber.org/zap/zapcore
zapcore定义并实现了构建zap所依赖的底层接口。通过提供这些接口的替代实现,外部包可以扩展zap的功能
内部定义好的encoder
LevelEncoder
func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder)
func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder)
TimeEncoder
func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func RFC3339NanoTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func RFC3339TimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
CallerEncoder
func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder)
func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder)
NameEncoder
func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder)
type Core
Core是一个最小的、快速的日志记录器接口。它是为库作者设计的,以包装在一个更用户友好的API中
type Core interface {
LevelEnabler
With([]Field) Core
Check(Entry, *CheckedEntry) *CheckedEntry
Write(Entry, []Field) error
Sync() error
}
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error)
- 根据现有的core,创建一个等级更高的core
- 如果级别降低了日志级别,则返回一个错误
func NewTee(cores …Core) Core 创建一个核心,该核心将日志条目复制到两个或多个底层核心中
func RegisterHooks(core Core, hooks …func(Entry) error) Core
- registerhook包装一个核心,并在每次记录一条消息时运行用户定义的回调钩子集合
type Encoder
编码器是用于所有日志条目的格式接口
func NewConsoleEncoder(cfg EncoderConfig) Encoder 普通格式编码器
func NewJSONEncoder(cfg EncoderConfig) Encoder json格式编码器
type EncoderConfig
type EncoderConfig struct {
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKey string `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 换行符
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// 普通格式的分隔符
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Level
type Level int8
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel Level = iota - 1
// InfoLevel is the default logging priority.
InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel
)
func (l Level) CapitalString() string
func (l Level) Enabled(lvl Level) bool
func (l Level) Get() interface{}
func (l Level) Set(s string) error
func (l Level) String() string
type LevelEnabler
LevelEnabler决定在记录消息时是否启用给定的日志级别,他会给本身或更高的级别返回true
type LevelEnabler interface {
Enabled(Level) bool
}
type TimeEncoder
时间格式化器
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)
func TimeEncoderOfLayout(layout string) TimeEncoder 指定一个序列化格式(版本低的没这个方法)
type WriteSyncer
WriteSyncer是一个io。也可以刷新任何缓冲数据的写入器
*os.File (and thus, os.Stderr and os.Stdout) 已实现也这个接口
type WriteSyncer interface {
io.Writer
Sync() error
}
func AddSync(w io.Writer) WriteSyncer
func Lock(ws WriteSyncer) WriteSyncer Lock将WriteSyncer包装在互斥锁中,以确保并发使用时安全
func NewMultiWriteSyncer(ws …WriteSyncer) WriteSyncer
type Entry
entry表示一直完整的日志信息
type Entry struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
}
type EntryCaller
EntryCaller表示日志函数的调用者
type EntryCaller struct {
Defined bool
PC uintptr
File string
Line int
Function string
}
func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller
func (ec EntryCaller) FullPath() string
func (ec EntryCaller) String() string
func (ec EntryCaller) TrimmedPath() string
type Field
用于向记录器的上下文添加键-值对
type Field struct {
Key string
Type FieldType
Integer int64
String string
Interface interface{}
}
例子
按级输出
也可以参考logrus写两个log记录器
import (
"github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"strings"
"time"
)
var log *zap.Logger
func init() {
// 设置一些基本日志格式 具体含义还比较好理解,直接看zap源码也不难懂
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
CallerKey: "caller",
StacktraceKey: "stacktrace",
TimeKey: "ts",
LineEnding: zapcore.DefaultLineEnding, //换行符
EncodeLevel: zapcore.CapitalLevelEncoder, // 小写编码器
EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"),
EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器
EncodeName: zapcore.FullNameEncoder,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
},
})
// 实现两个判断日志等级的interface
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl <= zapcore.InfoLevel
})
errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
//infoLevel := zap.NewAtomicLevelAt(zapcore.InfoLevel)
//errorLevel := zap.NewAtomicLevelAt(zapcore.ErrorLevel)
// 获取 info、error日志文件的io.Writer 抽象 getWriter() 在下方实现
infoWriter := getWriter("./logs/demo_info.log")
errorWriter := getWriter("./logs/demo_error.log")
// 最后创建具体的Logger
core := zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
)
log = zap.New(core, zap.AddCaller())
}
func getWriter(filename string) io.Writer {
// 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH
// demo.log是指向最新日志的链接
// 保存7天内的日志,每1小时(整点)分割一次日志
hook, err := rotatelogs.New(
strings.Replace(filename, ".log", "", -1) + "-%Y%m%d%H.log", // 没有使用go风格反人类的format格式
//rotatelogs.WithLinkName(filename),
//rotatelogs.WithMaxAge(time.Hour*24*7),
//rotatelogs.WithRotationTime(time.Hour),
)
if err != nil {
panic(err)
}
return hook
}
func main() {
log.Info("1111111111")
log.Error("22222222222")
}
日志分割
type trace zap.LevelEnablerFunc
func (t trace) Enabled(zapcore.Level) bool {
return true
}
func main() {
hook := lumberjack.Logger{
Filename: "./logs/spikeProxy1.log", // 日志文件路径
MaxSize: 128, // 每个日志文件保存的最大尺寸 单位:M
MaxBackups: 30, // 日志文件最多保存多少个备份
MaxAge: 7, // 文件最多保存多少天
Compress: true, // 是否压缩
}
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器
EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式
EncodeDuration: zapcore.SecondsDurationEncoder, //
EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器
EncodeName: zapcore.FullNameEncoder,
}
// 设置日志级别
atomicLevel := zap.NewAtomicLevel()
atomicLevel.SetLevel(zap.InfoLevel)
core := zapcore.NewCore(
zapcore.NewJSONEncoder(encoderConfig), // 编码器配置
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
atomicLevel, // 日志级别
)
// 开启调用者信息
caller := zap.AddCaller()
var t trace
stacktrace := zap.AddStacktrace(t)
// 设置初始化字段
filed := zap.Fields(zap.String("serviceName", "serviceName"))
// 构造日志
logger := zap.New(core, caller, stacktrace, filed)
run(logger)
}
func run(logger *zap.Logger) {
logger.Info("log 初始化成功")
logger.Info("无法获取网址",
zap.String("url", "http://www.baidu.com"),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second))
}
文件如下
{"level":"info","time":"2020-10-28T12:10:21.458+0800","caller":"C:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:72","msg":"log 初始化成功","serviceName":"serviceName","stacktrace":"main.run\n\tC:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:72\nmain.main\n\tC:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:67\nruntime.main\n\tD:/Go1.14/src/runtime/proc.go:203"}
{"level":"info","time":"2020-10-28T12:10:21.517+0800","caller":"C:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:73","msg":"无法获取网址","serviceName":"serviceName","url":"http://www.baidu.com","attempt":3,"backoff":1,"stacktrace":"main.run\n\tC:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:73\nmain.main\n\tC:/Users/user/Desktop/GOProject/src/study/zap/demo1/demo4.go:67\nruntime.main\n\tD:/Go1.14/src/runtime/proc.go:203"}
重写callerEncoder加上requesID
以grpc为例
var (
option *Option
accessLog *zap.Logger
)
type Option struct {
Name string
Trace bool
Logger *logging.ZapLogger
}
func Init(opt *Option) error {
option = opt
if option.Logger == nil {
option.Logger = logging.ConfigLogger()
}
logging.ReplaceGrpcLoggerV2(option.Logger.Logger)
logging.ReplaceGormLogger(option.Logger.Logger)
if accessLog == nil {
accessLog = logging.NewAccessLogger(&config.LoggerConfig{}, opt.Name)
}
fmt.Printf("grpckit init %s trace %t\n", option.Name, option.Trace)
grpclog.Infof("grpckit init %s trace %t", option.Name, option.Trace)
loadDotenv()
return nil
}
func loadDotenv() {
_ = godotenv.Load(".env") // 项目根目录
_ = godotenv.Load("/etc/dotenv/dotenv") // k8s 挂载目录
}
func NewServer() *grpc.Server {
var interceptors []grpc.UnaryServerInterceptor
opts := []grpc_zap.Option{
grpc_zap.WithDecider(logging.Decider()),
grpc_zap.WithMessageProducer(logging.MessageProducer()),
}
interceptors = append(interceptors, grpc_zap.UnaryServerInterceptor(accessLog, opts...))
// alwaysLoggingDeciderServer := func(ctx context.Context, fullMethodName string, servingObject interface{}) bool {
// return !strings.Contains(fullMethodName, "ExecuteTask")
// }
// interceptors = append(interceptors, grpc_zap.PayloadUnaryServerInterceptor(accessLog, alwaysLoggingDeciderServer))
if option.Trace {
interceptors = append(
interceptors,
UnaryServerTraceInterceptor(),
)
}
maxMsgSize := 1024 * 1024 * 16
grpcServer := grpc.NewServer(
grpc.MaxRecvMsgSize(maxMsgSize),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
interceptors...,
)),
)
healthService := health.NewGrpcHealthChecker()
grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
return grpcServer
}
type ZapLogger struct {
Logger *zap.Logger
}
func NewLogger(conf *config.LoggerConfig) *ZapLogger {
return NewLoggerWithEncoder(conf, zapcore.ISO8601TimeEncoder, GrpcCallerEncoder)
}
func NewLoggerWithEncoder(conf *config.LoggerConfig, timeEncoder zapcore.TimeEncoder, callerEncoder zapcore.CallerEncoder) *ZapLogger {
encoderConf := zap.NewProductionEncoderConfig()
encoderConf.EncodeTime = timeEncoder
encoderConf.EncodeCaller = callerEncoder
encoder := zapcore.NewConsoleEncoder(encoderConf)
ws, level := Conf2LogArgs(conf)
return &ZapLogger{
Logger: zap.New(
zapcore.NewCore(encoder, ws, zap.NewAtomicLevelAt(level)),
zap.AddCaller(),
zap.AddCallerSkip(2),
),
}
}
func GrpcCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
if ctx, ok := GetContext(); ok {
value := fmt.Sprintf(" request-id: %s group-id: %s station-id: %s account-id: %s username: %s ", ctx.RequestId, ctx.GroupId, ctx.StationId, ctx.AccountId, ctx.Username)
enc.AppendString(value)
}
enc.AppendString(caller.TrimmedPath())
}
// 将 proto message 转化为对人友好的字符串
func formatMessage(msg interface{}) string {
if m, ok := msg.(proto.Message); ok {
return fmt.Sprintf("%v", protojson.MarshalOptions{}.Format(m))
}
return fmt.Sprintf("%v", msg)
}
func UnaryServerTraceInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
shortMethod := info.FullMethod[strings.LastIndex(info.FullMethod, ".")+1:]
md, _ := metadata.FromIncomingContext(ctx)
logging.SetContext(shortMethod, md)
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
msg := fmt.Sprintf("server panic %s %v\n%s", shortMethod, r, stack)
os.Stderr.Write([]byte(msg))
grpclog.Errorf(msg)
err = status.Errorf(codes.Internal, "server panic")
resp = nil
}
}()
startTime := time.Now()
resp, err = handler(ctx, req)
if err == nil {
//grpclog.Infof("server trace response method: %s response: %s", shortMethod, formatMessage(resp))
if info.FullMethod != "/grpc.health.v1.Health/Check" && !strings.Contains(info.FullMethod, "SysSync") && !strings.Contains(info.FullMethod, "UploadFile") {
grpclog.Infof("server trace method: %s elapsed: %v request: %v", shortMethod, time.Since(startTime), formatMessage(req))
}
} else {
grpclog.Infof("server trace method: %s metadata: %v request: %s response: %s error: %v", shortMethod, md, formatMessage(req), formatMessage(resp), err)
}
logging.DelContext()
return resp, err
}
}
func UnaryClientTraceInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
shortMethod := method[strings.LastIndex(method, ".")+1:]
startTime := time.Now()
err := invoker(ctx, method, req, resp, cc, opts...)
if err == nil {
if method != "/grpc.health.v1.Health/Check" && !strings.Contains(method, "SysSync") {
grpclog.Infof("client trace method: %s elapsed: %v", shortMethod, time.Since(startTime))
}
// grpclog.Infof("client trace response method: %s response: %s", shortMethod, formatMessage(resp))
// grpclog.Infof("client trace response method: %s", shortMethod)
} else {
grpclog.Infof("client trace method: %s request: %s response: %s error: %v", shortMethod, formatMessage(req), formatMessage(resp), err)
}
return err
}
}
func UnaryClientOutgoingContextInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
ctx = metadata.NewOutgoingContext(ctx, md)
}
err := invoker(ctx, method, req, resp, cc, opts...)
return err
}
}
var (
UserIdKey = "x-user-id"
ClientIdKey = "x-client-id"
RequestIdKey = "x-request-id"
GroupIdKey = "x-group-id"
StationIdKey = "x-station-id"
AccountIdKey = "x-account-id"
UsernameKey = "x-username"
Contexts sync.Map
)
type Context struct {
Method string
UserId string
ClientId string
RequestId string
GroupId string
StationId string
AccountId string
Username string
}
func SetContext(method string, md metadata.MD) {
ctx := &Context{
Method: method,
UserId: "-",
ClientId: "-",
RequestId: "-",
GroupId: "-",
StationId: "-",
AccountId: "-",
Username: "-",
}
ctx.Method = method
var values []string
values = md[UserIdKey]
if len(values) == 1 {
ctx.UserId = values[0]
}
values = md[ClientIdKey]
if len(values) == 1 {
ctx.ClientId = values[0]
}
values = md[RequestIdKey]
if len(values) == 1 {
ctx.RequestId = values[0]
}
values = md[GroupIdKey]
if len(values) == 1 {
ctx.GroupId = values[0]
}
values = md[StationIdKey]
if len(values) == 1 {
ctx.StationId = values[0]
}
values = md[AccountIdKey]
if len(values) == 1 {
ctx.AccountId = values[0]
}
values = md[UsernameKey]
if len(values) == 1 {
ctx.Username = values[0]
}
Contexts.Store(routine.Getgid(), ctx)
}
func GetContext() (*Context, bool) {
if v, ok := Contexts.Load(routine.Getgid()); ok {
if ctx, ok := v.(*Context); ok {
return ctx, true
}
}
return nil, false
}
func DelContext() {
Contexts.Delete(routine.Getgid())
}
func Getgid() int64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
i, _ := strconv.ParseInt(string(b), 10, 64)
return i
}
需要注意的是,实现了proto的message接口的,在用protoc生成代码时,都会有一个String方法,能方便的打印结构体(嵌套,指针)