分析过程

使用 pprof top分析

可见 json.Marshal占第一内存. 为什么呢? 我们进一步分析

使用 tree 分析

image-20200701190848060

查看到 zerolog AppendInterface 方法占用 73.32%的内存量. 而 zerolog 是一个很优秀的日志库, 比 zap 还优秀. 为什么呢?我们需要查看源码

822.70MB 73.32% |   github.com/rs/zerolog/internal/json.Encoder.AppendInterface

分析源码

找到 github.com/rs/zerolog/internal/json.Encoder.AppendInterface` 366 行

// AppendInterface marshals the input interface to a string and
// appends the encoded string to the input byte slice.
func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
	marshaled, err := json.Marshal(i)
	if err != nil {
		return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err))
	}
	return append(dst, marshaled...)
}

4行 marshaled, err := json.Marshal(i) 使用了原生的 json 函数, 这是导致占用内存最大祸首.

为什么这样呢?

我们再查看github.com\rs\zerolog/array.go 213行, 有调用 AppendInterface 函数.

// Interface append append i marshaled using reflection.
func (a *Array) Interface(i interface{}) *Array {
	if obj, ok := i.(LogObjectMarshaler); ok {
		return a.Object(obj)
	}
	a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), i)
	return a
}

分析到这里不难发现. 是因为程序里大量使用 interface 函数.

如: 只要使用啦 Interface 函数最终是以 json.Marshal 落盘.这样是很糟糕的.

zlog.Info().Interface("act", "aa").Send()

结论

使用zerolog打印日志时,减少使用interface打印. 否则会占用大量内存.

我们可以使用选择具体的类型,还可以使用Dict

log.Info().
    Str("foo", "bar").
    Dict("dict", zerolog.Dict().
        Str("bar", "baz").
        Int("n", 1),
    ).Msg("hello world")

官方也提供 一个接口解决打印复杂结构的方案

// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface
// to be implemented by types used with Event/Context's Object methods.
type LogObjectMarshaler interface {
	MarshalZerologObject(e *Event)
}

LogObjectMarshaler 接口使用

打印复杂数据的解决方案

// 需要打印的结构体
type User struct {
	Name    string
	Age     int
	Created time.Time
}
// 继承 zerolog LogObjectMarshaler 接口
func (u User) MarshalZerologObject(e *zerolog.Event) {
	e.Str("name", u.Name).
		Int("age", u.Age).
		Time("created", u.Created)
}

func ExampleEvent_Object() {
	log := zerolog.New(os.Stdout)

	// User implements zerolog.LogObjectMarshaler
	u := User{"John", 35, time.Time{}}

	log.Log().
		Str("foo", "bar").
		Object("user", u). // 使用Object 搞定, 或使用 EmbedObject(u).
		Msg("hello world")

	// Output: {"foo":"bar","user":{"name":"John","age":35,"created":"0001-01-01T00:00:00Z"},"message":"hello world"}
}

参考官方实例: github.com\rs\zerolog\log_example_test.go