关于golang:聊聊golang的zap的error

48次阅读

共计 7169 个字符,预计需要花费 18 分钟才能阅读完成。

本文次要钻研一下 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

正文完
 0