首頁>技術>

本文主要研究一下golang的zap的error

error

[email protected]/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 []errorfunc (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

[email protected]/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

[email protected]/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 []errorfunc (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

[email protected]/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

[email protected]/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:31main.main        /zap/zap_demo.go:20runtime.main        /usr/local/go/src/runtime/proc.go:204------runtime.main        /usr/local/go/src/runtime/proc.go:2042020-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。

doczap

25
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • EarthWorm內網穿透