序
本文次要钻研一下 golang 的 zap 的 error
error
zap@v1.16.0/error.go
var _errArrayElemPool = sync.Pool{New: func() interface{} {return &errArrayElem{}
}}
// Error is shorthand for the common idiom NamedError("error", err).
func Error(err error) Field {return NamedError("error", err)
}
// NamedError constructs a field that lazily stores err.Error() under the
// provided key. Errors which also implement fmt.Formatter (like those produced
// by github.com/pkg/errors) will also have their verbose representation stored
// under key+"Verbose". If passed a nil error, the field is a no-op.
//
// For the common case in which the key is simply "error", the Error function
// is shorter and less repetitive.
func NamedError(key string, err error) Field {
if err == nil {return Skip()
}
return Field{Key: key, Type: zapcore.ErrorType, Interface: err}
}
type errArray []error
func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
for i := range errs {if errs[i] == nil {continue}
// To represent each error as an object with an "error" attribute and
// potentially an "errorVerbose" attribute, we need to wrap it in a
// type that implements LogObjectMarshaler. To prevent this from
// allocating, pool the wrapper type.
elem := _errArrayElemPool.Get().(*errArrayElem)
elem.error = errs[i]
arr.AppendObject(elem)
elem.error = nil
_errArrayElemPool.Put(elem)
}
return nil
}
type errArrayElem struct {error}
func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error {
// Re-use the error field's logic, which supports non-standard error types.
Error(e.error).AddTo(enc)
return nil
}
Error 办法应用 NamedError 创立 err 的 Field;NamedError 创立的 fieldType 为 zapcore.ErrorType;errArray 类型实现了 ArrayMarshaler 的 MarshalLogArray 办法;errArrayElem 实现了 ObjectMarshaler 的 MarshalLogObject 办法;error.go 定义了_errArrayElemPool,其 pool 的元素类型为 errArrayElem
AddTo
zap@v1.16.0/zapcore/field.go
func (f Field) AddTo(enc ObjectEncoder) {
var err error
switch f.Type {
case ArrayMarshalerType:
err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler))
case ObjectMarshalerType:
err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler))
case BinaryType:
enc.AddBinary(f.Key, f.Interface.([]byte))
case BoolType:
enc.AddBool(f.Key, f.Integer == 1)
case ByteStringType:
enc.AddByteString(f.Key, f.Interface.([]byte))
case Complex128Type:
enc.AddComplex128(f.Key, f.Interface.(complex128))
case Complex64Type:
enc.AddComplex64(f.Key, f.Interface.(complex64))
case DurationType:
enc.AddDuration(f.Key, time.Duration(f.Integer))
case Float64Type:
enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer)))
case Float32Type:
enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer)))
case Int64Type:
enc.AddInt64(f.Key, f.Integer)
case Int32Type:
enc.AddInt32(f.Key, int32(f.Integer))
case Int16Type:
enc.AddInt16(f.Key, int16(f.Integer))
case Int8Type:
enc.AddInt8(f.Key, int8(f.Integer))
case StringType:
enc.AddString(f.Key, f.String)
case TimeType:
if f.Interface != nil {enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location)))
} else {
// Fall back to UTC if location is nil.
enc.AddTime(f.Key, time.Unix(0, f.Integer))
}
case TimeFullType:
enc.AddTime(f.Key, f.Interface.(time.Time))
case Uint64Type:
enc.AddUint64(f.Key, uint64(f.Integer))
case Uint32Type:
enc.AddUint32(f.Key, uint32(f.Integer))
case Uint16Type:
enc.AddUint16(f.Key, uint16(f.Integer))
case Uint8Type:
enc.AddUint8(f.Key, uint8(f.Integer))
case UintptrType:
enc.AddUintptr(f.Key, uintptr(f.Integer))
case ReflectType:
err = enc.AddReflected(f.Key, f.Interface)
case NamespaceType:
enc.OpenNamespace(f.Key)
case StringerType:
err = encodeStringer(f.Key, f.Interface, enc)
case ErrorType:
encodeError(f.Key, f.Interface.(error), enc)
case SkipType:
break
default:
panic(fmt.Sprintf("unknown field type: %v", f))
}
if err != nil {enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error())
}
}
AddTo 办法针对 ErrorType 类型的 Field 执行的是 encodeError
encodeError
zap@v1.16.0/zapcore/error.go
func encodeError(key string, err error, enc ObjectEncoder) error {basic := err.Error()
enc.AddString(key, basic)
switch e := err.(type) {
case errorGroup:
return enc.AddArray(key+"Causes", errArray(e.Errors()))
case fmt.Formatter:
verbose := fmt.Sprintf("%+v", e)
if verbose != basic {
// This is a rich error type, like those produced by
// github.com/pkg/errors.
enc.AddString(key+"Verbose", verbose)
}
}
return nil
}
type errorGroup interface {
// Provides read-only access to the underlying list of errors, preferably
// without causing any allocs.
Errors() []error
}
type errArray []error
func (errs errArray) MarshalLogArray(arr ArrayEncoder) error {
for i := range errs {if errs[i] == nil {continue}
el := newErrArrayElem(errs[i])
arr.AppendObject(el)
el.Free()}
return nil
}
encodeError 办法判断 err.(type),若是 errorGroup 则执行 enc.AddArray,若是 fmt.Formatter 且 verbose 不是 basic 则执行
enc.AddString(key+"Verbose", verbose)
Stack
zap@v1.16.0/field.go
func Stack(key string) Field {return StackSkip(key, 1) // skip Stack
}
func StackSkip(key string, skip int) Field {
// Returning the stacktrace as a string costs an allocation, but saves us
// from expanding the zapcore.Field union struct to include a byte slice. Since
// taking a stacktrace is already so expensive (~10us), the extra allocation
// is okay.
return String(key, takeStacktrace(skip+1)) // skip StackSkip
}
Stack 执行 StackSkip,其 skip 为 1;StackSkip 用于构建 String 类型的 stack,同时跳过前几个 frame,这里应用 takeStacktrace 来取所需的 stack
takeStacktrace
zap@v1.16.0/stacktrace.go
func takeStacktrace(skip int) string {buffer := bufferpool.Get()
defer buffer.Free()
programCounters := _stacktracePool.Get().(*programCounters)
defer _stacktracePool.Put(programCounters)
var numFrames int
for {
// Skip the call to runtime.Callers and takeStacktrace so that the
// program counters start at the caller of takeStacktrace.
numFrames = runtime.Callers(skip+2, programCounters.pcs)
if numFrames < len(programCounters.pcs) {break}
// Don't put the too-short counter slice back into the pool; this lets
// the pool adjust if we consistently take deep stacktraces.
programCounters = newProgramCounters(len(programCounters.pcs) * 2)
}
i := 0
frames := runtime.CallersFrames(programCounters.pcs[:numFrames])
// Note: On the last iteration, frames.Next() returns false, with a valid
// frame, but we ignore this frame. The last frame is a a runtime frame which
// adds noise, since it's only either runtime.main or runtime.goexit.
for frame, more := frames.Next(); more; frame, more = frames.Next() {
if i != 0 {buffer.AppendByte('\n')
}
i++
buffer.AppendString(frame.Function)
buffer.AppendByte('\n')
buffer.AppendByte('\t')
buffer.AppendString(frame.File)
buffer.AppendByte(':')
buffer.AppendInt(int64(frame.Line))
}
return buffer.String()}
takeStacktrace 办法通过 runtime.Callers 来获取 frames,之后遍历 frames,将其拼接为 string
实例
func errorStacktraceDemo() {logger, err := zap.NewDevelopment()
defer logger.Sync()
if err != nil {panic(err)
}
logger.Info("errorField", zap.Error(errors.New("demo err")))
fmt.Println(zap.Stack("default stack").String)
fmt.Println("------")
fmt.Println(zap.StackSkip("skip 2", 2).String)
logger.Info("stacktrace default", zap.Stack("default stack"))
logger.Info("stacktrace skip 2", zap.StackSkip("skip 2", 2))
}
输入
2020-12-23T22:19:06.150+0800 INFO zap/zap_demo.go:29 errorField {"error": "demo err"}
main.errorStacktraceDemo
/zap/zap_demo.go:31
main.main
/zap/zap_demo.go:20
runtime.main
/usr/local/go/src/runtime/proc.go:204
------
runtime.main
/usr/local/go/src/runtime/proc.go:204
2020-12-23T22:19:06.150+0800 INFO zap/zap_demo.go:34 stacktrace default {"default stack": "main.errorStacktraceDemo\n\t/zap/zap_demo.go:34\nmain.main\n\t/zap/zap_demo.go:20\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:204"}
2020-12-23T22:19:06.150+0800 INFO zap/zap_demo.go:35 stacktrace skip 2 {"skip 2": "runtime.main\n\t/usr/local/go/src/runtime/proc.go:204"}
小结
zap 提供了 Error 及 Stack 办法用于创立 ErrorType 类型的 error 及 StringType 的 stacktrace;ErrorType 类型的 Field 应用的是 encodeError 办法;takeStacktrace 办法通过 runtime.Callers 来获取 frames,之后遍历 frames,将其拼接为 string。
doc
- zap