序
本文主要研究一下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
有疑问加站长微信联系(非本文作者)