Golang log log library optimization

Compared with native log

Reflection-based serialization and string formatting are prohibitively expensive - they are CPU intensive and make many small allocations. In other words, using encoding/json and fmt.Fprintf to log lots of interface{}s will slow down your application.

Zap takes a different approach. It includes a reflection-free, zero-allocation JSON encoder, and the base logger strives to avoid serialization overhead and allocations as much as possible. By building an advanced SugaredLogger on top of this, zap allows users to choose when each allocation needs to be computed, and when they prefer a more familiar, loosely typed API.

As measured by its own benchmark suite, zap is not only more performant than comparable structured logging packages, but also faster than the standard library. Like all benchmarks, take them with a grain of salt.

usage

In cases where performance is good but not critical, use SugaredLogger. It is 4-10 times faster than other structured logging packages and includes both structured and printf-style APIs.

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)

Use Loggers when performance and type safety are critical. It's even faster than SugaredLogger and allocates much less, but it only supports structured logging.

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 configuration

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 log output, the output of the determined type is really fast, so how will the uncertain type be handled? What about custom types?
Any type of source code solution, 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)
	}
}

There is an interface in zapcore/field.go: type ObjectEncoder interface {}

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

Implement the provided interface and load the custom structure
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))
}

Custom Structure Reference Blog

asynchronous log

Related implementation blog
Currently zap does not provide asynchronous output log natively

Write source code synchronously:

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
}

Add count log

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}}

Guess you like

Origin blog.csdn.net/weixin_56766616/article/details/129955893