文档: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中

  1. type Core interface {
  2. LevelEnabler
  3. With([]Field) Core
  4. Check(Entry, *CheckedEntry) *CheckedEntry
  5. Write(Entry, []Field) error
  6. Sync() error
  7. }

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

  1. type EncoderConfig struct {
  2. MessageKey string `json:"messageKey" yaml:"messageKey"`
  3. LevelKey string `json:"levelKey" yaml:"levelKey"`
  4. TimeKey string `json:"timeKey" yaml:"timeKey"`
  5. NameKey string `json:"nameKey" yaml:"nameKey"`
  6. CallerKey string `json:"callerKey" yaml:"callerKey"`
  7. FunctionKey string `json:"functionKey" yaml:"functionKey"`
  8. StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
  9. LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 换行符
  10. EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
  11. EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
  12. EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
  13. EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
  14. EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
  15. // 普通格式的分隔符
  16. ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
  17. }

type Level

  1. type Level int8
  2. const (
  3. // DebugLevel logs are typically voluminous, and are usually disabled in
  4. // production.
  5. DebugLevel Level = iota - 1
  6. // InfoLevel is the default logging priority.
  7. InfoLevel
  8. // WarnLevel logs are more important than Info, but don't need individual
  9. // human review.
  10. WarnLevel
  11. // ErrorLevel logs are high-priority. If an application is running smoothly,
  12. // it shouldn't generate any error-level logs.
  13. ErrorLevel
  14. // DPanicLevel logs are particularly important errors. In development the
  15. // logger panics after writing the message.
  16. DPanicLevel
  17. // PanicLevel logs a message, then panics.
  18. PanicLevel
  19. // FatalLevel logs a message, then calls os.Exit(1).
  20. FatalLevel
  21. )

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

  1. type LevelEnabler interface {
  2. Enabled(Level) bool
  3. }

type TimeEncoder

时间格式化器

  1. type TimeEncoder func(time.Time, PrimitiveArrayEncoder)

func TimeEncoderOfLayout(layout string) TimeEncoder 指定一个序列化格式(版本低的没这个方法)

type WriteSyncer

WriteSyncer是一个io。也可以刷新任何缓冲数据的写入器
*os.File (and thus, os.Stderr and os.Stdout) 已实现也这个接口

  1. type WriteSyncer interface {
  2. io.Writer
  3. Sync() error
  4. }

func AddSync(w io.Writer) WriteSyncer
func Lock(ws WriteSyncer) WriteSyncer Lock将WriteSyncer包装在互斥锁中,以确保并发使用时安全
func NewMultiWriteSyncer(ws …WriteSyncer) WriteSyncer

type Entry

entry表示一直完整的日志信息

  1. type Entry struct {
  2. Level Level
  3. Time time.Time
  4. LoggerName string
  5. Message string
  6. Caller EntryCaller
  7. Stack string
  8. }

type EntryCaller

EntryCaller表示日志函数的调用者

  1. type EntryCaller struct {
  2. Defined bool
  3. PC uintptr
  4. File string
  5. Line int
  6. Function string
  7. }
  8. func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller
  9. func (ec EntryCaller) FullPath() string
  10. func (ec EntryCaller) String() string
  11. func (ec EntryCaller) TrimmedPath() string

type Field

用于向记录器的上下文添加键-值对

  1. type Field struct {
  2. Key string
  3. Type FieldType
  4. Integer int64
  5. String string
  6. Interface interface{}
  7. }

例子

按级输出

也可以参考logrus写两个log记录器

  1. import (
  2. "github.com/lestrrat-go/file-rotatelogs"
  3. "go.uber.org/zap"
  4. "go.uber.org/zap/zapcore"
  5. "io"
  6. "strings"
  7. "time"
  8. )
  9. var log *zap.Logger
  10. func init() {
  11. // 设置一些基本日志格式 具体含义还比较好理解,直接看zap源码也不难懂
  12. encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
  13. MessageKey: "msg",
  14. LevelKey: "level",
  15. CallerKey: "caller",
  16. StacktraceKey: "stacktrace",
  17. TimeKey: "ts",
  18. LineEnding: zapcore.DefaultLineEnding, //换行符
  19. EncodeLevel: zapcore.CapitalLevelEncoder, // 小写编码器
  20. EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"),
  21. EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器
  22. EncodeName: zapcore.FullNameEncoder,
  23. EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
  24. enc.AppendInt64(int64(d) / 1000000)
  25. },
  26. })
  27. // 实现两个判断日志等级的interface
  28. infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  29. return lvl <= zapcore.InfoLevel
  30. })
  31. errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  32. return lvl >= zapcore.ErrorLevel
  33. })
  34. //infoLevel := zap.NewAtomicLevelAt(zapcore.InfoLevel)
  35. //errorLevel := zap.NewAtomicLevelAt(zapcore.ErrorLevel)
  36. // 获取 info、error日志文件的io.Writer 抽象 getWriter() 在下方实现
  37. infoWriter := getWriter("./logs/demo_info.log")
  38. errorWriter := getWriter("./logs/demo_error.log")
  39. // 最后创建具体的Logger
  40. core := zapcore.NewTee(
  41. zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
  42. zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
  43. )
  44. log = zap.New(core, zap.AddCaller())
  45. }
  46. func getWriter(filename string) io.Writer {
  47. // 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH
  48. // demo.log是指向最新日志的链接
  49. // 保存7天内的日志,每1小时(整点)分割一次日志
  50. hook, err := rotatelogs.New(
  51. strings.Replace(filename, ".log", "", -1) + "-%Y%m%d%H.log", // 没有使用go风格反人类的format格式
  52. //rotatelogs.WithLinkName(filename),
  53. //rotatelogs.WithMaxAge(time.Hour*24*7),
  54. //rotatelogs.WithRotationTime(time.Hour),
  55. )
  56. if err != nil {
  57. panic(err)
  58. }
  59. return hook
  60. }
  61. func main() {
  62. log.Info("1111111111")
  63. log.Error("22222222222")
  64. }

日志分割

  1. type trace zap.LevelEnablerFunc
  2. func (t trace) Enabled(zapcore.Level) bool {
  3. return true
  4. }
  5. func main() {
  6. hook := lumberjack.Logger{
  7. Filename: "./logs/spikeProxy1.log", // 日志文件路径
  8. MaxSize: 128, // 每个日志文件保存的最大尺寸 单位:M
  9. MaxBackups: 30, // 日志文件最多保存多少个备份
  10. MaxAge: 7, // 文件最多保存多少天
  11. Compress: true, // 是否压缩
  12. }
  13. encoderConfig := zapcore.EncoderConfig{
  14. TimeKey: "time",
  15. LevelKey: "level",
  16. NameKey: "logger",
  17. CallerKey: "caller",
  18. MessageKey: "msg",
  19. StacktraceKey: "stacktrace",
  20. LineEnding: zapcore.DefaultLineEnding,
  21. EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器
  22. EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式
  23. EncodeDuration: zapcore.SecondsDurationEncoder, //
  24. EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器
  25. EncodeName: zapcore.FullNameEncoder,
  26. }
  27. // 设置日志级别
  28. atomicLevel := zap.NewAtomicLevel()
  29. atomicLevel.SetLevel(zap.InfoLevel)
  30. core := zapcore.NewCore(
  31. zapcore.NewJSONEncoder(encoderConfig), // 编码器配置
  32. zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
  33. atomicLevel, // 日志级别
  34. )
  35. // 开启调用者信息
  36. caller := zap.AddCaller()
  37. var t trace
  38. stacktrace := zap.AddStacktrace(t)
  39. // 设置初始化字段
  40. filed := zap.Fields(zap.String("serviceName", "serviceName"))
  41. // 构造日志
  42. logger := zap.New(core, caller, stacktrace, filed)
  43. run(logger)
  44. }
  45. func run(logger *zap.Logger) {
  46. logger.Info("log 初始化成功")
  47. logger.Info("无法获取网址",
  48. zap.String("url", "http://www.baidu.com"),
  49. zap.Int("attempt", 3),
  50. zap.Duration("backoff", time.Second))
  51. }

image.png
文件如下

  1. {"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"}
  2. {"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为例

  1. var (
  2. option *Option
  3. accessLog *zap.Logger
  4. )
  5. type Option struct {
  6. Name string
  7. Trace bool
  8. Logger *logging.ZapLogger
  9. }
  10. func Init(opt *Option) error {
  11. option = opt
  12. if option.Logger == nil {
  13. option.Logger = logging.ConfigLogger()
  14. }
  15. logging.ReplaceGrpcLoggerV2(option.Logger.Logger)
  16. logging.ReplaceGormLogger(option.Logger.Logger)
  17. if accessLog == nil {
  18. accessLog = logging.NewAccessLogger(&config.LoggerConfig{}, opt.Name)
  19. }
  20. fmt.Printf("grpckit init %s trace %t\n", option.Name, option.Trace)
  21. grpclog.Infof("grpckit init %s trace %t", option.Name, option.Trace)
  22. loadDotenv()
  23. return nil
  24. }
  25. func loadDotenv() {
  26. _ = godotenv.Load(".env") // 项目根目录
  27. _ = godotenv.Load("/etc/dotenv/dotenv") // k8s 挂载目录
  28. }
  29. func NewServer() *grpc.Server {
  30. var interceptors []grpc.UnaryServerInterceptor
  31. opts := []grpc_zap.Option{
  32. grpc_zap.WithDecider(logging.Decider()),
  33. grpc_zap.WithMessageProducer(logging.MessageProducer()),
  34. }
  35. interceptors = append(interceptors, grpc_zap.UnaryServerInterceptor(accessLog, opts...))
  36. // alwaysLoggingDeciderServer := func(ctx context.Context, fullMethodName string, servingObject interface{}) bool {
  37. // return !strings.Contains(fullMethodName, "ExecuteTask")
  38. // }
  39. // interceptors = append(interceptors, grpc_zap.PayloadUnaryServerInterceptor(accessLog, alwaysLoggingDeciderServer))
  40. if option.Trace {
  41. interceptors = append(
  42. interceptors,
  43. UnaryServerTraceInterceptor(),
  44. )
  45. }
  46. maxMsgSize := 1024 * 1024 * 16
  47. grpcServer := grpc.NewServer(
  48. grpc.MaxRecvMsgSize(maxMsgSize),
  49. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  50. interceptors...,
  51. )),
  52. )
  53. healthService := health.NewGrpcHealthChecker()
  54. grpc_health_v1.RegisterHealthServer(grpcServer, healthService)
  55. return grpcServer
  56. }
  1. type ZapLogger struct {
  2. Logger *zap.Logger
  3. }
  4. func NewLogger(conf *config.LoggerConfig) *ZapLogger {
  5. return NewLoggerWithEncoder(conf, zapcore.ISO8601TimeEncoder, GrpcCallerEncoder)
  6. }
  7. func NewLoggerWithEncoder(conf *config.LoggerConfig, timeEncoder zapcore.TimeEncoder, callerEncoder zapcore.CallerEncoder) *ZapLogger {
  8. encoderConf := zap.NewProductionEncoderConfig()
  9. encoderConf.EncodeTime = timeEncoder
  10. encoderConf.EncodeCaller = callerEncoder
  11. encoder := zapcore.NewConsoleEncoder(encoderConf)
  12. ws, level := Conf2LogArgs(conf)
  13. return &ZapLogger{
  14. Logger: zap.New(
  15. zapcore.NewCore(encoder, ws, zap.NewAtomicLevelAt(level)),
  16. zap.AddCaller(),
  17. zap.AddCallerSkip(2),
  18. ),
  19. }
  20. }
  21. func GrpcCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
  22. if ctx, ok := GetContext(); ok {
  23. 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)
  24. enc.AppendString(value)
  25. }
  26. enc.AppendString(caller.TrimmedPath())
  27. }
  1. // 将 proto message 转化为对人友好的字符串
  2. func formatMessage(msg interface{}) string {
  3. if m, ok := msg.(proto.Message); ok {
  4. return fmt.Sprintf("%v", protojson.MarshalOptions{}.Format(m))
  5. }
  6. return fmt.Sprintf("%v", msg)
  7. }
  8. func UnaryServerTraceInterceptor() grpc.UnaryServerInterceptor {
  9. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  10. shortMethod := info.FullMethod[strings.LastIndex(info.FullMethod, ".")+1:]
  11. md, _ := metadata.FromIncomingContext(ctx)
  12. logging.SetContext(shortMethod, md)
  13. defer func() {
  14. if r := recover(); r != nil {
  15. stack := debug.Stack()
  16. msg := fmt.Sprintf("server panic %s %v\n%s", shortMethod, r, stack)
  17. os.Stderr.Write([]byte(msg))
  18. grpclog.Errorf(msg)
  19. err = status.Errorf(codes.Internal, "server panic")
  20. resp = nil
  21. }
  22. }()
  23. startTime := time.Now()
  24. resp, err = handler(ctx, req)
  25. if err == nil {
  26. //grpclog.Infof("server trace response method: %s response: %s", shortMethod, formatMessage(resp))
  27. if info.FullMethod != "/grpc.health.v1.Health/Check" && !strings.Contains(info.FullMethod, "SysSync") && !strings.Contains(info.FullMethod, "UploadFile") {
  28. grpclog.Infof("server trace method: %s elapsed: %v request: %v", shortMethod, time.Since(startTime), formatMessage(req))
  29. }
  30. } else {
  31. grpclog.Infof("server trace method: %s metadata: %v request: %s response: %s error: %v", shortMethod, md, formatMessage(req), formatMessage(resp), err)
  32. }
  33. logging.DelContext()
  34. return resp, err
  35. }
  36. }
  37. func UnaryClientTraceInterceptor() grpc.UnaryClientInterceptor {
  38. return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  39. shortMethod := method[strings.LastIndex(method, ".")+1:]
  40. startTime := time.Now()
  41. err := invoker(ctx, method, req, resp, cc, opts...)
  42. if err == nil {
  43. if method != "/grpc.health.v1.Health/Check" && !strings.Contains(method, "SysSync") {
  44. grpclog.Infof("client trace method: %s elapsed: %v", shortMethod, time.Since(startTime))
  45. }
  46. // grpclog.Infof("client trace response method: %s response: %s", shortMethod, formatMessage(resp))
  47. // grpclog.Infof("client trace response method: %s", shortMethod)
  48. } else {
  49. grpclog.Infof("client trace method: %s request: %s response: %s error: %v", shortMethod, formatMessage(req), formatMessage(resp), err)
  50. }
  51. return err
  52. }
  53. }
  54. func UnaryClientOutgoingContextInterceptor() grpc.UnaryClientInterceptor {
  55. return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  56. md, ok := metadata.FromIncomingContext(ctx)
  57. if ok {
  58. ctx = metadata.NewOutgoingContext(ctx, md)
  59. }
  60. err := invoker(ctx, method, req, resp, cc, opts...)
  61. return err
  62. }
  63. }
  1. var (
  2. UserIdKey = "x-user-id"
  3. ClientIdKey = "x-client-id"
  4. RequestIdKey = "x-request-id"
  5. GroupIdKey = "x-group-id"
  6. StationIdKey = "x-station-id"
  7. AccountIdKey = "x-account-id"
  8. UsernameKey = "x-username"
  9. Contexts sync.Map
  10. )
  11. type Context struct {
  12. Method string
  13. UserId string
  14. ClientId string
  15. RequestId string
  16. GroupId string
  17. StationId string
  18. AccountId string
  19. Username string
  20. }
  21. func SetContext(method string, md metadata.MD) {
  22. ctx := &Context{
  23. Method: method,
  24. UserId: "-",
  25. ClientId: "-",
  26. RequestId: "-",
  27. GroupId: "-",
  28. StationId: "-",
  29. AccountId: "-",
  30. Username: "-",
  31. }
  32. ctx.Method = method
  33. var values []string
  34. values = md[UserIdKey]
  35. if len(values) == 1 {
  36. ctx.UserId = values[0]
  37. }
  38. values = md[ClientIdKey]
  39. if len(values) == 1 {
  40. ctx.ClientId = values[0]
  41. }
  42. values = md[RequestIdKey]
  43. if len(values) == 1 {
  44. ctx.RequestId = values[0]
  45. }
  46. values = md[GroupIdKey]
  47. if len(values) == 1 {
  48. ctx.GroupId = values[0]
  49. }
  50. values = md[StationIdKey]
  51. if len(values) == 1 {
  52. ctx.StationId = values[0]
  53. }
  54. values = md[AccountIdKey]
  55. if len(values) == 1 {
  56. ctx.AccountId = values[0]
  57. }
  58. values = md[UsernameKey]
  59. if len(values) == 1 {
  60. ctx.Username = values[0]
  61. }
  62. Contexts.Store(routine.Getgid(), ctx)
  63. }
  64. func GetContext() (*Context, bool) {
  65. if v, ok := Contexts.Load(routine.Getgid()); ok {
  66. if ctx, ok := v.(*Context); ok {
  67. return ctx, true
  68. }
  69. }
  70. return nil, false
  71. }
  72. func DelContext() {
  73. Contexts.Delete(routine.Getgid())
  74. }
  75. func Getgid() int64 {
  76. b := make([]byte, 64)
  77. b = b[:runtime.Stack(b, false)]
  78. b = bytes.TrimPrefix(b, []byte("goroutine "))
  79. b = b[:bytes.IndexByte(b, ' ')]
  80. i, _ := strconv.ParseInt(string(b), 10, 64)
  81. return i
  82. }

需要注意的是,实现了proto的message接口的,在用protoc生成代码时,都会有一个String方法,能方便的打印结构体(嵌套,指针)