golang log日志库优化

与原生log对比

基于反射的序列化和字符串格式昂贵得令人望而却步——它们是CPU密集型的,并进行许多小的分配。换句话说,使用encoding/json和fmt.Fprintf来记录大量的interface{}s会使您的应用程序变慢。

Zap采取了不同的方法。它包括一个无反射的零分配JSON编码器,基本Logger努力尽可能避免序列化开销和分配。通过在此基础上构建高级SugaredLogger,zap允许用户选择何时需要计算每个分配,以及何时他们更喜欢更熟悉、类型松散的API。

正如其自身的基准套件所衡量的那样,zap不仅比可比的结构化日志包性能更高,而且比标准库更快。像所有基准一样,对它们持保留态度。

用法

在性能良好但不关键的情况下,请使用SugaredLogger。它比其他结构化日志包快4-10倍,包括结构化和printf风格的API。

logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  // Structured context as loosely typed key-value pairs.
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

当性能和类型安全至关重要时,请使用Logger。它甚至比SugaredLogger更快,分配也少得多,但它只支持结构化日志记录。

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // Structured context as strongly typed Field values.
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

zap配置

config

type Config struct {
	// 级别是启用的最低日志记录级别。注意,这是一个动态级别,因此调用Config.level.SetLevel将自动更改日志,所有记录器的级别都从该配置下降。
	Level AtomicLevel `json:"level" yaml:"level"`
	// 开发使记录器处于开发模式,DPanicLevel的行为,并更自由地进行堆叠。
	Development bool `json:"development" yaml:"development"`
	//停止使用调用函数的文件注释日志,名称和行号。默认情况下,所有日志都有注释。
	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
	// DisableStacktrace完全禁用自动堆栈跟踪捕获。通过,默认情况下,为WarnLevel及更高级别的日志捕获堆栈,开发和ErrorLevel及以上。
	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
	// 采样设置采样策略。nil SamplingConfig禁用采样。
	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
	// 编码设置记录器的编码。有效值为“json”和,“控制台”,以及通过注册的任何第三方编码,寄存器编码器。
	Encoding string `json:"encoding" yaml:"encoding"`
	// EncoderConfig为所选编码器设置选项。
	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
	// OutputPaths是要将日志输出写入的URL或文件路径的列表。
	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
	// ErrorOutputPaths是将内部记录器错误写入的URL列表。默认值为标准错误。请注意,此设置仅影响内部错误;
	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
	// InitialFields是要添加到根记录器的字段集合。
	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

func NewProductionConfig() Config {
	return Config{
		Level:       NewAtomicLevelAt(InfoLevel),    // 日志级别
		Development: false,    // 开发模式,堆栈跟踪
		Sampling: &SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},
		Encoding:         "json",    // 输出格式 console 或 json
		EncoderConfig:    NewProductionEncoderConfig(),    // 编码器配置
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}

// 编码器配置
func NewProductionEncoderConfig() zapcore.EncoderConfig {
	return zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,    // 小写编码器
		EncodeTime:     zapcore.EpochTimeEncoder,    // ISO8601 UTC 时间格式
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,    // 全路径编码器
	}
}

zap日志输出,确定类型的输出确实很快,那么不确定类型会怎么处理呢?自定义类型呢?
源码的任意类型方案, switch case:

func Any(key string, value interface{}) Field {
    switch val := value.(type) {
	case zapcore.ObjectMarshaler:
		return Object(key, val)
	case zapcore.ArrayMarshaler:
		return Array(key, val)
	case bool:
		return Bool(key, val)
	case *bool:
		return Boolp(key, val)
	case []bool:
		return Bools(key, val)
	case complex128:
		return Complex128(key, val)
	case *complex128:
		return Complex128p(key, val)
	case []complex128:
		return Complex128s(key, val)
	case complex64:
		return Complex64(key, val)
	case *complex64:
		return Complex64p(key, val)
	case []complex64:
		return Complex64s(key, val)
	case float64:
		return Float64(key, val)
	case *float64:
		return Float64p(key, val)
	case []float64:
		return Float64s(key, val)
	case float32:
		return Float32(key, val)
	case *float32:
		return Float32p(key, val)
	case []float32:
		return Float32s(key, val)
	case int:
		return Int(key, val)
	case *int:
		return Intp(key, val)
	case []int:
		return Ints(key, val)
	case int64:
		return Int64(key, val)
	case *int64:
		return Int64p(key, val)
	case []int64:
		return Int64s(key, val)
	case int32:
		return Int32(key, val)
	case *int32:
		return Int32p(key, val)
	case []int32:
		return Int32s(key, val)
	case int16:
		return Int16(key, val)
	case *int16:
		return Int16p(key, val)
	case []int16:
		return Int16s(key, val)
	case int8:
		return Int8(key, val)
	case *int8:
		return Int8p(key, val)
	case []int8:
		return Int8s(key, val)
	case string:
		return String(key, val)
	case *string:
		return Stringp(key, val)
	case []string:
		return Strings(key, val)
	case uint:
		return Uint(key, val)
	case *uint:
		return Uintp(key, val)
	case []uint:
		return Uints(key, val)
	case uint64:
		return Uint64(key, val)
	case *uint64:
		return Uint64p(key, val)
	case []uint64:
		return Uint64s(key, val)
	case uint32:
		return Uint32(key, val)
	case *uint32:
		return Uint32p(key, val)
	case []uint32:
		return Uint32s(key, val)
	case uint16:
		return Uint16(key, val)
	case *uint16:
		return Uint16p(key, val)
	case []uint16:
		return Uint16s(key, val)
	case uint8:
		return Uint8(key, val)
	case *uint8:
		return Uint8p(key, val)
	case []byte:
		return Binary(key, val)
	case uintptr:
		return Uintptr(key, val)
	case *uintptr:
		return Uintptrp(key, val)
	case []uintptr:
		return Uintptrs(key, val)
	case time.Time:
		return Time(key, val)
	case *time.Time:
		return Timep(key, val)
	case []time.Time:
		return Times(key, val)
	case time.Duration:
		return Duration(key, val)
	case *time.Duration:
		return Durationp(key, val)
	case []time.Duration:
		return Durations(key, val)
	case error:
		return NamedError(key, val)
	case []error:
		return Errors(key, val)
	case fmt.Stringer:
		return Stringer(key, val)
	default:
		return Reflect(key, val)
	}
}

在zapcore/field.go中有个接口:type ObjectEncoder interface {}

type ObjectEncoder interface {
    // Logging-specific marshalers.
    AddArray(key string, marshaler ArrayMarshaler) error
    AddObject(key string, marshaler ObjectMarshaler) error
    ***
}

实现提供的接口,加载自定义结构
ObjectMarshaller, ArrayMarshaler

type ObjectMarshaler interface {
	MarshalLogObject(ObjectEncoder) error
}

type ArrayMarshaler interface {
	MarshalLogArray(ArrayEncoder) error
}

demo

type User struct {
    Name      string
    Email     string
    CreatedAt time.Time
}

func (u *User) MarshalLogObject(enc zapcore.ObjectEncoder) error {
    enc.AddString("name", u.Name)
    enc.AddString("email", u.Email)
    enc.AddInt64("created_at", u.CreatedAt.UnixNano())
    return nil
}

type Users []*User

func (uu Users) MarshalLogArray(arr zapcore.ArrayEncoder) error {
    var err error
    for i := range uu {
        err = multierr.Append(err, arr.AppendObject(uu[i]))
    }
    return err
}

func marshalerDemo() {
    logger, err := zap.NewProduction()
    defer logger.Sync()
    if err != nil {
        panic(err)
    }
    var user = &User{
        Name:      "hello1",
        Email:     "[email protected]",
        CreatedAt: time.Date(2020, 12, 19, 8, 0, 0, 0, time.UTC),
    }
    var users Users
    users = append(users, &User{
        Name:      "hello2",
        Email:     "[email protected]",
        CreatedAt: time.Date(2020, 12, 19, 9, 0, 0, 0, time.UTC),
    }, &User{
        Name:      "hello3",
        Email:     "[email protected]",
        CreatedAt: time.Date(2020, 12, 20, 10, 0, 0, 0, time.UTC),
    })
    logger.Info("marshaler", zap.Object("user", user))
    logger.Info("marshaler", zap.Array("users", users))
}

自定义结构体参考博客

异步日志

相关实现博客
目前zap原生不提供异步输出日志

源码同步写:

type WriteSyncer interface {
	io.Writer
	Sync() error
}
type lockedWriteSyncer struct {
	sync.Mutex
	ws WriteSyncer
}

type multiWriteSyncer []WriteSyncer

func (ws multiWriteSyncer) Write(p []byte) (int, error) {
	var writeErr error
	nWritten := 0
	for _, w := range ws {
		n, err := w.Write(p)
		writeErr = multierr.Append(writeErr, err)
		if nWritten == 0 && n != 0 {
			nWritten = n
		} else if n < nWritten {
			nWritten = n
		}
	}
	return nWritten, writeErr
}

添加计数型日志

package test

import(
        "time"
        "testing"
        "code/log"
        "code/timer"
        "go.uber.org/zap"
        "go.uber.org/zap/zapcore"
)

func TestMain(m *testing.M){
        log.Init("maomi")

        f := &Frame{}

        t1 := timer.NewTicker(2 * time.Second, func() {
                log.Info("record", zap.Object("frame", f))
        })

        for i := 0; i< 100; i++ {
                f.Frame_0 = f.Frame_0 + 1
                f.Frame_3 = f.Frame_3 + 1
                time.Sleep(time.Second/2)
        }

        time.Sleep(10 * time.Second)
        t1.Stop()
}

type Frame struct{
        Frame_0 int32
        Frame_1 int32
        Frame_2 int32
        Frame_3 int32
}

func (f *Frame) MarshalLogObject(enc zapcore.ObjectEncoder) error {
    enc.AddInt32("frame_0", f.Frame_0)
    enc.AddInt32("frame_1", f.Frame_1)
    enc.AddInt32("frame_2", f.Frame_2)
    enc.AddInt32("frame_3", f.Frame_3)
    return nil
}

output:

{"level":"info","time":"2023-02-13T22:49:08.743+0800","msg":"record","servername":"maomi","caller":"/go/src/gin/test/log_test.go:50","frame":{"frame_0":4,"frame_1":0,"frame_2":0,"frame_3":4}}
{"level":"info","time":"2023-02-13T22:49:10.743+0800","msg":"record","servername":"maomi","caller":"/go/src/gin/test/log_test.go:50","frame":{"frame_0":8,"frame_1":0,"frame_2":0,"frame_3":8}}

猜你喜欢

转载自blog.csdn.net/weixin_56766616/article/details/129955893
今日推荐